While listening to The Ruby Show episode #176 I heard about the ThoughtBot post about the Maybe Pattern as discussed in their “If you gaze into nil, nil gazes also into you” post.
It took me a minute to figure out what was going on. (Maybe I’m just getting slower in my old age.) Plagiarizing from their post, they included the code required to set up the Maybe Pattern and I’ll try to add my own comments in red so I feel like I’m adding something to this conversation:
This is modifying the base Object of which every Ruby object is a child. Yes, that means even Nil gets an automatic maybe() method stuck in it. The idea is that if you think a method *might* return a value then you ought to return your value with a .maybe() on it. That accomplishes the "wrapping". You'll see that it takes itself and returns an instance of Some with itself stuffed into it. Thus at this point you really get a Some() object back, not your original return value. class Object def maybe Some.new(self) end end ... Except for Nil. This one definitely is empty/blank/nothing so we need to create an instance of Some that will throw exceptions if you try to unwrap it. class NilClass def maybe None.new end end And here is the Some object that wraps anything you give it by storing it in its own @object attribute. class Some def initialize(object) @object = object end This is how you "unwrap" an object. Pay close attention that the None version of this will throw exceptions. def get @object end This is one way you can check to see if there might be values. def present? true end And this is the other. def blank? false end end And back to some Magic Sauce. It creates a new error type, Unwrapped, and will return it when you attempt to call get(). This is because as a consumer of the Some or None values you ought to have checked present?() or blank?() first. That's right, consumers need to be sure that they're not just going to use a nil value. class None class Unwrapped < StandardError; end def get raise Unwrapped end def present? false end def blank? true end end
OK, great. So how do you use this in practice?
When you return a value that is absolutely true or false, then do so. If you think your return value might be null then wrap it by tacking on a .maybe() call.
The consumer of these return values should always check for present?() or blank?(). If something is not present or is blank then you should no longer need to access the return value because, well, you got your answer.
However, if you need to use the return value for something (e.g. it might be a single object, or might be an array, or a Hash, or ...) then you need to check if it's present?() and/or not blank?(). If that's true then you can call the object's .get() method to grab that value and continue with your logic. Like:
accounts = bank(me).accounts # accounts might be nil ... check present?() if accounts.present? account_list = accounts.get # Now account_list definitely has something which isn't nil! end
The main thought I had in all of this is that this will create tons of wrapper objects that could just add bloat to your application. Plus there's the extra evaluation involved. In my own code I almost always just use the Rails .blank?() method to check if things are there. That seems to handle nil, empty strings, and empty arrays just fine!