Handling nil in method calls

On my current project, we noticed common pattern when dealing with nil.
We would often check an object to see if it was nil before calling a
method on that object:

name = person ? person.name : nil

To reduce duplication, Patrick Farley
and Ali Aghareza created a nil_or method
which handles this. The above code becomes:

name = person.nil_or.name

The nil_or causes the expression to return nil if the target is nil. If
not, the name method is called.

The code for nil_or looks like:

module ObjectExtension
  def nil_or
    return self unless self.nil?
    Class.new do
      def method_missing(sym, *args); nil; end
    end.new
  end
end

class Object
  include ObjectExtension
end

The nil_or method returns self if self is not nil. If self is nil, it
creates a new Object which eats all method calls and returns nil.

We use a fair amount of delegation on this project using forwardable, so
Michael Schubert and Toby
Tripp
created
a delegator which has the same effect. For example, you can replace this
delegation:

class Person
  extend Forwardable
  def_delegator :@job, :title, :job_title
end

with this one:

class Person
  extend Forwardable
  def_delegator_or_nil :@job, :title, :job_title
end

This delegation is equivalent to this code:

class Person
  def job_title
    @job ? @job.title : nil
  end
end

The code for def_delegator_or_nil looks like:

module ForwardableExtension
  def def_delegator_or_nil(accessor, method, new_method = method)
    accessor = accessor.id2name if accessor.kind_of?(Integer)
    method = method.id2name if method.kind_of?(Integer)
    new_method = new_method.id2name if new_method.kind_of?(Integer)

    module_eval(<<-EOS, "(__FORWARDABLE_EXTENSION__)", 1)
      def #{new_method}(*args, &block)
        begin
          if #{accessor}.nil?
            nil
          else
            #{accessor}.__send__(:#{method}, *args,&block)
          end
        rescue Exception
          $@.delete_if{|s| /^\\(__FORWARDABLE_EXTENSION__\\):/ =~ s} unless Forwardable::debug
          Kernel::raise
        end
      end
    EOS
  end
end

module Forwardable
  include ForwardableExtension
end
Paul Gross

Paul Gross

I'm a lead software developer in Seattle working for Braintree Payments.

Read More