- Iterate through a nested hash
- Modify the correct element in a nested hash
So much of what we do in programming involves storing data in hashes. Often the hashes that we will encounter will have more than one level. As we get into the web, this will become abundantly clear. To build programs in the future, we'll absolutely need to get comfortable working with hashes. Let's get started!
Fork and clone this lab. You'll be coding your solution in lib/contacts.rb
.
You'll be manipulating the following Hash
:
contacts = {
"Jon Snow" => {
name: "Jon",
email: "jon_snow@thewall.we",
favorite_ice_cream_flavors: ["chocolate", "vanilla"]
},
"Freddy Mercury" => {
name: "Freddy",
email: "freddy@mercury.com",
favorite_ice_cream_flavors: ["strawberry", "cookie dough", "mint chip"]
}
}
Your good buddy Freddy Mercury has recently developed a strawberry allergy! You
need to delete "strawberry"
from his list of favorite ice cream flavors in the
remove_strawberry
method.
Iterate over the contacts
hash and when you reach the key
:favorite_ice_cream_flavors
, remove "strawberry"
from the Array of Freddy's
favorite ice cream flavors.
There are at least two ways you can accomplish this, and for this codealong, we'll work with the second way.
-
You can directly iterate over the hash that is the value of the
"Freddy Mercury"
key by calling an enumerator method incontacts["Freddy Mercury"]
. -
You can set a conditional to iterate through the hash for
Freddy Mercury
only; when you reach the appropriate level, check to see if the key==
("is equal to"):favorite_ice_cream_flavors
. If it is, check to see if the array of flavors contains"strawberry"
. If it does, then delete it from theArray
.
Inside the remove_strawberry
method, let's take our first dive into the
contacts Hash
. Then we'll use binding.pry
to see where we are.
We are going to first iterate over the top level of the Hash
where the keys
should be the person and the values should be a Hash
of details about the
person.
Note on variable naming: This process will be remarkably easier if you name
your variables to accurately reflect the data they represent. For now, when the
value we're iterating over is another hash, we will explicitly add a _hash
to
the end of the variable name (E.G. contact_details_hash
below).
contacts.each do |person, contact_details_hash|
binding.pry
end
We can enter the pry
in one of two ways: by running learn test
or by
running ruby lib/contacts.rb
. We'll use learn test
.
Let's run learn test
in the terminal and, at the pry
prompt, check that
our defined variables (person
and contact_details_hash
) match our
expectations.
> person
=> "Jon Snow"
> contact_details_hash
=> {:name=>"Jon", :email=>"jon_snow@thewall.we", :favorite_ice_cream_flavors=>["chocolate", "vanilla"]}
Excellent! They do!
Type exit
while in pry
to continue. The pry
should trigger a second time
because we have two contacts. You can verify that we're in the second loop
through our hash by checking the values of person
and data
at the pry
prompt.
Typing exit
now will end the loop and exit pry
since we've finished
iterating through our contacts. It will also display the results of the test,
which we haven't passed just yet.
contacts.each do |person, contact_details_hash|
if person == "Freddy Mercury"
contact_details_hash.each do |attribute, data|
binding.pry
end
end
end
Again, let's jump into our binding.pry
using learn test
. We can verify
that we've found the record for Freddy Mercury by checking the values
of our variables:
> attribute
=> :name
> data
=> "Freddy"
Before we move on, you will need to exit pry
again so you can see the
results of the new code we'll be writing in Step 3. We are now inside the
loop through the attributes. Because there are three of them, we will need
to run exit
three times to finish the loop and exit pry
. Alternatively,
you can run exit!
or !!!
at any time to exit out of pry
entirely.
contacts.each do |person, contact_details_hash|
if person == "Freddy Mercury"
contact_details_hash.each do |attribute, data|
if attribute == :favorite_ice_cream_flavors
binding.pry
end
end
end
end
This time we are still iterating through the attributes but we've added a
conditional so the pry
will only hit when the attribute is equal to
:favorite_ice_cream_flavors
. If we check the value of data
in our
binding, we should see the array containing Freddy's favorite flavors.
Lastly, we will use delete_if
to iterate through the ice cream array and
remove any element that matches "strawberry". Recall that data
is the array
containing Freddy's favorite ice cream flavors. delete_if
will iterate through
the array, check each element to see if it is equal to "strawberry", and delete
the key/value pair if the block returns true
. Learn more about it in the ruby
docs..
contacts.each do |person, contact_details_hash|
if person == "Freddy Mercury"
contact_details_hash.each do |attribute, data|
if attribute == :favorite_ice_cream_flavors
data.delete_if {|ice_cream| ice_cream == "strawberry"}
end
end
end
end
The full method should now be:
def remove_strawberry(contacts)
contacts.each do |person, contact_details_hash|
if person == "Freddy Mercury"
contact_details_hash.each do |attribute, data|
if attribute == :favorite_ice_cream_flavors
data.delete_if {|ice_cream| ice_cream == "strawberry"}
end
end
end
end
end
Congrats! You made it. Test that your method works by running ruby bin/contacts
in the terminal. It should output the hash without strawberry ice
cream. Also, be sure to run the specs to make sure they pass.