Ruby fetch can have an unexpected side-effect which is that the fallback value gets evaluated even if the key is present. Lets explore that with a code example.

fetch use-cases

Using fetch is a common pattern in Ruby, especially when dealing with hashes. It allows you to retrieve a value for a given key, and if the key is not found, you can provide a default value. This is useful for avoiding nil values and for providing a fallback value.

hash = { a: 1, b: 2 }
hash.fetch(:a) # => 1
hash.fetch(:c, 3) # => 3

fetch fallback values and fallback blocks

When using fetch, the fallback value is evaluated even if the key is present. This can lead to unexpected behavior, especially if the fallback value is a complex expression or a method call.

hash = { a: 1, b: 2 }
hash.fetch(:a, "not found" ) # => 1
hash.fetch(:c, "not found" ) # => "Key not found"

Now with a defined function which throws an exception:


def complex_fallback
  raise "This is an exception"
end

hash = { a: 1, b: 2 }
hash.fetch(:a, complex_fallback) # => This is an exception!

This is definitely not what we wanted, as the value a is present in the hash, and we’d expect for it to be returned. To avoid this we have to pass a block to fetch:

hash = { a: 1, b: 2 }
hash.fetch(:a) { complex_fallback } # => 1
hash.fetch(:c) { complex_fallback } # => This is an exception

Conclusion

If you want to use fetch with a fallback value, be careful about the evaluation of the fallback value. If the fallback value is a complex expression or a method call, it is better to use a block to avoid unexpected evaluation of the function.