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.

This entry was posted in Uncategorized and tagged . Bookmark the permalink.
  • http://bicosyes.com blaxter

    I didn't know about #fetch methods! I really like them, thanks!

    • http://avdi.org avdi

      Glad to hear it!

  • pschless

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

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

    • http://avdi.org avdi

      Hey Patrick, great to hear it was of use to you!

  • http://benmabey.com Ben Mabey

    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.

    • http://avdi.org avdi

      Dude, I either forgot about or didn't know about that form. Either way, thanks for bringing that up!

  • http://subelsky.com Mike Subelsky

    This is great! I'm totally using this everywhere from now on. Thanks for pointing it out!

  • http://www.bostonmaattorneys.com Jhonez

    I didn't know about #fetch methods .
    Thanks for pointing it out !…………………………….. : )

  • http://steveeichert.com/ Steve Eichert

    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! :)

  • http://www.aimred.com/ Farrel

    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.

    • http://avdi.org avdi

      Indeed. This feature is useful in cases where you know you want to treat all missing values in the same way.

  • phoenixchu

    it maybe cause some misunderstanding if other developers don't know fetch method for default value usage

    • http://avdi.org avdi

      Then they should read this article… or the Ruby documentation :-)

  • phoenixchu

    it maybe cause some misunderstanding if other developers don't know fetch method for default value usage

  • http://wideteams.com Avdi Grimm

    Then they should read this article… or the Ruby documentation :-)

  • http://erthad.name/ Timur Batyrshin

    Nice feature. Any similar one for retrieving instance variables?