Archive for May, 2007

RubyBeans revisited (again) – Closures in Ruby

Tuesday, May 1st, 2007
Coffee Beans

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 = []

  def register_listener(l)
    @listeners.push(l) unless @listeners.include?(l)

  def unregister_listener(l)

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.

    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)
        value = new_value

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

class LoggingPropertyChangeListener
  def property_changed(property, old_value, new_value)
    print property, " changed from ",
      old_value, " to ",
      new_value, "\n"

test =
listener =
test.register_listener(listener) = "Parker"
test.firstname = "Charlie"
test.firstname = "Maceo"

Let me know if you spot something else!