As I was drafting my last blog post I quickly realised there were sufficient points about Hashes in Ruby to justify a dedicated (albeit brief) post on the topic!

Breakfast

Default values with Hash

Hashes in Ruby are kind of like objects in JavaScript (the purists in the room just rolled their eyes). Sometimes a hash might include some optional values, e.g.

coffee_order_1 = { name: "Andrew", type: "flat_white" }

coffee_order_2 = { name: "Sam", type: "cappuccino", milk: "trim", sugars: 2 }

Both coffees are going to be made with milk and sugar (even if it’s 0 sugars), but only one coffee order has the specifics, the other should use the default settings for milk & sugar.

In other languages, I often see this resulting in code like:

def num_sugars_for_coffee
  if coffee_order[:sugars].present?
    coffee_order[:sugars]
  else
    1
  end
end

barista.add_sugar(num_sugars_for_coffee)

A “helper” type of method has been written which produces the correct number of sugars regardless of whether it was specified in the coffe order. Rails has got our back with at least two possible refactorings.

Create a Hash with a custom default value to be returned when the specified key does not exist

This allows a default value to be specified at the time of the Hash being instanciated. In other words, your coffee order class could be the one to specify the defaults at the time of every new order.

coffee_order_3 = Hash.new("Liam Neeson") # <---- Specifies the default value to return for non-existent keys in the Hash

coffee_order_3[:type] = "flat_white"

coffee_order_3[:type]
=> "flat_white"  # Great!

coffee_order_3[:name]
=> "Liam Neeson" # Sweet. Hash default in action

coffee_order_3[:who_will_find_you_and_will_kill_you]
=> "Liam Neeson" # I guess that makes sense

coffee_order_3[:sugars]
=> "Liam Neeson" # 🤔

Liam Neeson

Fetch a Hash value with a specified default

Contrastingly, this allows a default value to be specified at query-time. Meaning, the enquirer (i.e. the barista) class can be the one to know & specify the defaults at the time of preparing the coffee.

coffee_order_4 = { name: "Dylan", sugars: 3 }

coffee_order_4.fetch(:name, "NoName")
=> "Dylan"      # Hash has a value for :name which takes precidence

coffee_order_4.fetch(:type, "Long Black")
=> "Long Black" # Hash does not have a :type, so the default prevails

Result

coffee_order_1 = { name: "Andrew", type: "flat_white" }

.
.
.

barista.add_sugar(coffee_order_1.fetch(:sugar, 1))

Two different solutions to the same problem 🤗. As usual, the context of the situation will probably deem one solution more appropriate than the other.