On May 6, 2008, at 3:33 AM, Pelle Strul wrote:
> Hi, I'm trying to load a file with specifications like:
>
> title     :Person
> attribute :name, String
> attribute :age,  Fixnum
> constraint :name, 'name != nil'
> constraint :name, 'name.size > 0'
> constraint :name, 'name =~ /^[A-Z]/'
> constraint :age, 'age >= 0'

i'd do something like



cfp:~ > cat a.rb
class Specd
   alias_method '__eval__', 'instance_eval'

   instance_methods.each{|m| undef_method m unless m[%r/__/]}

   def Specd.build config
     c = Class.new
     new(c).__eval__(config)
     Object.send :const_set, c.title, c
     Object.send :const_get, c.title
   end

   def initialize c
     @class = c
     @singleton_class =
       class << @class
         self
       end
   end

   def title name
     @class.module_eval{ @title = name }
     @singleton_class.module_eval{ attr_accessor :title }
   end

   def attribute name, type
     constraint name, type
   end

   def constraint key, constraint
     @class.module_eval do
       key = key.to_s

       ( ( @@constraints ||= Hash.new )[key] ||= [] ).push( constraint )

       reader, writer, ivar = "#{ key }", "#{ key }=", "@#{ key }"

       unless instance_methods(false).include?(key)
         attr reader

         define_method(writer) do |value|
           previous = instance_variable_get ivar
           begin
             instance_variable_set ivar, value
             @@constraints[key].each do |constr|
               ok =
                 case constr
                   when Module
                     constr === value
                   when String
                     instance_eval(constr)
                   else
                     true
                 end
               raise ArgumentError, "#{ value.inspect } [#{ constr }]"  
unless ok
             end
           rescue Object => e
             instance_variable_set ivar, previous
             raise
           end
         end
       end
     end
   end
end


Specd.build(
   <<-text
     title     :Person
     constraint :name, 'name =~ /^Z/'
     attribute :name, String
     attribute :age,  Fixnum
     constraint :name, 'name != nil'
     constraint :name, 'name.size > 0'
     constraint :age, 'age >= 0'
   text
)

person = Person.new
person.name = 'Zaphod'
person.age = 42

p person

begin
   person.name = 'lowercase'
rescue
   puts $!.message
end

begin
   person.age = -42
rescue
   puts $!.message
end


cfp:~ > ruby a.rb
#<Person:0x24db0 @age=42, @name="Zaphod">
"lowercase" [name =~ /^Z/]
-42 [age >= 0]





a @ http://codeforpeople.com/
--
we can deny everything, except that we have the possibility of being  
better. simply reflect on that.
h.h. the 14th dalai lama