ruby-on-railsrubyactivesupport

Why does Object::try work if it's sent to a nil object?


If you try to call a method on a nil object in Ruby, a NoMethodError exception arises with the message:

"undefined method ‘...’ for nil:NilClass"

However, there is a try method in Rails which just return nil if it's sent to a nil object:

require 'rubygems'
require 'active_support/all'

nil.try(:nonexisting_method) # no NoMethodError exception anymore

So how does try work internally in order to prevent that exception?


Solution

  • ActiveSupport 4.0.0 defines two try methods: one is for Object instances:

    class Object
      def try(*a, &b)
        if a.empty? && block_given?
          yield self
        else
          public_send(*a, &b) if respond_to?(a.first)
        end
      end
    end
    

    the other is for NilClass instances (nil objects):

    class NilClass
      def try(*args)
        nil
      end
    end
    

    Now, suppose we have an Object instance (excluding nil, which actually inherits from Object, like everything else in Ruby), defining a method which returns nil:

    class Test
      def returns_nil
        nil
      end
    end
    

    So, running Test.new.try(:returns_nil) or Test.new.not_existing_method, Object#try gets called, which checks if a public method exists (the respond_to?); if it does it calls the method (the public_send), otherwise it returns nil (there aren't other lines).

    If we call another try on of these returning nil methods:

    Test.new.try(:returns_nil).try(:any_other_method)
    Test.new.try(:not_existing_method).try(:any_other_method)
    

    we call NilClass#try, which is nil#try, which simply ignores everything and returns nil. So any other try is called on a nil instance and returns nil.