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

16 Responses to “Handling nil in method calls”

  1. What if you extend the NilClass with method_missing so if you call person.name it will return nil if person is nil – or is this a bad idea?

  2. This would also work:

    person = person.name if person

  3. …I meant:

    name = person.name if person

  4. @Daniel: That would conflict with the “whiny nil” behavior already built into Rails.

    @Paul: ahem.

  5. Daniel, that would also affect nil everywhere, whereas nil_or only affects the place we use it. If we change nil, it may be hard to track down places where we get unexpected nils.

  6. Hey, thanks for this post, I’m fairly new to rail and ruby. I’ve been wondering how others handle nil issues.

  7. This is nice and DRY. In the example, you only have to mention ‘person’ once. This is even more important when ‘person’ is a more complicated expression.

    ///ark

  8. another way without ?:
    person && person.name

  9. I think this would cause a memory leak because it creates a new class each time #nil_or gets called. That class wouldn’t be garbage collected. Instead, you should create a new object and define method_missing on its metaclass:

    1
    2
    3
    4
    5
    6
    7
    8
    
    def nil_or
      return self unless self.nil?
      o = Object.new
      class << o
        def method_missing(sym, *args); nil; end
      end
      o
    end
  10. Alek, your example only works in views. If you remove the conditional, it works everywhere, but then you’ve changed nil in your entire app. I like nil_or because it is more intentional. We are only changing the behavior in the place where we are using it.

  11. Pat Maddox, can you give more detail? Why wouldn’t the new class be garbage collected when the nil_or goes out of scope? Why is defining it on the metaclass different?

  12. Hi there…. Why can’t one simply do this?

    class NilClass
    def method_missing *dontcare
    return nil
    end
    end

  13. Hi,

    I am quite new to ruby and I was looking for a solution that lets me call deep hierarchies on data-structures without having to check for nil.

    The nil_or method does not seem to fill my needs becaus one couldnt call


    x.nil_or.some_data.nil_or.other_data.nil_or....

    so I tried my luck and came up with this

    Which then could be called like

    NilOr.new(my_object_where_i_dont_care_its_nil).x.y.z.to_s

  14. Damn! Pastie didnt workout:

    http://pastie.org/506875

  15. This is why I love http://www.pgrs.net. Incredible posts.

Sorry, the comment form is closed at this time.

© 2012 Paul Gross's Blog Suffusion theme by Sayontan Sinha