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.
I didn't know about #fetch methods! I really like them, thanks!
Glad to hear it!
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!
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!
This is great! I'm totally using this everywhere from now on. Thanks for pointing it out!
I didn't know about #fetch methods .
Thanks for pointing it out !…………………………….. : )
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! 🙂
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.
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 🙂
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 🙂
Nice feature. Any similar one for retrieving instance variables?
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
Yes, that is how it has always worked.
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.