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

About these ads

One Response to “RubyBeans revisited (again) – Closures in Ruby”


  1. […] 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 […]


Comments are closed.

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: