4 minute read

I love that feeling when a new concept starts to come together in your mind and you can point to all the converging sources of insight.

Right now, I can’t tell if I’m fooling myself, hiding some logic, or making my code more readable with this particular concept, but when I put together these three pieces of information, I think I start to see something emerge.

I’ve been inspired to do some more digging into these kinds of questions lately thanks to the awesome new Ruby Book Club Podcast. Co-hosts Nadia Odunayo and Saron Yitbarek are leading us chapter-by-chapter through different Ruby books and sharing their thoughts on the podcast as they go.

This week, specifically, I came across:

I still don’t know exactly what to call this collection of methods, but they all feel very related to me. They all remind me of #try, which makes me nervous.

Using #try always feels like a little bit of a crutch, where you don’t quite know what inputs you are expecting or haven’t coded thoroughly to all your cases, but in Rails, #try swallows some of the errors you would have run into and returns nil instead.

With these three methods, we’re doing something similar. In each case, we get a simple way to check for a value and handle missing values gracefully with defaults, moving forward if the value is present.

So, for #presence, instead of:


params[:name].blank? ? params[:name] : nil

We can do:


params[:name].presence

For whitelisting, instead of:


if params[:name].present? && %w(foo bar).include?(params[:name])
  params[:name]
else
  nil
end

We can use #presence_in like this:


params[:name].presence_in %w(foo bar)

Finally, with #fetch, the only Ruby standard library method in this group (the rest come from Rails), you have a few different options. Lifted straight from the docs:


h = { "a" => 100, "b" => 200 }
h.fetch("a")                            #=> 100
h.fetch("z", "go fish")                 #=> "go fish"
h.fetch("z") { |el| "go fish, #{el}"}   #=> "go fish, z"

With #fetch, you use a key to puull a value from the hash, but you now you can define a default as your second argument, which is used to rescue missing key errors. Even better, you can execute whole block in the case that your key is not there.

Bonus Reading

I went digging a little more because I was still feeling uneasy about #try, but I couldn’t quite articulate why. Guess who I found to say it for me? Here’s Avdi from 2011 on Structural Coupling enabled by #try:

The seed of this all-too-common predicament is structural coupling. What’s structural coupling? To define it, let’s start with a review of the DRY principle:

"Every piece of knowledge must have a single, unambiguous, authoritative representation within the system."

It’s easy to think about DRYness just in terms of data: e.g., there should be only one place in the system for API keys; they shouldn’t just be copy-and-pasted willy-nilly throughout the codebase. But DRY applies equally to structural knowledge: knowledge about the composition of and relationships between your objects.

Let’s take a look at the code we started out with:

def user_info(user)
  "Name: #{user.name}. Dept: #{user.department.try(:name)}"
end

This seemingly innocuous code makes the following assumptions:

  • user will have a name property.
  • user may or may not have a single department.
  • user's department, in turn, has a name property

By going two levels deep into user's associations, we’ve made a structural coupling between this code and the models it works with. We’ve duplicated knowledge about a User’s associations—canonically located in the User and Department classes—in the #user_info method.

And the #try method was an enabler. By papering over the ugly user.department && user.department.name construct we’d otherwise have had to use, #try made the coupling an easier syntactical pill to swallow.

I highly recommend you read the whole article, as he goes into how this coupling violates the Law of Demeter and links back to an earlier discussion of #try, because, clearly, while these types of methods are cool and convenient, they make him a little uneasy too.