Thursday 22 April 2010

Method chaining with Ruby

Implementing method chaining in Ruby is a bit fiddly. Let's, for example and without loss of generality, look at plain data holders: objects whose sole existence in life is to hold some data.
Standard data holders, in Ruby, are easily defined using attr_accessors. For example:

class Person
attr_accessor :name, :surname
end

person = Person.new
person.name = "Bob"
person.surname = "Smith"
p person.name
p person

produces

"Bob"
#<person:0xb78925a8 surname="Smith" name="Bob">

But it would be far better and more compact to be able to use method chaining, so to be able to write:

person = Person.new.name("Bob").surname("Smith")
person.name
person.surname

To achieve it, some Ruby metaprogramming is required. Jay Fields wrote an article some time ago providing an implementation that is not entirely satisfactory. His implementation differenciates between getters and setters and the example above would look like

person = Person.new.set_name("Bob").set_surname("Smith")
person.name
person.surname

It's possible to do better by doing:

module MethodChain
def chained_attr_accessor(*names)
names.each do |name|
define_method :"#{name}" do | *args |
return instance_variable_get(:"@#{name}") if args.length == 0
if args.length == 1
instance_variable_set(:"@#{name}", args[0])
return self
end
raise ArgumentError.new("wrong number of arguments (#{args.length} for 1)")
end
end
end
end

class Person
extend MethodChain
chained_attr_accessor :name, :surname
end

person = Person.new
person.name("Bob").surname("Smith")
p person.name
p person

which, undoubtedly, will produce the desired output:

"Bob"
#<Person:0xb776ff40 @surname="Smith", @name="Bob">

It could be neat, where appropriate, to add the chain_attr_accessor to the Class class, so that it's not even necessary to extend the class with the module:

class Class
def chained_attr_accessor(*names)
#...
end
end

class Person
# extend MethodChain
chained_attr_accessor :name, :surname
end


Conclusion



Extending the code is trivial. What the module all does is defining a method on the class Person with the name of the attribute. Each implementation checks the length of the arguments list and decided if behaving like a setter or a getter.

More complicated logic can be implemented to allow more than one input or provide only getters for calculated attributes.

This technique can also be used to go beyond simple data holders, towards fluid interfaces.

But all that is left as an exercise to the reader.

4 comments:

Manrico Corazzi said...

kudos for you!

Melody Class said...

interesting article, thanks

Anonymous said...

Thanks for the article. Of course, though, the examples are not the way you construct an object. Rather:

Person.new(:firstname=>"Bob", :surname=>"Smith")

as idiomatic. I would strongly urge Rubyists to *not* build object (particularly those in the domain model) using this mechanism.

Method chaining is useful on objects when they are immutable and you are using some "tell-don't-ask" methods to end up with a new immutable object (the original one perhaps no longer being reference and garbage collected).

Method chaining is best suited for fluent interfaces (as you mentioned), especially on APIs where you are providing a series of commands to transform data like you might with Unix "pipes".

RSpec is a great example of this, particularly RSpec 2.

Cheers,
JoshG

smartrics said...

Joshua, fair point on constructing objects using the more idiomatic form in generic ruby code.

I am (was) building a DSL and I opted to provide users with a single "look&feel" by means of method chaining for the initialization of domain objects