Object-Oriented Ruby: The importance of knowing your return values
In preparation for my first ‘code challenge’ in object-oriented Ruby at Flatiron School, I spent many, many hours practicing object models, establishing relationships between different objects, and writing class and instance methods. I had even figured out how to successfully use a join class to establish a many-to-many relationship (!). I was starting to feel pretty confident about my understanding of object relationships — until a seemingly straightforward ‘Bakery’ problem set had me puzzled.
The Problem
For this specific problem set, the problem (albeit simplified for the purposes of this blog) was set out as follows:
You are building an app for a national bakery chain. Your models are bakeries, desserts, and ingredients.
- a bakery has many desserts
- a dessert belongs to a bakery
- a dessert has many ingredients
- an ingredient belongs to a dessert
Write out the relationships using has_many, belongs_to and has_many_through. Create the necessary methods to connect these classes.
For the purposes of this blog post, I will be doing a deep-dive into one particular instance method which was requested as part of the problem set:
- Bakery#shopping_list: should return an array of ingredient names for the bakery
The Approach
I begin all object-oriented Ruby problems with three steps:
- Sketch out a domain model to visualise the relationships between objects
- Establish what attributes each object will be assigned at initialisation
- Decide which ‘helper’ instance methods I’m going to write
Brilliant! Now I can visualise exactly what I’m going to build in code before I even start looking at what other custom instance methods the problem is asking for.
My models, complete with boilerplate, looked like this:
But how do I know if my relationships are working properly? Let’s write some seed data.
Looks delicious! After checking to make sure all my methods are working properly using pry, I’m ready to move onto writing my #shopping_list method under the Bakery class.
Since I already have an #ingredients instance method for my Bakery class, surely I can just use the ‘.map’ method to call ‘.name’ on each ingredient to return an array of ingredient names? Let’s try it out!
I run my code — no errors returned. Excellent! Next, using pry, I try out my shiny new method on my favourite bakery, b2.
Uh-oh. How could this be? Since my #shopping_list method relies on Bakery#ingredients, let’s double-check that this method is still working:
Bakery#ingredients returns an array of ingredients associated with the b2 instance of bakery, as expected. But lets investigate a little closer…
What’s this? If you look reeeally closely, next to the return arrow, it looks like Bakery#ingredients is returning an array of arrays, not an array of ingredients! No wonder we couldn’t call ‘.name’ on each element of this array! Good thing Ruby gives us a method to fix this problem: .flatten
Which, when called on b2, returns…
Voila! The moral of the story? Always make sure you understand exactly what your methods are returning to you when you write them, otherwise you may end up with your head spinning in circles like me!