Ruby - Avoid 'undefined method' Exceptions
- Intro
- Understanding the Exception
- Problematic Example
- A Fix for Our Example
- A Better Fix: Safe Navigation Operator
- Thoughts for the reader:
Intro
If you’ve been programming in Ruby for any amount of time, you’ve probably seen an error like this:
NoMethodError: undefined method 'methodname' for nil:NilClass
Understanding the Exception
The meaning is actually pretty straightforward if you think about it. We’re trying to call a method called “methodname” that is not defined for the object we’re calling it on. In this case, that object is nil
:
a = nil
a.methodname()
#=> NoMethodError: undefined method `methodname' for nil:NilClass
However, nil
isn’t special here, other classes do throw the same error:
a = "string"
a.methodname()
#=> NoMethodError: undefined method `methodname' for "string":String
We’re only really focusing on the case where an undefined method is called on nil
though. I just thought it was important to point out.
Anyways, probably the most common method you’ll see this happen with is: []
NoMethodError: undefined method `[]' for nil:NilClass
Problematic Example
You might see something like this when you’re pulling data out of a nested data structure you receive over HTTP or some other protocol:
response1 = [1, 2, [3, 4]]
response2 = [1, 2, [1, 2]]
response3 = [ 1, [3, 4]]
def process_response(response)
puts response[2][1] + response[0]
end
process_response(response1)
#=> 5
process_response(response2)
#=> 3
process_response(response3)
#=> NoMethodError: undefined method `[]' for nil:NilClass
In the above code, we assumed that every response would have a nested array as the third element of the outer array. Of course, this was not the case for response3
. There is no third element in response3
, so nil
is returned. We then try to run the []
method on that nil
and that’s when we generate the NoMethodError
:
response3[2]
#=> nil
nil[1]
#=> NoMethodError: undefined method `[]' for nil:NilClass
A Fix for Our Example
Let’s fix it:
def process_response(response)
if response[2] && response[2][1] && response[0]
puts response[2][1] + response[0]
end
end
process_response(response3)
#=> nil
Well, that’s pretty tedious, there must be a better way. Enter the safe navigation operator!: &
A Better Fix: Safe Navigation Operator
def process_response(response)
puts response[2]&[1] + response[0]
end
That’s it, did you even notice? All we have to do is put a &
between our possibly-nil object, and the method being called on it. If the object is nil
, the method won’t be called, and nil
will be returned instead of an exception.
So much easier than checking every single item manually!
Thoughts for the reader:
- Is there a better way to handle this?
- Where else might you run into errors like this?