The "final" update for Clean Ruby will be released soon. More on that below, but first here are some thoughts on what to do when you've got a missing object.
The Ruby standard library Forwardable is really useful for raising up valuable information and hiding unimportant details.
A single line of code can configure the relationship between two objects and the data they share.
delegate [:street, :city, :state] => :address
I'm often asked: what happens if that address is nil?
In this case, you'll see an error about an undefined method on nil:
NoMethodError: undefined method `street' for nil:NilClass
You might want that, or you might not. It depends on how you want to structure your program.
When using the Forwardable library, the street method above will raise an error if address is missing.
But, it might make the most sense for your application that if there is no address, then there is obviously no street. So a method that handles this will make more sense:
def street if address address.street end end
With this requirement, Forwardable seems useless. I don't have a way to configure Forwardable to just skip the nil object and the forwarded message. ActiveSupport (and Rails) adds its own answer to this problem.
delegate :street, :to => :address, :allow_nil => true
By specifying allow_nil the call to street will silently continue returning nothing if the address is nil. This type of approach to managing nil objects is valuable when it's what you want (of course), and can be a life-saver when you're dealing with a large and/or unfamiliar codebase that's throwing up errors where you don't expect them.
A problem with this approach, however, is that the absence of a an object might be an indication that you're missing some vaulable setup steps. Perhaps a missing address is a problem and removing the error hides the problem. You can take your error as encouragement to use a null object, an object which will stand-in for your missing address.
class Person def initialize(address) @address = address end def address @address || DefaultAddress.new end end
You likely have a different way to initialize your objects, but with something like the above code we can use a DefaultAddress which can answer for your missing address.
class DefaultAddress def street "123 Home Base" end end
By following this design you can easily change your program from displaying nothing, as you do in the case of allow_nil, to displaying a default value. But what's great about this structure is that you can change the default behavior without any change to your Person class.
Before reaching for an option like allow_nil, consider an alternative object instead. How do you handle nil objects in your code?
Lastly, Clean Ruby is out and you can get it now! I'll be keeping the book up to date with relevant changes to Ruby or with any good techniques for Object-oriented programming so "final" true, barring any updates that come along.