Go Fetch

2009 March 16
tags:
by avdi

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.

Bookmark and Share
Creative Commons License
This work, unless otherwise expressly stated, is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License.
  • phoenixchu
    it maybe cause some misunderstanding if other developers don't know fetch method for default value usage
  • Then they should read this article... or the Ruby documentation :-)
  • 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.
  • Indeed. This feature is useful in cases where you know you want to treat all missing values in the same way.
  • Very cool. Not sure why but I don't think I've ever come across fetch before. It will make some code a bit cleaner, thanks! :)
  • I didn't know about #fetch methods .
    Thanks for pointing it out !................................... : )
  • This is great! I'm totally using this everywhere from now on. Thanks for pointing it out!
  • 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.
  • Dude, I either forgot about or didn't know about that form. Either way, thanks for bringing that up!
  • Very good tip.. I don't remember ever seeing this method before.

    I was able to use this within an hour of reading it :)
  • Hey Patrick, great to hear it was of use to you!
  • I didn't know about #fetch methods! I really like them, thanks!
  • Glad to hear it!
blog comments powered by Disqus