Hashes in #Ruby
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!
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" # 🤔
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.