RubyBeans, a short example of Ruby metaprogramming

Wednesday, February 22nd, 2006

As I explained in my recent post about metaprogramming, implementing a JavaBean is a task that would greatly benefit from metaprogramming. Here, I illustrate how this would be done in Ruby. Note that the resulting code has little interest for Ruby as JavaBeans are a Java idiom. I hope however that it demonstrates my purpose. Let’s dive straight into the code:

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

These methods implement the management of the property change listeners on the bean. It is a subset of what java.beans.PropertyChangeSupport provides. Now comes the most interesting part:

  def RubyBean.property(*properties)
    properties.each { |property|
      class_eval(%Q[
        def #{property}
          @#{property}
        end

        def #{property}=(value)
          old_value = @#{property}
          return if (value == old_value)
          @listeners.each { |listener|
            listener.property_changed(:#{property},
                                     old_value,
                                     value)
          }
          @#{property} = value
        end
      ])
    }
  end
end

I define property as a class method that takes a variable number of arguments. For each of its arguments, property dynamically creates and adds to the RubyBean class, a getter and a setter to the bean. This follows the Ruby conventions for accessors: the getter is simply the property name, the setter is the property name followed by the equal sign. The setter also notifies the registered property change listeners. Note that in Ruby, you would use the attr_accessor method to create these accessors.

Now, writing a bean is as simple as:

class TestBean < RubyBean
  property :name, :firstname
end

Simple, no? Finally, here is how this code could be used:

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

test = TestBean.new
listener = LoggingPropertyChangeListener.new
test.register_listener(listener)
test.name = "Parker"
test.firstname = "Charlie"
test.firstname = "Maceo"
test.unregister_listener(listener)

and the output it generates:

name changed from nil to Parker
firstname changed from nil to Charlie
firstname changed from Charlie to Maceo

Do not hesitate to comment if you have any query, suggestion, critic or simply if you want to say Hi!

Advertisements

9 Responses to “RubyBeans, a short example of Ruby metaprogramming”

  1. Nithin Says:

    Nice Example… how would you create static methods using metaprogramming? For example a class method that gives the number of properties that have been assigned.

    test = TestBean.new
    test.name = “parker”
    test2 = TestBean.new
    test2.name = “lewis”
    TestBean.name_count # => 2
    TestBean.firstname_count # => 0

    You can use another example if you want. It was the most trivially one that I could come up with.

  2. ozone Says:

    Nithin,

    you could do it this way:

    def RubyBean.property(*properties)
      properties.each { |property|
        class_eval(%Q[
          @@#{property}_count = 0
    
          def #{property}
            @#{property}
          end
    
          def #{property}=(value)
            oldValue = @#{property}
            return if (value == oldValue)
            @listeners.each { |listener|
              listener.propertyChanged(:#{property},
                                       oldValue,
                                       value)
            }
            @#{property} = value
            @@#{property}_count += 1
          end
    
          def self.#{property}_count
            @@#{property}_count
          end
        ])
      }
    end
    

    Note that there are more elegant ways than using class_eval to do metaprogramming (see this post).

    Have a look at this article from Why-the-lucky-stiff. He is a Ruby Legend!


  3. […] Today, I refactored the Rubybeans code to illustrate more metaprogramming methods in Ruby. Since the original article is mainly intended for Java developers, I have decided to explain the code with more details (you probably won’t need to have a look at a Ruby book). […]


  4. […] Ruby is a highly mutable language.  Objects and methods are first-class types that can be manipulated as easily as a string or an integer.  Objects can examine themselves (called “reflection”) or modify themselves by adding or changing methods.  This capability is what makes Rails so kick-ass.  It’s also why everyone’s talking about Ruby for domain specific languages (DSLs). […]

  5. Milburn Young Says:

    Nice example of meta-programming. Don’t your class variables in your answer to Nithin’s question actually count the number of times a property is assigned a different value, not the number of properties that have that property assigned?

    I’d like to see more real-world examples like this. For example, a meta-class that can become a ADT where even the operators are meta-defined.


  6. Hi Milburn,

    you are quite right!

    Moving the @@#{property}_count += 1 line up to the beginning of the method, just after def #{property}=(value) solves the problem.

    Thanks for spotting this and for your comments!

  7. Thomas Gies Says:

    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?


  8. […] comment by Thomas Geis on my first RubyBeans post lead me to another rework of the RubyBeans metaprogramming […]


Comments are closed.

%d bloggers like this: