Object-Oriented Ruby: The importance of knowing your return values

Elizabeth Prendergast
The Startup
Published in
3 min readSep 10, 2019

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:

  1. Sketch out a domain model to visualise the relationships between objects
  2. Establish what attributes each object will be assigned at initialisation
  3. Decide which ‘helper’ instance methods I’m going to write
A digital representation of my sketch, complete with object relationships, attributes to be defined at initialisation, and which ‘helper’ methods I plan 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:

Defining the Bakery, Dessert, and Ingredient classes.

But how do I know if my relationships are working properly? Let’s write some seed data.

Creating some seed data by calling ‘.new’ on each of my newly defined objects.

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!

My first go at defining the Bakery#shopping_list method using my ‘helper’ method, Bakery#ingredients

I run my code — no errors returned. Excellent! Next, using pry, I try out my shiny new method on my favourite bakery, b2.

Testing my new method — uh-oh…

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:

Double checking my Bakery#ingredients method on my b2 instance of Bakery

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

My previously defined Bakery#shopping_list method, now with .flatten prepended to our .map method.

Which, when called on b2, returns…

We’ve got an array of ingredient names! Hooray!

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!

--

--