Refactoring Code
Refactoring Code: Nested Collections in Ruby
I’m currently working through the Ruby Exercises repo for Turing School, and recently I completed nesting_test.rb. It was a productive struggle for sure, and it’s humbling to really take the time to look through the Ruby Docs for hashes and arrays.
The goal is to make each test pass in the nesting_test.rb file, which requires a nested hash called stores in the nesting.rb file.
The stores collection contains a hash of hashes and arrays. The first key values are three stores, :olive_garden, :dennys, and :macdonalds. Each key contains a hash value with additional keys, :employees and :dishes, which then contain additional collections. For the purpose of this blog post, I am going to focus on the :olive_garden key, and in particular the test test_full_menu_price_for_olive_garden.
Here is the first part of the stores collection with :olive_garden. I am abbreviating the code for brevity:
def stores
{
olive_garden: {
employees: ['Jeff', 'Zach', 'Samantha'],
dishes: [ {name: 'Risotto',
ingredients: ['Rice',
'Cheese',
'Butter'],
price: 12},
{name: 'Steak',
ingredients: ['Beef',
'Garlic'],
price: 15}
]
},
# <...>
I got the test to pass, but I was dissatisfied with my initial solution, which felt clunky:
def test_full_menu_price_for_olive_garden
#=======================
full_menu_price = []
stores[:olive_garden].each do |key, value|
if key == :dishes
value.each do |element|
if element.class == Hash
full_menu_price << element.values_at(:price)
end
end
end
end
full_menu_price = full_menu_price.flatten.sum
#=======================
assert_equal 27, full_menu_price
end
The directions encourage using each for the initial solution and then refactoring, which I did. I initialized variable full_menu_price and assigned it the value of an empty array. Then, I called the each method on the stores[:olive_garden] hash. Since we wanted the array within :dishes, we only wanted to access the value if key == dishes. Then, I called the each method on value, which is the array within :dishes.
This ended up being way overcomplicated, and there was clearly a better, neater solution.
I’ve been trying to use the powerful Array#inject method more lately, and this seemed like the perfect opportunity to do so. This Medium post about inject has helped me a lot with figuring out the best opportunities to use the method.
Ultimately, I arrived at this solution, which is far more elegant:
def test_full_menu_price_for_olive_garden
#=======================
full_menu_price = stores[:olive_garden][:dishes].inject(0) do |accumulator, dish|
accumulator += dish[:price]
end
#=======================
assert_equal 27, full_menu_price
end
If you are looking to practice working with collections in Ruby, here are some resources I’ve found helpful: