Navigating nil (Method Chaining) in Ruby

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

© Greg Weber. All original code snippets are placed in the public domain.
Written on Sun Mar 16 08:17:59 2008.
Tagged as gem, ruby.
Send author an email.