Skip to content

Instantly share code, notes, and snippets.

@leucos
Created March 31, 2012 15:21
Show Gist options
  • Save leucos/2266146 to your computer and use it in GitHub Desktop.
Save leucos/2266146 to your computer and use it in GitHub Desktop.
An attempt with Sequel's composition plugin
#
# 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