Why and how to use Ruby’s Hash#fetch for default values
Tags: rubytapasfreebies, datastructures, featured, idioms
Published: 2020-11-08 18:24:42

---
title: Why and how to use Ruby’s Hash#fetch for default values
published: true
date: 2020-10-04 18:09:21 UTC
tags: RubyTapasFreebies,datastructures,featured,idioms
canonical_url: https://avdi.codes/why-and-how-to-use-rubys-hashfetch-for-default-values/
---

Here’s another classic from the early days of [RubyTapas](https://www.rubytapas.com). Originally published as [Episode #11](https://www.rubytapas.com/2012/10/19/episode-012-fetch-for-defaults/) in October 2012, it’s a complement to the episode on [using `fetch` as an assertion](https://www.rubytapas.com/2012/10/10/episode-008-fetch-as-an-assertion/#tapas_script). This episode digs into the difference between using the `||` operator for defaults vs. using `Hash#fetch`.

<script src="https://fast.wistia.com/assets/external/E-v1.js" async></script>

 ![](https://fast.wistia.com/embed/medias/qxvmb0loz7/swatch)

**Director’s commentary:** I can see some tiny advancements in quality here. At this point I’d realized that my original comment color was nearly unreadable on the dark background, and brightened it up a bit.

But my voiceover still sounds kind of bored. And was still content to have long sections of silence while I put new code on the screen. These days, I try to always accompany coding with simultaneous explanation.

Read on for the original script and code…

* * *

In a [previous episode](https://www.rubytapas.com/2012/10/10/episode-008-fetch-as-an-assertion/#tapas_script), we looked at how the `#fetch` method on `Hash` can be used to assert that a given hash key is present.

```
auth = {
  'uid' => 12345,
  'info' => {
  }
}

# ...

email_address = auth['info'].fetch('email')
# ~> -:11:in `fetch': key not found: "email" (KeyError)
# ~> from -:11:in `<main>'

```

But what if the `KeyError` that Hash raises doesn’t provide enough context for a useful error message?

Along with the key to fetch, the `#fetch` method can also receive an optional block. This block is evaluated if, and only if, the key is _not_ found.

Knowing this, we can pass a block to `#fetch` which raises a custom exception:

```
auth['uid'] # => 12345
auth['info'].fetch('email') do 
  raise "Invalid auth data (missing email)."\
        "See https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema"
end
email_address = auth['info'].fetch('email')
# ~> -:10:in `block in <main>': Invalid auth data (missing email).See https://github.com/intridea/omniauth/wiki/Auth-Hash-Schema (RuntimeError)
# ~> from -:8:in `fetch'
# ~> from -:8:in `<main>'

```

Now when this code encounters an unexpectedly missing key, the raised exception will explain both the problem, and where to find more information.

The block argument to `#fetch` isn’t just for raising errors, however. If it doesn’t raise an exception, `#fetch` will return the result value of the block to the caller, meaning that `#fetch` is also very useful for providing default values. So, for instance, we can provide a default email address when none is specified.

```
email_address = auth['info'].fetch('email'){ 'anonymous@example.org' }
email_address # => "anonymous@example.org"

```

Now, you may be wondering: what’s the difference between using `#fetch` for defaults, and using the `||` [operator](https://www.rubytapas.com/t/operators) for default values? While these may seem equivalent at first, they actually behave in subtly, but importantly different ways. Let’s explore the differences.

Here’s an example of using the `||` operator for a default. This code receives an options hash, and uses the `:logger` key to find a logger object. If the key isn’t specified, it creates a default logger to `$stdout`. If the key is `nil` or `false`, it disables logging by substituting a `NullLogger` object.

This works fine when we give it an empty `Hash`.

```
require 'logger'

class NullLogger
  def method_missing(*); end
end

options = {}
logger = options[:logger] || Logger.new($stdout) 
unless logger
  logger = NullLogger.new
end
logger
# => #<Logger:0x000000030545a8
# @default_formatter=
# #<Logger::Formatter:0x00000003054580 @datetime_format=nil>,
# @formatter=nil,
# @level=0,
# @logdev=
# #<Logger::LogDevice:0x00000003054530
# @dev=#<IO:<STDOUT>>,
# @filename=nil,
# @mutex=
# #<Logger::LogDevice::LogDeviceMutex:0x00000003054508
# @mon_count=0,
# @mon_mutex=#<Mutex:0x000000030544b8>,
# @mon_owner=nil>,
# @shift_age=nil,
# @shift_size=nil>,
# @progname=nil>

```

But when we pass `false` as the value of `:logger`, we get a surprise:

```
options = {logger: false}
logger = options[:logger] || Logger.new($stdout) 
unless logger
  logger = NullLogger.new
end
logger
# => #<Logger:0x000000040bb608
# @default_formatter=
# #<Logger::Formatter:0x000000040bb5e0 @datetime_format=nil>,
# @formatter=nil,
# @level=0,
# @logdev=
# #<Logger::LogDevice:0x000000040bb590
# @dev=#<IO:<STDOUT>>,
# @filename=nil,
# @mutex=
# #<Logger::LogDevice::LogDeviceMutex:0x000000040bb568
# @mon_count=0,
# @mon_mutex=#<Mutex:0x000000040bb518>,
# @mon_owner=nil>,
# @shift_age=nil,
# @shift_size=nil>,
# @progname=nil>

```

That was supposed to be a `NullLogger`, not the default logger!

So what happened here? The problem with using `||` with a `Hash` for default values is that it can’t differentiate between a _missing_ key, versus a key whose value is `nil` or `false`. Here’s some code to demonstrate:

```
{}[:foo] || :default # => :default
{foo: nil}[:foo] || :default # => :default
{foo: false}[:foo] || :default # => :default

```

In contrast, `#fetch` only resorts to the default when the given key is actually missing:

```
{}.fetch(:foo){:default} # => :default
{foo: nil}.fetch(:foo){:default} # => nil
{foo: false}.fetch(:foo){:default} # => false

```

When we switch to using `#fetch` in our logger-defaulting code, it works as intended.

```
options = {logger: false}
logger = options.fetch(:logger){Logger.new($stdout)}
unless logger
  logger = NullLogger.new
end
logger
# => #<NullLogger:0x00000003b73858>

```

When you want to provide default value for a missing hash key, consider carefully whether you want an explicitly supplied `nil` or `false` to be treated the same as a missing key. If not, use `#fetch` to provide the default value.

OK, that’s all for today. [Happy hacking!](https://www.rubytapas.com/2012/10/19/episode-012-fetch-for-defaults/#tapas_script)