Transparent Property Change Listeners in Ruby

madlep

One of my side projects is a framework for building Swing apps using JRuby. I’ve always maintained that Swing is a nice framework - it’s just that Java is a lousy language for writing it in.

One of the areas that Swing is weak in, is binding components to the underlying model. For those unfamiliar with Swing, its designed with a nice way of loosely coupling the model to the GUI components. This is done using listeners - typically Property Change Listeners - which fire whenever the value of some property changes and can be coded to explicitly replicate the state from the model to the view or (vice versa).

…Which is really nice in theory, but in practice it’s not a clean or easy task. It’s usually done badly as there is so much glue code to write if you want a nice pure OO design (developers hate writing boiler plate code). There are Java frameworks like JGoodies Binding and the PropertyChangeSupport class built into the JDK that help out, but in typical Java style, these can get quite complicated, and still require you to carefully manage new Objects to make sure they are manually bound correctly. It usually just ends up being a burden in real life which no one follows properly. (For an example of how NOT to write Swing properly - see Sun’s own Swing tutorials…)

I figured it should be possible to do this in a more Rubyish way. Something that is smooth and easy to use, and provides the best practice as the path of least resistance. I googled a bit and found RubyBeans revisited by Olivier Ansaldi, which seems cool… but involves having to subclass your bean classes from a common base class - some thing I was wanting to avoid. Ideally, the bean classes should be pure and simple Ruby classes build with attr_accessor. (what you’d call a POJO if were in Java land - although this concept is so obvious in Ruby I haven’t even come across a name for it)

Here’s what I came up with:

class ListenerSupport

  public
  def self.listen_to(klass, *properties)
    setup(klass)
    properties.each do |p|
      klass.class_eval do

        alias_method "#{p}_orig=", "#{p}="

        define_method "#{p}=" do |value|
          old_value = send "#{p}"
          send "#{p}_orig=",value
          fire_property_changed p, old_value, value
        end
      end
    end
  end

  private
  def self.setup(klass)
    klass.class_eval do

      def fire_property_changed(prop,pre,post)
        return if pre == post
        @listeners[prop].each do |l|
          l.property_changed(self, prop, pre, post)
        end unless @listeners.nil?
      end

      def add_property_listener(prop,listener)
        @listeners ||= {}
        prop_listeners = @listeners.include?(prop) ? @listeners[prop] : []
        @listeners[prop] = prop_listeners
        prop_listeners << listener unless prop_listeners.include? listener
      end

    end
  end

end

The general idea is that the original class is modified so that all properties that are listened to have their accessor write methods intercepted, and any property change listeners are notified when that property changes.

Let’s walk through how it works:

  • A standard Ruby class with property accessors is created. e.g.
    class RubyBean
      attr_accessor :foo, :bar
    end
  • A call to ListenerSupport.listen_to is made to modify the class to allow properties to be listened to.
    ListenerSupport.listen_to RubyBean, :foo, :bar
  • listen_to adds methods add_property_listener and fire_property_changed to allow listeners to be added to RubyBean for specific properties.
  • listen_to then aliases the original methods foo= and bar= to foo_orig= and bar_orig=. New methods for foo= and bar= are defined that delegate the work to the original method, then call fire_property_changed to notify the listeners.
  • Listeners are called, and any custom action (such as GUI component binding) can be carried out.

An example of this in action

class BeanListener
  def property_changed(obj, property, old_value, new_value)
    puts "#{obj.to_s} updated property:#{property}. old:#{old_value}. new:#{new_value}"
  end
end

class RubyBean
  attr_accessor :foo, :bar
end

ListenerSupport.listen_to RubyBean, :foo, :bar

bean = RubyBean.new
bean.add_property_listener(:foo,BeanListener.new)
bean.add_property_listener(:bar,BeanListener.new)
bean.foo = 'foo1'
bean.foo = 'foo2'
bean.bar = 'bar1'
bean.bar = 'bar2'
bean.bar = 'bar2'

Which outputs:

# updated property:foo. old:. new:foo1
# updated property:foo. old:foo1. new:foo2
# updated property:bar. old:. new:bar1
# updated property:bar. old:bar1. new:bar2

Which shows that the listener is being called each time the property changes :)

Although this doesn’t show any kind of GUI binding, it does show how easy it is to make ordinary Ruby classes (or even Javabeans in JRuby) behave as if they had property change support built in from the start. It’s an important piece of the design a smooth and easy GUI binding framework.

Update - Fixed code after feedback from Pit Capitain in comments - should have used class_eval rather than instance_eval to define new methods in setup()


2 Responses to “Transparent Property Change Listeners in Ruby”

  • Pit Capitain Says:

    Julian, there’s no need for define_method and eval in the setup method. If you change “instance_eval” to “class_eval”, you can simple use def to define the two methods. In any case, you can remove the calls to eval. To get rid of a warning (run your code with ruby -w) change the line “@listeners = {} if @listeners.nil?” to “@listeners ||= {}”. Regards, Pit

  • Julian Doherty Says:

    Thanks for the feedback pit capitain :) Changes incorporated in the original post (and prompted me to read up more on class_eval)

Leave a Reply