» Undefined method for nil:NilClass
First off, nil
is a creature in its own rite, and is a common source of confusion when debugging Ruby programs. A NoMethodError
resulting from attempting to call a method on nil
is the most common error in Ruby.
The problem often arises when unexpected input is passed into a class or method. For example, say we want to capture a creature:
class Cage
attr_accessor :creature
def initialize(creature = nil)
self.creature = creature # We forget to set a default!
end
def rawr
creature.rawr
end
end
cage = Cage.new
cage.rawr # => NoMethodError: undefined method `rawr' for nil:NilClass
Not only did we fail to capture our creature, we just spawned the scariest creature of all: an undefined method for nil
NoMethodError
!
» Now, where'd I put that method?
When you call a method on an object and Ruby can't find it, it raises NoMethodError
. For example:
class Creature
end
creature = Creature.new
creature.rawr # => NoMethodError: undefined method `rawr' for #<Creature:0x0055e6fb99e390>
The path Ruby takes to arrive at a NoMethodError
exception is a winding one. Before throwing it's hands in the air and giving up, Ruby searches extensively for the method:
» 1. First, it looks on the singleton class
A singleton method is like a normal Ruby method, but it's defined on an instance instead of on a class:
class Creature
def rawr
'rawr!'
end
end
creature = Creature.new
def creature.rawr
'raaaaawr!'
end
creature.rawr # => 'raaaaawr!'
» 2. Next, it looks on modules which extend the singleton
Similar to adding a method to an instance of a class, Ruby can also extend and instance of an object with a module's methods. This is a way to define a group of singleton methods at once:
module LoudCreature
def rawr
'raaaaawr!'
end
end
creature = Creature.new
creature.extend(LoudCreature)
creature.rawr # => 'raaaaawr!'
If the instance is extended multiple times, Ruby works back from the most recently extended module to find the method you asked for:
module QuietCreature
def rawr
'purrrrr :3'
end
end
creature = Creature.new
creature.extend(LoudCreature)
creature.extend(QuietCreature)
creature.rawr # => 'purrrrr :3'
» 3. Now Ruby looks on the class
You may have been expecting this to be the first place Ruby would look, but as we saw, the singleton comes first. If the method doesn't exist there, Ruby looks for the method on the class:
creature = Creature.new
creature.rawr # => 'rawr!'
» 4. Modules that were included when defining the class
Next up, Ruby looks for the method in any modules that are included in the class (commonly called "Mixins"), in reverse order of inclusion:
class Creature
include LoudCreature
include QuietCreature
end
creature = Creature.new
creature.rawr # => 'purrrrr :3'
» Prepend works differently in Ruby 2.0
Ruby 2.0 introduced prepend
, which works a bit differently than include
. Instead of looking in the class first, Ruby searches the module before the class:
class Creature
prepend LoudCreature
def rawr
'rawr!'
end
end
creature = Creature.new
creature.rawr # => 'raaaaawr!'
» 5. Ruby Meets the Parents
OK, so the instance and class definitely don't have the method. The next place Ruby looks is the superclass of the object's class, repeating steps 3-5 (there are no new instances involved from this point on):
class Beast
def rawr
'hurrrrrrrr!'
end
end
class Creature < Beast
# Method defined on superclass
end
creature = Creature.new
creature.rawr # => 'hurrrrrrrr!'
» 6. A Method to Find Missing Methods
Ruby searches the ancestors of the class all the way up to BasicObject
. If the method is still missing, Ruby goes back to the original class and calls a special method: method_missing
:
class Creature
def method_missing(method, *args, &block)
"I haven't learned to #{method} yet :("
end
end
creature = Creature.new
creature.rawr # => "I haven't learned to rawr yet :("
creature.rawor # => "I haven't learned to rawor yet :("
If the class doesn't define method_missing
, Ruby repeats the search we just explored, but this time for method_missing
:
module Beast
def method_missing(method, *args, &block)
"I haven't learned to #{method} yet :("
end
end
class Creature
include Beast
end
creature = Creature.new
creature.rawr # => "I haven't learned to rawr yet :("