Method Chaining is an important technique to create beautiful code in object oriented languages, but Ruby (the beautiful language, right?) does not support it as well as it should.
There has been a lot of discussion about finding ways to chain methods and effectively deal with nil values in Ruby.
One issue is that the standard libraries do not always return convenient values for chaining. But this isn’t as much of a problem if we can find ways of supporting Ad Hoc method chaining. This is facilitated mostly by changing or guarding against the return value of a method.
Let’s discuss suggested solutions, and then I will propose my own solutions, which I think offer advantages.
Current Solutions
Object#tap
So far the community has found one great tool for changing the return value of a method in the form of Object#tap. In Rails, Object#returning is similiar.
Some of Ruby’s methods like compact! and flatten! can return nil where it makes more sense to just return the object to allow method chaining.
arr = [1] arr.compact! # => nil arr.first # => 1
So Object#tap was created, which will return the tapped object after evaluating a block.
[1].tap {|arr| arr.compact!}.first # => 1
Using the Symbol#to_proc extension we can write
[1].tap(&:compact!).first # => 1
But why not just write:
[1].tap(:compact!).first # => 1
The code for this is actually quite straightforward.
class Object # yield or eval based on the block arity def yield_or_eval &block case block.arity # ruby bug for -1 when 0, -1 then instance_eval(&block) when 1 then yield(self) else raise ArgumentError, "too many arguments required by block" end end private :yield_or_eval def tap meth=nil, &block __send__ meth if meth yield_or_eval(&block) if block_given? self end end
yield_or_eval allows you to write two differnt forms where the context of evaluation is different.
[1].tap {|s| s.compact!}.first # => 1 [1].tap {compact!}.first # => 1
in the first form, the context of evaluation is a normal block context – the context of the code being written. The object being tapped [1] is yielded to the block. The second form is evaluated in the context of the array object [1], which is then an implicit receiver for the compact! call
Here is a nice article about changing the context of a block and the result returned
Guarding against (nil) values when chaining
Testing for nil is a very common idiom in Ruby
person = nil # more code ... name = person ? person.name : nil
This isn’t very painful. The problem is when we have to start introducing local variables.
local variable creation
def find(*args) # do some expensive database queries end person = find(:first) @name = person && person.name # => nil
Now there is another variable to track in the code. To understand what is going on we will have to look at the rest of the code to make sure person is not being used anywhere else. We also get the feeling that we should be able to accomplish this with one lind of code instead of two.
In other languages can easily define the scope of a variable so that it won’t exist after you are done using them. Lisp uses let expressions
(let (person (find :first)) (if person person.name nil)) # person doesn't exist here outside of the let expression
In Ruby, we create variables scoped to blocks, but we have never stopped to create a let construct. ick by Reginald Braithwaite does just that.
let( find(:first) ) { |person| person.name if person }
removing unecessary local variables
ick can do one better than scoping variables. It can remove them by creating implicit guards, in this case guarding against nil.
maybe( find(:first) ) { |person| person.name }
The guard continues working for all the methods in the block
maybe( find(:first) ) { |person| person.name.upcase[0..5] }
Ick is actually a toolset for building these kinds of expressions, and shows great promise. But it is implemented by wrapping objects, which has a performance penalty, and allows incorrect uses, as quoted from the honest documentation:
... wrappers are unidirectional: they only work when you are calling methods on your value, not when you are passing the wrapped value as a parameter to any other method. This is hideous: why should there be any difference between { |value| value + 1 } and { |value| 1 + value }?
For the most common operations, we may as well implement direct method calls without creating wrapper classes, and try to make them as fool-proof as possible.
Object#try is a solution that I like, and is very similiar to some of the solutions I will suggest. However, it uses Object#respond_to?, so I think it is a solution to a different problem then the one it is being proposed for! We are not trying to test to see if an object responds to a message, we are trying to guard against it (being nil). For example:
# guarding against nil in this case nil.try(:some_method) # => nil # but not in this one! nil.try(:to_s) # => ""
Some have suggested (jokingly, I think) modifying method_missing of nil. This is very unsafe because it can allow errors to propogate.
I have also seen the following idiom:
@name = person.name rescue nil
This is terse, but bad because meth could be raising any number of exceptions, but a one-liner rescue catches all exceptions. It also obscures the intent. We are not trying to recover from exceptions, just trying to test for nil.
My System
I have been using my own solutions to this problem, so I thought I would share because I think they provide advantages over the previously proposed solutions.
Object#then and Object#else
Update! Object#then and Object#else can be passed messages like Object#chain. see the README file
Using yield_or_eval from above:
class Object def then &block if self yield_or_eval(&block) else self end end end @phone = find(:first).then {phone} # => nil
We have made it a one-liner with no local variables, or even block variables! We can also define Object#else as the opposite of Object#then.
'a'.then{'b'}.else{'c'} #=> 'b' nil.then{'b'}.else{'c'} #=> 'c'
Object#tap
Now I want to extend what we have been doing to allow guarded method chaining. First Lets talk about Object#tap again. The tap I showed before was a slightly simplified version. Instead of sending a method name, we can be more flexible and send arguments, or evaluate a proc. Consider that Symbol#to_proc converts a symbol into a proc. We can extend this idea to convert an array of a method name and arguments from an array to a proc.
class Array def to_proc proc{|o| o.send *self} end end [1,2].map &[:*, 2] # => [2, 4]
But instead of making the caller use &, we can put the responsibility on the method to do the conversion.
class Array def map arg=nil, &block res = [] if arg # instance_eval to send the private method send_as_funciton each {|o| res << o.instance_eval {send_as_function arg} } elsif block each {|o| res << yield(o) } else fail ArgumentError, "No arguments given" end res end end [1,2].map [:*, 2] # => [2, 4]
send_as_function can send a symbol as a method, an Array as a method with arguments, or evaluate a normal proc object.
class Object def send_as_function arg case arg when Symbol then __send__ arg when Array then __send__(*arg) else yield_or_eval(&arg) end end private :send_as_function end
I like this better then having the caller use Symbol#to_proc because we don’t have to define to_proc type conversions which could cause type errors, and we don’t have to create Proc objects to send our messages.
So we can re-write our version of tap:
class Object def send_as_functions *args args.each {|arg| send_as_function arg} self end private :send_as_functions def tap *messages, &block send_as_functions *messages unless messages.empty? yield_or_eval(&block) if block_given? self end end [1].tap( :compact!, [:*, 2] ).first # => 1
Object#chain
Often times instead of returning self, we want to chain methods.
customer = nil
customer && customer.order && customer.order.id
Object#chain is an improved version of the one I saw here
class Object def chain *messages, &guard return self if messages.empty? or not( (block_given? ? (yield_or_eval(&guard)) : self)) (send_as_function (messages.shift)).chain(*messages, &guard) end end customer.chain(:order, :id)
note that because we did not supply a guard block, this is equivalent to
customer.then {order}.then {id}
except that if the original object was nil the chain version will only test for nil once.
Now for custom guarding: (Example of Old Way taken from the chain article mentioned above
Old Way:
value = 0 result = if value == 0 then value else tmp = value.abs if tmp == 0 then tmp else tmp * 20 end end result # => 0
New Way:
value.chain(:abs, [:*, 20]) {|num| num == 0 } # => 0
Update! Object#and, Object#or, modification to Object#then, Object#else see the README file
But it pollutes the namespace!
I think these methods should be considered fundamental to Ruby. You want your namespace to be polluted with fundamental methods. That being said, the helper methods are named in a long-winded style to avoid conficts. Object#tap will be implemented in Ruby 1.9, so it is here to stay. Object#chain has the most potential for problems. I cleverly named Object#then and Object#else after keywords since I figured only an idiot would name methods after keywords- so those should be relatively safe.
Only want to add some of the methods to certain namespaces? See the end of the README file.
git it
download the gem
gem install methodchain
Browse the source
git the source
git clone git://github.com/gregwebs/methodchain.git
documentation
- pretty README
- rdoc included in the gem