
A comment by Thomas Geis on my first RubyBeans post lead me to another rework of the RubyBeans metaprogramming example.
Thomas accurately points out:
I think the property should sit in a proxy-object, that does inform the listeners of its change. In your example, one could directly change the properties value from within the class without a call to the property_changed method.
This would be a concern for me. Isn’t it a concern for you?
Yes it is! I tried the proxy-object solution, but I am not satisfied with it for two reasons: the code get lengthy and inelegant, but mostly because the proxy-objects themselves aren’t protected from direct access.
I believe I have a nicer solution to protect the properties from direct access: closures. The first few lines of code remain unchanged:
class RubyBean
def initialize
@listeners = []
end
def register_listener(l)
@listeners.push(l) unless @listeners.include?(l)
end
def unregister_listener(l)
@listeners.delete(l)
end
Now, observe how value is captured in the getter and setter blocks. The scope of the value local variable is the local block and the blocks passed in define_method. These blocks “close over” the value local variable.
def self.property(*properties)
properties.each do |p|
value = nil
define_method(p) { return value }
define_method("#{p}=") do |new_value|
return if value == new_value
@listeners.each do |l|
l.property_changed(p, value, new_value)
end
value = new_value
end
end
end
end
Finally, here is an example of how RubyBeans would be used (were they not a Java idiom shamelessly ported for illustration purposes). The impotent_name= method cannot affect the name property, it will create a name instance variable, unrelated to the property.
class SimpleBean < RubyBean
property :name, :firstname
def impotent_name=(new_name)
@name = new_name
end
end
class LoggingPropertyChangeListener
def property_changed(property, old_value, new_value)
print property, " changed from ",
old_value, " to ",
new_value, "\n"
end
end
test = SimpleBean.new
listener = LoggingPropertyChangeListener.new
test.register_listener(listener)
test.name = "Parker"
test.firstname = "Charlie"
test.firstname = "Maceo"
test.unregister_listener(listener)
Let me know if you spot something else!

Tuesday, May 1st, 2007 at 16:41
[...] Io! A slow-paced introduction to the Io languageRubyBeans, a short example of Ruby metaprogrammingRubyBeans revisited (again) – Closures in RubyReinventing the wheel and associated painsBinary Search Tree sauce Ruby (part 1)The Panda [...]