Created
March 31, 2012 15:21
-
-
Save leucos/2266146 to your computer and use it in GitHub Desktop.
An attempt with Sequel's composition plugin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# The purpose of the database is to hold nameserver zone records | |
# | |
# Unfortunately, the SOA type record contains several interesting space-separated | |
# "inner" fields we would like to access individually. | |
# | |
# The point is is to "autovivify" getters and | |
# setters for these "inner fields" | |
# | |
# I'll try to solve this using Sequel's composition plugin | |
# described in the Sequel documentation and announcement post : | |
# - http://sequel.heroku.com/2010/03/08/composition-plugin-added/ | |
# - http://sequel.rubyforge.org/rdoc-plugins/classes/Sequel/Plugins/Composition.html | |
# | |
# Unfortunately, the example in the documentation does the opposite : | |
# it builds one getter/setter from multiple record columns from on field, | |
# while we want to build multiple getter/setters from on record column | |
# | |
# This is my unsuccessful attempt to make it work, thanks for reading & feel free to comment | |
# | |
######## | |
# In-memory test database | |
# The schema has been simplified for the discussion (yes, it should have some domain_id somewhere !) | |
# | |
# Values in the content field for a SOA, are, in order : | |
# ns email serial refresh retry expiry minimum | |
DB = Sequel.sqlite | |
DB.run "CREATE TABLE records (name VARCHAR(255) PRIMARY KEY NOT NULL, type VARCHAR(5), content VARCHAR(255))" | |
DB.run "INSERT INTO records VALUES ('example.com', 'SOA', 'ns.example.com root.example.com 2012333335 28800 86400 3600000 86400')" | |
# Just an assert function for basic testing | |
def assert | |
printf '.' | |
raise "#{caller} => assertion failed !" unless yield | |
end | |
# The record model | |
class Record < Sequel::Model | |
plugin :composition | |
# This is the list of the inner fields, located in the 'content' | |
# column of the SOA type records | |
# The fields are listed in order so we can map easily to the | |
# splited column | |
@@soalist = [ :ns, :email, :serial, :refresh, | |
:retry, :expiry, :minimum ] | |
# We will need on composer per field, lets create a Hash to store the | |
# composer procs inside | |
@@soa_composer = {} | |
# Composers return the values we want from the underlying record | |
# Creates a composer proc for each field | |
@@soalist.each do |k| | |
@@soa_composer[k] = proc do | |
# We only do this for SOA type records | |
self.content.split()[@@soalist.index(k)] if self.type == 'SOA' | |
end | |
end | |
# Decomposer builds the record column at save time | |
# We'll just need one decomposer, identical for all composers | |
soa_decomposer = proc do | |
@@soalist.each do |f| | |
#puts "deco #{f}, value is #{compositions[f]} or #{self.send(f)}" | |
#puts "\t<=#{self.content}" | |
# We split self.content, so we can replace the required inner field | |
# Thanks to @@soalist, we can map inner values easily | |
# There's probably a more elegant way though | |
fields = self.content.split | |
fields[@@soalist.index(f)] = self.send(f) | |
self.content = fields.join(' ') | |
#puts "\t=>#{self.content}" | |
end | |
end | |
# Create '@@soalist.count' composers, one for each 'iner field' | |
@@soalist.each do |k| | |
composition k, | |
:composer => @@soa_composer[k], | |
:decomposer => soa_decomposer | |
end | |
end | |
# Tests | |
# Uncommented tests are passing | |
# Commented tests are failing | |
# | |
print "Testing." | |
a = Record.first | |
# Original content | |
assert { a.content == 'ns.example.com root.example.com 2012333335 28800 86400 3600000 86400' } | |
a.ns = "newns.example.com" | |
a.email = "hostmaster.example.com" | |
a.serial = "00000" | |
a.refresh = "11111" | |
a.retry = "22222" | |
a.expiry = "33333" | |
a.minimum = "44444" | |
# 1st downside : asserting before save will fail | |
# | |
#assert { a.ns == 'newns.example.com' } | |
#assert { a.email == 'hostmaster.example.com' } | |
#assert { a.serial == '00000'} | |
#assert { a.refresh == '11111' } | |
#assert { a.retry == '22222' } | |
#assert { a.expiry == '33333' } | |
#assert { a.minimum == '44444'} | |
# 2nd problem : when save is called, the decomposer will | |
# be called for each inner item. This is quite suboptimal | |
# since the decomposer takes care of each field everytime | |
a.save | |
# Everythings works perfectly below | |
assert { a.content == 'newns.example.com hostmaster.example.com 00000 11111 22222 33333 44444' } | |
a.save | |
a = Record.first | |
assert { a.content == 'newns.example.com hostmaster.example.com 00000 11111 22222 33333 44444' } | |
assert { a.ns == 'newns.example.com' } | |
assert { a.email == 'hostmaster.example.com' } | |
assert { a.serial == '00000'} | |
assert { a.refresh == '11111' } | |
assert { a.retry == '22222' } | |
assert { a.expiry == '33333' } | |
assert { a.minimum == '44444'} | |
puts "done" | |
__END__ | |
# May be it would be easier if we could pass a parameter to the composer, | |
# something, along the lines, that would look like this : | |
# | |
# | |
# Composer | |
soa_composer = proc do |f| | |
# We only do this for SOA type records | |
self.content.split()[@@soalist.index(f)] if self.type == 'SOA' | |
end | |
# Decomposer builds the record column at save time | |
# We'll just need one decomposer, identical for all composers | |
soa_decomposer = proc do |f| | |
fields = self.content.split | |
fields[@@soalist.index(f)] = self.send(f) | |
self.content = fields.join(' ') | |
end | |
# Create a composer/decomposer for each inner field | |
# The {de,}composer receives the field symbol as the first argument | |
# and acts accordingly | |
@@soalist.each do |k| | |
composition k, | |
:composer => soa_composer(k), | |
:decomposer => soa_decomposer(k) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment