If at first you don’t succeed, .try again

As a relatively new developer here at Software for Good, I’m pretty much like a giant sponge. Almost every day, I learn something that I didn’t know I didn’t know. In order to document some of my learning and hopefully help some other junior developers, I’ve decided to write about it.

Presenting the .try method in Rails.

.Try is a method we can use to call on an object without having to worry about whether or not that object is nil, which would raise an exception and blow up your app. It’s important to remember, though, that sometimes you want a nil value to blow up your app, and sometimes you want to be reminded that you didn’t set default database values, etc.

Rails documentation says that .try

Invokes the public method whose name goes as first argument just like public_send does, except that if the receiver does not respond to it the call returns nil rather than raising an exception.

Say what?

The most obvious example of where to use this is in a view. You might have something like

<% unless @user.profile.nil? %>
  <%= @user.profile.favorite_food %>
<% end %>

Here, we are checking to make sure that the user has a profile so that our app doesn’t try to call .favorite_food on nil. We could clean this up a bit with the following:

<%= @user.profile.try(:favorite_food) %>

We might want to check whether or not a user is logged in, and also whether or not that user has the user role of ‘poster’ before we show a link on the page. We could do something like the following:

<% if current_user && current_user.role == 'poster' %>
<%= button_to "New Post", new_post_path %>
<% end %>

We could clean this up like so:

<% if current_user.try(:role) == 'poster' %>
<%= button_to "New Post", new_post_path %>
<% end %>

Of course, here at SfG we use HAML, so here’s a recent example from a view I was working on. If the inconsistent_result model is there, it calls .calculation, and if not, it returns nil so that our OR condition is output instead:

  %th Calculation Which May Not Be There
  %td= @model.inconsistent-results.try(:calculation) || "Could Not Calculate"

We can also chain tries, which is where they start to come in handy even more.

Suppose we have a robot that may or may not have an owner, and this owner may or may not have a favorites model attached to it, that may or may not have a list of favorite programming languages.

We could do something like

robot_owner_languages = robot.owner && robot.owner.favorites && robot.owner.favorites.languages ? robot.owner.favorites.languages : nil

Or we could just do something like:

robot_owner_languages = robot.owner.try(:favorites).try(:languages)

(We can also always rescue and return nil, but be careful! Once you start rescuing errors and returning nil, you make your code harder to debug!)

robot_owner_languages = robot.owner.favorites.languages rescue nil

.Try is only available in Rails-flavored Ruby. If you want it in your regular code, install the activesupport gem and then
require 'active_support/core_ext/object/try'