Accessing Data With 'Fetch'

Extracting data out of a Hash is pretty straight forward:

# params = { title: "Title Here" }
title = params[:title]

# ...

sanitized_title = title.strip

This works fine, for a while. Pretty soon we begin seeing error messages like these:

NoMethodError: undefined methodstrip' for nil:NilClass`

After some quality debugging time, we end up tracing the bug back to the point where we extracted the data from the parameters that were passed in. Unfortunately, one common fix for these types of bugs is to bandaid the method call that resulted in the error:

sanitized_title = title.strip if title.present?

More weeks pass, and more ‘nil’ errors continue to happen as the result of our code above. Sure, we can continue to add conditionals to all of the places the error is happening, but that would be ignoring the problem. The problem isn’t that a method is being called on a ‘nil’, that’s the symptom. The problem is that our application is making an implicit assumption that a value will be there. Our application is depending on the value being there. In order to uphold the contract that the rest of our application is depending on, and to prevent scattering ‘if’ checks everywhere, we need to make this assumption explicit in the code.

One potential solution is to set a sensible default value:

title = params[:title] || "Untitled"

This works in the case outlined above, but the problem with using || is that it will kick in if the key is missing OR if the value of params[:title] is falsey (ie. nil or false). For example:

# params = { published: false }
params[:published] || true
# => true

# params = { published: nil }
params[:published] || true
# => true

# params = { }
params[:published] || true
# => true

Sometimes this is the behavior we want, but it isn’t always the behavior we want. Instead, lets take a look at Hash#fetch:

# params = { post: {
title = params.fetch(:title)
# => KeyError: key not found: :title
#      from (irb):11:in `fetch'
#      from (irb):11
#      from /Users/kevin/.rbenv/versions/2.0.0-p0/bin/irb:12:in `<main>'

Instead of returning a nil value when a key is missing, #fetch returns an error message that includes the missing key and the line on which the error occured. No more sifting through your entire stack trying to figure out where a nil originated from. No our application will fail at the time the value is being extracted from the Hash. This means that we can have confidence that the value will be there. We have made an impicit dependency explicit.

You may not want your application to blow up everytime a hash is missing a key, so #fetch provides a nice, concise syntax for settting default values if a KeyError is raised:

# params = { post: { date: "2013-1-1" } }

title = params.fetch(:title, "Untitled")
# => "Untitled"

title = params.fetch(:title) { "Untitled" }
# => "Untitled"

The default values provided will only be set when there is a KeyError, so you don’t have to worry about overwriting values that were explicitly set to false or nil. Also, the block form is lazily evaluated so it is convenient for invoking methods that do intensive work. It’s also convenient for raising custom errors. For example:

# When you want a custom error, this is really nice
title = params.fetch(:title) { raise "Title can't be blank" }

# If you have a method that you want invoke to initialize default values...

# Does the crazy calculation, even if the key exists (Be careful..)
title = params.fetch(:title, do_crazy_calculation)

# Only does the crazy calculation if the KeyError is thrown
title = params.fetch(:title) { do_crazy_calculation }

Use #fetch, it will make your code more maintainable and you will spend less time tracing down the origins of obscure errors and issues resulting from nil values accidently being passed around your system.