Some of you may not been aware but there is more than one way to get a value out of a hash. By the end of this article I will have told you about three of them and the relative merits of each method.
The first is the one most Ruby developers know and love,
#[]
. This is the
method we see when we first learn about hashes. It is how programmers from other
languages might access elements in arrays or hashes and we use it as follows.
pets = { cat: "Jess" }
pets[:cat]
# => "Jess"
This is great. It does exactly what we want it to do. What if we have a situation where the value might not exist? What happens then? Lets have a look.
pets = { cat: "Jess" }
pets[:dinosaur]
# => nil
It’s all right, you think. I’ll deal with the nil
.
pets = { cat: "Jess" }
pets[:dinosaur] || "They all died :("
# => "They all died :("
What if, for some crazy reason, your key is not nil
, but false
? You could
then end up fallbacking to a value you never intended to.
dinosaurs = { alive?: false }
dinosaurs[:alive?] || true
# => true
What if the key does not exist in the hash at all? You would never know this, unless you put some guard clause or error handling in place.
There is an answer to both of these scenarios and its name is #fetch
.
#fetch
works in much the same way as #[]
, but it also allows you to set a
fallback value for when the key does not exist or it is nil
.
pets = { cat: "Jess" }
pets.fetch(:dinosaur, "They all died :(")
# => "They all died :("
You may been wondering how this is any better than our example from earlier. Let’s look at another example.
dinosaurs = { alive?: false }
dinosaurs.fetch(:alive?, true)
# => false
This seems to give a much better answer than our original solution. #fetch
only falls back to the default if the key does not exist or if the key’s value
is nil
. Here the :alive?
is false
. This is the returned value.
Now, what if we wanted to do something crazy like this.
pets.fetch(:dinosaur, Dinosaur.raise_from_the_dead!)
When we use #fetch
with two arguments, the second argument is always evaluated
even if it is never used. This could be an expensive operation. Think of all the
time and effort this would take. You would have to spend time finding some amber
with a mosquito in it. Then there is all the research and development behind the
science. In the end we all know life finds a way.
What we actually want is a way to only use the fallback if we need it. Luckily such an operation exists.
pets.fetch(:dinosaur) { Dinosaur.raise_from_the_dead! }
Now we will only be raising dinosaurs from the dead when we do not already have a pet dinosaur. This seems sensible. Who has space for two dinosaurs? This is particularly helpful when trying to introduce dependency injection.
You should now understand three ways to read a value from a hash, their relative merits, and when to use them. Good luck!