Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 744
elitalon Avatar asked Oct 07 '13 12:10

elitalon


People also ask

What does nil mean Ruby?

In Ruby, nil is a special value that denotes the absence of any value. Nil is an object of NilClass. nil is Ruby's way of referring to nothing or void.

What is a nil object?

Nil… What is it, really? Well, nil is a special Ruby object used to represent an “empty” or “default” value. It's also a “falsy” value, meaning that it behaves like false when used in a conditional statement.

Is nil Ruby?

The NilClass class NilClass is a built-in class provided by Ruby. This class is not instantiable. When a message is sent to nil , a hard-coded C-level “class” called rb_cNilClass — which corresponds to the NilClass in Ruby — is used as receiver of the message.

How do you check if an object is nil in Ruby?

In Ruby, you can check if an object is nil, just by calling the nil? on the object... even if the object is nil. That's quite logical if you think about it :) Side note : in Ruby, by convention, every method that ends with a question mark is designed to return a boolean (true or false).


4 Answers

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).

like image 77
Daniel Vandersluis Avatar answered Sep 30 '22 07:09

Daniel Vandersluis


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.

like image 27
mdesantis Avatar answered Sep 27 '22 07:09

mdesantis


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.

like image 38
Marek Lipka Avatar answered Oct 01 '22 07:10

Marek Lipka


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
like image 30
Stefan Avatar answered Sep 29 '22 07:09

Stefan