Ruby’s Forwardable

Last night I had the pleasure of attending the Arlington Ruby User Group meeting in Arlington, Virginia. Marius Pop, a new Rubyist, presented on Ruby’s Forwardable module. Forwardable allows you to very succinctly specify that you want to define a method that simply calls (that is, delegates to) a method on one of the object’s instance variables, and returns its return value, if there is one. Here is an example file that illustrates this:

require 'forwardable'

class FancyList
  extend Forwardable
  
  def_delegator :@records, :size
  
  def initialize
    @records = []
  end
  
end

puts "FancyList.new.size = #{FancyList.new.size}"
puts "FancyList.new.respond_to?(:size) = #{FancyList.new.respond_to?(:size)}"

# Output is:
# FancyList.new.size = 0
# FancyList.new.respond_to?(:size) = true

After the meeting I thought of a class I had been working on recently that would benefit from this. It’s the LifeTableModel class in my Life Game Viewer application, a Java Swing app written in JRuby. The LifeTableModel is the model that backs the visual table (in Swing, a JTable). Often the table model will contain the logic that provides the data to the table, but in my case, it was more like a thin adapter between the table and other model objects that did the real work.

It turned out that almost half the methods were minimal enough to be replaced with Forwardable calls. The diff is shown here:

The modified class is viewable on Github here.

As you can see, there was a substantial reduction in code, and that is always a good thing as long as the code is clear. More importantly, though, def_delegator is much more expressive than the equivalent standard method definition. It’s much more precise because it says this function delegates to another class’ method exactly, in no way modifying the behavior or return value of that other function. In a standard method definition you’d have to inspect its body to determine that. That might seem trivial when you’re considering one method, but when there are several it makes a big difference.

One might ask why not to use inheritance for this, but that would be impossible because:

a) the class delegates to three different objects, and
b) the class already inherits from AbstractTableModel, which provides some default Swing table model functionality.

Marius showed another approach that delegates to the other object in the method_missing function. This would also work, but has the following issues:

a) It determines whether or not the delegate object can handle the message by calling its respond_to method. If that delegate intended to handle the message in its method_missing function, respond_to will return false and the caller will not call it, calling its superclass’ method_missing instead.

b) The delegating object will itself not contain the method. (Maybe the method_missing handling adds a function to the class, but even if it does, that function will not be present when the class is first loaded.) So it too will return a misleading false if respond_to is called on it.

c) In addition to not communicating its capabilities to objects of other classes, it does not communicate to the human reader what methods are available on the class. One has to look at the class definition of the delegate object, and given Ruby’s duck typing, that may be difficult to find. It could even be impossible if users of your code are passing in their own custom objects. This may not be problematic, but it’s something to consider. (I talk more about duck typing’s occasional challenges at Design by Contract, Ruby Style.)

It was an interesting subject. Thank you Marius!

One Response to “Ruby’s Forwardable”

  1. Marius says:

    Thanks for the post Keith!

Leave a Response

You must be logged in to post a comment.