Metaprogramming problem

I'm trying to define s.t. like attr_accessor, but one that takes
an optional block defining a function that filters the value on
the setter. Perhaps that will be clear if I show you the snippet:

class Object
     def add_property(sym, &block)
         n = sym.id2name
         code = %Q{
             def #{n}()
                 @#{n} ||= nil
             end
             def #{n}=(value)
                 @#{n} = block ? block.call(value) : value
             end
         }
         eval(code)
     end
end

# A test object:
d = 3

# Add a getter and custom setter to 3:
d.add_property(:do_it) {|v|
     # Custom setter method
     puts "d.do_it here, v = #{v.inspect}"
     v # No filtering this time, just return the value.
}

# Assign the property:
d.do_it = "a value"

Ok, I can see why "block" isn't defined. How can I pass add_property's
block in as a block to be used *inside* the evaluated code?

Clifford Heath.

Clifford Heath wrote:

I'm trying to define s.t. like attr_accessor, but one that takes
an optional block defining a function that filters the value on
the setter. Perhaps that will be clear if I show you the snippet:

class Object
     def add_property(sym, &block)
         n = sym.id2name
         code = %Q{
             def #{n}()
                 @#{n} ||= nil
             end
             def #{n}=(value)
                 @#{n} = block ? block.call(value) : value
             end
         }
         eval(code)
     end
end

# A test object:
d = 3

# Add a getter and custom setter to 3:
d.add_property(:do_it) {|v|
     # Custom setter method
     puts "d.do_it here, v = #{v.inspect}"
     v # No filtering this time, just return the value.
}

# Assign the property:
d.do_it = "a value"

Ok, I can see why "block" isn't defined. How can I pass add_property's
block in as a block to be used *inside* the evaluated code?

You can use define_method:

class Module
  def add_property(sym,&test)
    define_method("#{sym}=") do |val|
      test && test[val]
      instance_variable_set("@#{sym}", val)
    end
    
    define_method(sym) do |val|
      instance_variable_get("@#{sym}")
    end
  end
end

class Foo
  add_property :age do |x|
    raise "Illegal negative" unless x >= 0
  end
end

Foo.new.age = -2

RuntimeError: Illegal negative
        from (irb):55
        from (irb):54:in `'
        from (irb):43:in `age='
        from (irb):42:in `age='
        from (irb):59

Foo.new.age = 1

=> 1

Foo.new.age = 0

=> 0

Kind regards

    robert

Here's *a* working solution. The big string + eval feels like a serious hack, but I'm not cool enough to know how to deal with it properly.

class Object
     def add_property(sym, &block)
         n = sym.id2name
         (@_property_callbacks||={})[ n ] = block
         code = %Q{
             def #{n}()
                 @#{n} ||= nil
             end
             def #{n}=(value)
                 if block = @_property_callbacks[ '#{n}' ]
                     @#{n} = block.call(value)
                 else
                     @#{n} = value
                 end
             end
         }
         eval(code)
     end
end

# A test object:
d = 3

# Add a getter and custom setter to 3:
d.add_property(:do_it) {|v|
# Custom setter method
     puts "d.do_it here, v = #{v.inspect}"
     v # No filtering this time, just return the value.
}

# Assign the property:
d.do_it = "a value"

···

On Sep 19, 2005, at 6:16 AM, Clifford Heath wrote:

Ok, I can see why "block" isn't defined. How can I pass add_property's
block in as a block to be used *inside* the evaluated code?

Robert Klemme wrote:

You can use define_method:

Doh! I went searching for that, since it wasn't in my
(ruby 1.6) HTML manual and I expected it to have been
added in 1.8 :-)... But I tried

p Object.methods
p Class.methods
p Module.methods
p Kernel.methods

without success, so I stopped looking :frowning:

Thanks for the help. I have the 1.8 book, both paper
and PDF, but the HTML is so convenient :-).

Clifford Heath.

Clifford Heath wrote:

Robert Klemme wrote:

You can use define_method:

Doh! I went searching for that, since it wasn't in my
(ruby 1.6) HTML manual and I expected it to have been
added in 1.8 :-)... But I tried

p Object.methods
p Class.methods
p Module.methods
p Kernel.methods

without success, so I stopped looking :frowning:

Thanks for the help. I have the 1.8 book, both paper
and PDF, but the HTML is so convenient :-).

Clifford Heath.

An additional remark: you can improve performance if you extract the test
for the block's presence from the method and use your approach of code
generation for the getter and for the setter (for the case where there is
no block).

Kind regards

    robert