Go Fetch

I’m a fan of the #fetch method in Ruby. I’ve noticed that other Rubyists don’t use it as much as I do, so I thought I’d write a little bit about why I like it so much.

First of all, in case you’ve forgotten, #fetch is a method implemented on both Array and Hash, as well as some other Hash-like classes (like the built-in ENV global). It’s a near-synonym for the subscript operator (#[]). #fetch differs from the square brackets in how it handles missing elements:

  h = {:foo => 1, :bar=> 2}
  h[:buz] # => nil
  h.fetch(:buz) # => IndexError: key not found
  h.fetch(:buz){|k| k.to_s * 3} # => "buzbuzbuz"

The simplest use of #fetch is as a “bouncer” to ensure that the given key exists in a hash (or array). This can eliminate confusing NoMethodErrors later in the code:

  color = options[:color]
  rgb  = RGB_VALUES[color]
  red = rgb >> 32 # => undefined method `>>' for nil:NilClass (NoMethodError)

In the preceding code you have to trace back a few steps to determine where that nil is coming from. You could surround your code with nil-checks and AndAnd-style conditional calls – or you could just use #fetch:

  color = options.fetch(:color) # => IndexError: key not found
  # ...

Here we’ve caught the missing value at the point where it was first referenced.

You can use the optional block argument to #fetch to either return an alternate value, or to take some arbitrary action when a value is missing. This latter use is handy for raising more informative errors:

  color = options.fetch(:color) { raise "You must supply a :color option!" }
  # ...

Another common use case is default values. These are often handled with the || operator:

  verbose = options['verbose'] || false

But this has the problem that the case where the element is missing, and the case where the element is set to nil or false, are handled interchangeably. This is often what you want; but if you make it your default it will eventually bite you in a case where false is a legitimate value, distinct from nil. I find that #fetch is both more precise and better expresses your intention to provide a default:

  verbose = options.fetch('verbose'){ false }

In my code I try to remember to use #fetch unless I am reasonably sure that the Array or Hash dereference can’t fail, or I know that a nil value is acceptable by the code that will use the resulting value.

19 comments

  1. Very good tip.. I don't remember ever seeing this method before.

    I was able to use this within an hour of reading it 🙂

  2. Thanks for the informative post! I had forgotten all about #fetch, but you make a very good case for using it. BTW, from the docs, another way of writing your last example would be without the block like so:

    verbose = options.fetch('verbose', false)

    I think I like the block syntax better though. It seems to communicate better.

  3. With Hash you can initialise it with a block of code that does the same thing without using fetch.

    h = Hash.new{ raise IndexError }
    h[ :a ] = 1
    h[ :a ] # => 1
    h[ :b ] # IndexError raised.

  4. Since this post is from 2009, it’s possible that things have changed. In Ruby 2.2, using fetch with or without a default value will always return the key’s value if the key exists. For example:

    options = { foo: ‘bar’, verbose: nil }

    options.fetch(:verbose, false)
    => nil

    This means you can’t use #fetch to provide a default value when the element is present but is set to nil. You can, however, use the || operator:

    options[:verbose] || false
    => false

  5. Hi Avdi, nice informative article.

    Just FYI –
    h = {:foo => 1, :bar=> 2}
    {
    :foo => 1,
    :bar => 2
    }
    h.fetch(:buz) now throws # KeyError: key not found: :buz
    not IndexError, probably this is a older post.

Leave a Reply to avdi Cancel reply

Your email address will not be published. Required fields are marked *