- Item 1: Understand What Ruby Considers to Be True
- Item 2: Treat All Objects as If They Could Be nil
- Item 3: Avoid Ruby's Cryptic Perlisms
- Item 4: Be Aware That Constants Are Mutable
- Item 5: Pay Attention to Run-Time Warnings
Item 2: Treat All Objects as If They Could Be nil
Every object in a running Ruby program comes from a class that, in one way or another, inherits from the BasicObject class. Imagining how all these objects relate to one another should conjure up the familiar tree diagram with BasicObject at the root. What this means in practice is that an object of one class can be substituted for an object of another (thanks to polymorphism). That’s why we can pass an object that behaves like an array—but is not actually an array—to a method that expects an Array object. Ruby programmers like to call this “duck typing.” Instead of requiring that an object be an instance of a specific class, duck typing shifts the focus to what the object can do; in other words, interface over type. In Ruby terms, duck typing means you should prefer using the respond_to? method over the is_a? method.
But in reality, it’s rare to see a method inspect its arguments using respond_to? to make sure it supports the correct interface. Instead, we tend to just invoke methods on an object and if the object doesn’t respond to a particular method, we leave it up to Ruby to raise a NoMethodError exception at run time. On the surface, it seems like this could be a real problem for Ruby programmers. Well, just between you and me, it is. It’s one of the core reasons testing is so very important. There’s nothing stopping you from accidentally passing a Time object to a method expecting a Date object. These are the kinds of mistakes we have to tease out with good tests. And thanks to testing, these types of problems can be avoided. But one of these polymorphic substitutions plagues even well-tested applications:
undefined method 'fubar' for nil:NilClass (NoMethodError)
This is what happens when you call a method on an object and it turns out to be that pesky nil object...the one and only object from the NilClass class. Errors like this tend to slip through testing only to show up in production when a user does something out of the ordinary. Another situation where this can occur is when a method returns nil and then that return value gets passed directly into another method as an argument. There’s a surprisingly large number of ways nil can unexpectedly get introduced into your running program. The best defense is to assume that any object might actually be the nil object. This includes arguments passed to methods and return values from them.
One of the easiest ways to avoid invoking methods on the nil object is by using the nil? method. It returns true if the receiver is nil and false otherwise. Of course, nil objects are always false in a Boolean context, so the if and unless expressions work as expected. All of the following lines are equivalent to one another:
person.saveif
person person.saveif
!person.nil? person.saveunless
person.nil?
It’s often easier to explicitly convert a variable into the expected type rather than worry about nil all the time. This is especially true when a method should produce a result even if some of its inputs are nil. The Object class defines several conversion methods that can come in handy in this case. For example, the to_s method converts the receiver into a string:
irb> 13.to_s ---> "13" irb> nil.to_s ---> ""
As you can see, NilClass#to_s returns an empty string. What makes to_s really nice is that String#to_s simply returns self without performing any conversion or copying. If a variable is already a string then using to_s will have minimal overhead. But if nil somehow winds up where a string is expected, to_s can save the day. As an example, suppose a method expects one of its arguments to be a string. Using to_s, you can hedge against that argument being nil:
def
fix_title (title) title.to_s.capitalizeend
The fun doesn’t stop there. As you’d expect, there’s a matching conversion method for almost all of the built-in classes. Here are some of the more useful ones as they apply to nil:
irb> nil.to_a ---> [] irb> nil.to_i ---> 0 irb> nil.to_f ---> 0.0
When multiple values are being considered at the same time, you can make use of a neat trick from the Array class. The Array#compact method returns a copy of the receiver with all nil elements removed. It’s common to use it for constructing a string out of a set of variables that might be nil. For example, if a person’s name is made up of first, middle, and last components—any of which might be nil—you can construct a complete full name with the following code:
name = [first, middle, last].compact.join(" "
)
The nil object has a tendency to sneak into your running programs when you least expect it. Whether it’s from user input, an unconstrained database, or methods that return nil to signal failure, always assume that every variable could be nil.
Things to Remember
- Due to the way Ruby’s type system works, any object can be nil.
- The nil? method returns true if its receiver is nil and false otherwise.
- When appropriate, use conversion methods such as to_s and to_i to coerce nil objects into the expected type.
- The Array#compact method returns a copy of the receiver with all nil elements removed.