Back to RoR Development posts

Hash#fetch in Ruby Development

Aug 2013

In Ruby there are a number of ways you can access the value of a key within a hash. The most commonly used is the square brackets syntax hash[:key]. However, there is an alternative, which is to use Hash#fetch e.g. hash.fetch(:key).

In this post, we’re going to examine how the fetch method differs from typical access methods, the cases where it will be better to use this syntax, and how you use it.

Hash#fetch provides better handling of default values for missing keys

Let’s imagine we are storing configuration options in a hash. Ruby has a way of providing the default value for them when none is provided, by using a ternary OR statement:

config[:key] || default

But if we care that the configuration option value is false or nil, using the above will override it with the default value. This is where the fetch method can be useful, as we can provide the fetch method with a default value as a second argument and it will not override the originally set value:

config.fetch(:key, default)

Note: If we do not provide a default argument or a block, and the key is not found in the hash, then a KeyError will be raised.

Using Hash#fetch makes it easier to work with nested hashes

Another example where the fetch method is useful is when trying to cope with nested hashes. Specifically, it helps when providing default values for accessing nested keys.

Lets take the example of a configuration file again:

{
  environment: {
    adapter: adapter,
  }
}

Let’s say you wanted to set the default adapter in development to sqlite3 and wanted to handle the case when there was no configuration set at all for the development environment. Using the square brackets syntax:

adapter = config[:development][:adapter]

This would raise NoMethodError: undefined method `[]‘ for nil:NilClass because config[:development]would return nil.

You could get around this by writing:

adapter = (config[:development] || {})[:adapter] || “sqlite3″

but a nicer syntax would be:

adapter = config.fetch(:development, {})
.fetch(:adapter, “sqlite3″)

Performance tip for Hash#fetch

There is a performance issue with the fetch method—the default value always gets evaluated, even if the key is present in the hash. This might not be an issue if the value is simple (such as a string) or quick to calculate, but if it’s an API call or database query then you can quickly run into performance issues.

Let’s imagine we want to retrieve a user’s coordinates:

user_location = {
  coordinates: {
    lat: latitude,
    lng: longitude
  },
  ip: user_request_ip
}

If the coordinates are not provided in the hash we will look up the coordinates from the IP address using an external service CoordinatesFetcher.

If we were to use the default value as in the previous examples:

user_location.fetch(:coordinates, CoordinatesFetcher.from_ip(user_location[:ip]))

It will call the CoordinatesFetcher every time, regardless of whether the coordinates are present in the hash, but to avoid this you just need to set the default value as a block:

user_location.fetch(:coordinates) { |ul| CoordinatesFetcher.from_ip(ul[:ip]) }

That’s it for this time. You can read more about fetch method in the ruby documentation at API dock.

Oren Dobzinski also has a nice write up of using the fetch method for default values in service objects at Re-factor.

If you want to find more interesting ideas and best practices about RoR development have a look at the Active Support Core Extensions guide.


Stay up to date with our blog posts

Keep up-to-date with our latest blog posts, news, case studies, and thought leadership.

  • This field is for validation purposes and should be left unchanged.

We’ll never share your information with anyone else. Read our Privacy Policy.