Question

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?

Was it helpful?

Solution 2

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 will be called, which will check if a public method exists (the respond_to?); if it does it will call the method (the public_send), else it will return 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 will call NilClass#try, which is nil#try, which simply ignores everything and returns nil. So any other try will be called on a nil instance and return nil.

OTHER TIPS

Like every other object in Ruby, nil has a class (NilClass), which has methods. For instance, nil? is defined on NilClass to return true, which is why nil.nil? doesn't return a NoMethodError.

Every class in ruby can be opened (updated with new methods). Rails opens NilClass to add the try method and have it return nil (which is also why try can be chained even when one step in the chain returns nil, because that subsequent nil also responds to try).

From Rails (3.2) documentation:

  # Object#try:
  def try(*a, &b)
    if a.empty? && block_given?
      yield self
    else
      __send__(*a, &b)
    end
  end

  # NilClass#try (http://apidock.com/rails/NilClass/try):
  def try(*args)
    nil
  end

Method try is simply implemented separately for nil object so it always returns nil, even if the method you try to call is implemented for nil object.

It ignores the argument and just returns nil. Here's the implementation:

class NilClass
  def try(*args)
    nil
  end
end

Note that this implementation always returns nil, even if nil responds to the method:

nil.try(:to_i) # => nil, rather than 0
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top