Jake Worth

Jake Worth

Hash Fetch Instead of If/Else

Published: April 16, 2022 2 min read

  • ruby

Conditional logic has its place, but often there’s a better alternative. Today, we’ll look at a Ruby solution: a hash with .fetch.

My thanks to Brian Dunn, who showed me the value of this technique.

Imagine you have a method that translates some data, a symbol for a role, into a presentation. A conditional is a popular solution here:

def display_name(role)
  if role == :customer
    'angler'
  elsif role == :employee
    'guide'
  else
    name.to_s
  end
end

I think this method is much improved as a hash:

ROLE_MAP = { customer: 'angler', employee: 'guide' }

def display_name(role)
  ROLE_MAP.fetch(role)
end

So, what have I got against conditional logic?

  • It’s not very readable (subjective, but still). Conditionals don’t ‘line up’ well, they use the awkward keyword elsif, they’re wordy (eight lines of code), they encourage sneaky things like early returns. It’s not code I can scan quickly, even an example as simple as this.
  • It doesn’t scale. Need a new role? Add two lines of code and another logical fork. Again and again.
  • It doesn’t fail. If you don’t have a role defined, you must have a default or this method returns nil. Passing that nil back the caller is going to cause problems.

Just use a switch, you say! I like switches. But beyond perhaps being a little more readable, they don’t solve the problems I mentioned above.

Consider a hash:

ROLE_MAP = { customer: 'angler', employee: 'guide' }

def display_name(role)
  ROLE_MAP.fetch(role)
end

We start with ROLE_MAP, a constant. This tells the reader: this is an important thing, for mapping roles!

Into the method, we call .fetch on our map. Fetch raises a KeyError when the first argument isn’t a key in the hash. If we don’t have a mapping, we fail fast, rather than passing the problem along.

Additionally, we use can use second argument to .fetch, a default value. This allows our method to always show something, ideal for presentation.

ROLE_MAP.fetch(role, role.to_s)

So, why use the hash?

  • I think it is more readable. If you know .fetch and you haven’t hidden ROLE_MAP away, this is simple code.
  • It scales. Need a new role? Add a new key-value pair to the hash.
  • I can fail fast. .fetch raises when it doesn’t have a mapping, or, you can use a default. Both are useful.

If you’re changing existing code, make sure that the legacy behavior is tested so you’re only changing behavior in a way you understand.

Use a hash and enjoy your more readable, more scalable, fail-fast code.

What are your thoughts on this? Let me know!


Join 100+ engineers who subscribe for advice, commentary, and technical deep-dives into the world of software.