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?
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.
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.
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.
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).
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
).
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
.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With