RubyBeans revisited

Monday, March 6th, 2006

I have refactored the Rubybeans, a short example of Ruby metaprogramming code to make better use of Ruby’s metaprogramming API. I would recommend reading the original post to get the background information (in short: developing JavaBeans is a repetitive task that would greatly benefit from metaprogramming, RubyBeans are an illustration of how this would be done in Ruby). Since this post is also intended for developers not familiar with Ruby, I have tried to be quite verbose while explaining the code.

Buckle your seatbelts! Here we go:

class RubyBean
  def initialize
    @listeners = []
  end

I have just declared the RubyBean class. The initialize method is called everytime a RubyBean is created. It initializes the listeners instance variable to be an empty array. In Ruby, instance variables are preceded with @. Note that, instead of using [], I could have used Array.new. The square bracket notation is more commonly used in Ruby. In case it isn’t obvious, the listeners array will contain the property change listeners registered on this RubyBean.

Now, I add two methods to manage the listeners: one to register a listener on this bean, one to unregister a previously registered bean.

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

  def unregister_listener(l)
    @listeners.delete(l)
  end

In register_listener method takes one parameter l, the listener to be registered. Its code is almost plain english: push the listener at this end of the list of registered listeners, unless it is already there. The unregister_listener method is even simpler: remove the given listener from the list of registered listeners.

The code so far hasn’t used any metaprogramming features. But things are about to change!

In what follows I define a property class method. It is intended to be used by RubyBean subclasses to define their properties. For every argument it receives, the property class method will add 2 methods to the current RubyBean subclass: a getter and a setter.

  def RubyBean.property(*properties)
    properties.each do |prop|

In Ruby, a class method is defined by using the prefixing the method name by the class name: def RubyBean.property (this is equivalent to Java’s static modifier when it is used before a method). The *properties notation lets me capture a variable number of arguments as an array. The first line of the body loops through each element of this array. The name between the pipe characters is the name of the variable used to hold the current element in the block. For the time being, you can consider a block to be akin to an anonymous inline function. Blocks in Ruby come in two flavours do |param, other_param|... end and { |param, other_param|... } (note that if no parameter is required by the block, there is no need to use the pipe characters).

      define_method(prop) {
        instance_variable_get("@#{prop}")
      }

We’ve just written our last ever getter! The accessors in Ruby do not follow the JavaBeans conventions: the getter for a property is simply the property name (getName() is name) and the related setter is the property name followed by equal (setName(...) is name=). The previous code defines a method named using the content of the prop variable. The body of this method is defined by the block between the curly braces (this block has no parameters, so no need for pipe characters this time). The block will return the value of the instance variable named prop. In a double quoted string, #{…} is replaced by the evaluation of its content, here prop. So if the value of proc is name, "@#{prop}" will be "@name".

The following code defines how we declare setter methods. It also uses the #{...} notation, this time to concatenate the property name to equal, to create method name of the setter.

      define_method("#{prop}=") do |value|
        old_value = instance_variable_get("@#{prop}")
        return if (value == old_value)

The block for the setter accepts one argument, the value, as indicated between the pipe characters. As per Java conventions, we first make sure that the new value is different from the current value. If the values are the same, no need to notify the listeners, so we simply return.

Otherwise, we loop on all the listeners and notify them of which property is changing from what value to what value:

        @listeners.each { |listener|
          listener.property_changed(prop,
                                   old_value,
                                   value)
        }
        instance_variable_set("@#{prop}", value)
      end

Once all listeners have been notified, we set that property to its new value.

And finally, a bit of tidying up:

    end # loop on properties
  end # end of property method
end # end of RubyBean class

I hope this has enlightened you! Let me know!

Advertisements

3 Responses to “RubyBeans revisited”

  1. Nithin Says:

    Instead of:
    define_method(prop) { instance_variable_get(“@#{prop}”) }

    you can also do this:
    define_method(prop)
    attr_reader prop.to_sym # prop by itself also worked for me
    # code snip — setter method
    end

  2. ozone Says:

    Absolutely Nithin! I avoided using attr_reader (and related methods) because I wanted to show how these could be implemented, from scratch. Why miss an occasion to demonstrate metaprogramming!


  3. […] 06/03/2006: see RubyBeans revisited for a better […]


Comments are closed.

%d bloggers like this: