From: Sean O'Halpin Date: 2008-05-08T11:18:33+09:00 Subject: Re: Reading from file, create a class with variables On Wed, May 7, 2008 at 5:03 PM, Daniel Finnie wrote: > Hi, > > I think that the Doodle Rubygem might be a good fit for this purpose Indeed it is - see the code below. This requires the latest version 0.1.6 (which among other things renames 'attributes' to avoid name clashes). Classes that define classes are the happiest classes :) # see ruby-talk:300767 require 'rubygems' # note: this requires doodle 0.1.6+ require 'doodle' # set up classes to manage class definitions of form: # title :Person # attribute :name, String # attribute :age, Fixnum # constraint :name, 'name != nil' # constraint :name, 'name.size > 1' module ClassDef class Attribute < Doodle has :name, :kind => Symbol has :kind, :kind => Class end class Constraint < Doodle has :key, :kind => Symbol has :condition, :kind => String end class Definition < Doodle has :title, :kind => Symbol has :attributes, :collect => Attribute has :constraints, :collect => Constraint # the names don't have to match - you could have this, for example: # has :validations, :collect => { :constraint => Validation } # create a new object from a string containing Ruby source for # an initialization block - this method works with any Doodle # class def self.load(str, context = self.to_s + '.load') begin new(&eval("proc { #{str} }", binding, context)) rescue SyntaxError, Exception => e raise e, e.to_s, [caller[-1]] end end end # this is the core method that defines a class def self.define(source, namespace = Object, superclass = Doodle) # read class definition cd = Definition.load(source, 'example') # create anonymous class klass = Class.new(superclass) do include Doodle::Core if !(superclass <= Doodle) cd.attributes.each do |attribute| has attribute.name, :kind => attribute.kind end # the constraints as given work as class level constraints in # Doodle so that's what we're setting up here cd.constraints.each do |constraint| must "have " + constraint.condition do instance_eval(constraint.condition) end end end # associate anonymous class with constant name namespace.const_set(cd.title, klass) # and add factory method/shorthand constructor (if wanted) - has to happen ~after~ class has a name klass.class_eval { include Doodle::Factory } klass end end # demo source = %[ title :Person attribute :name, String attribute :age, Fixnum constraint :name, 'name != nil' constraint :name, 'name.size > 1' constraint :name, 'name =~ /^[A-Z]/' constraint :age, 'age >= 0' ] # install class definitions in their own namespace - you don't have to # do this - this is just to show how module Outer module Inner end end ClassDef.define(source, Outer::Inner) # return value or exception from block def try(&block) begin block.call rescue Exception => e e end end module Outer::Inner # example use of newly defined class (run this file with xmpfilter to display output) try { person = Person.new :name => "Arthur", :age => 42 } # => # try { person = Person.new :name => "arthur", :age => 42 } # => # try { person = Person.new :name => "Arthur", :age => -1 } # => #= 0> try { person = Person.new :name => "", :age => -1 } # => # 1> try { person = Person.new :name => nil, :age => -1 } # => # try { person = Person.new :name => "", :age => 42 } # => # 1> try { person = Person.new :name => nil, :age => 42 } # => # try { person = Person.new :name => "Arthur", :age => "1" } # => # # or using Doodle postitional args try { person = Person.new("Arthur", 42) } # => # # and shorthand constructor try { person = Person("Arthur", 42) } # => # try { person = Person(:name => "Arthur", :age => 42) } # => # end require 'yaml' try { Outer::Inner::Person.new(:name => "Arthur", :age => 42).to_yaml } # => "--- !ruby/object:Outer::Inner::Person \nage: 42\nname: Arthur\n" Apologies if this looks a mess - you may have to edit line breaks to get the code to work. Regards, Sean