Debugging Has Many, Through Relationships in Ruby on Rails
Tags: ruby, rails, oop, beginners
Published: 2020-02-02 17:27:34

---
title: Debugging Has Many, Through Relationships in Ruby on Rails
published: true
date: 2019-12-15 10:25:02 UTC
tags: ruby, rails, oop, beginner
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/g93zh7wtpg32wt2mls4m.jpg
canonical_url: https://shannoncrabill.com/blog/ruby-on-rails-debugging-model-associations/
---

Working with associations was the most difficult part of building [Central Perk](https://github.com/scrabill/central-perk), my Ruby on Rails application. `has_many` and `belongs_to` relationships made sense, but the addition to many-to-many—`has_many through`—relationships are where it got weird. I ran into several issues while building my application and this is how I solved them. 

## Model Behavior

For my project, I planned on three models. Users (baristas), orders and menu items (products). The relationship would like this:

![](https://shannoncrabill.com/blog/wp-content/uploads/2020/02/Screen-Shot-2020-02-02-at-11.34.01-AM.png)<figcaption>Outline of model associations for Central Perk, a coffee shop, point-of-sale application</figcaption>

With those models, programmatically, I wanted to be able to do the following:

- **Create a new order and automatically associate it with a user.** `shannon.orders.create`
- **Be able to see all orders that belong to a user.** `shannon.orders`
- **See all menu\_items (products) in a specific order.** `order.menu_items`

After setting up my models, everything seemed to be working up until `order.menu_items`. Currently, an Order with an attribute of `:menu_items` could only have one value. Meaning, _one_ MenuItem per Order. Can you imagine if you had to get back in line and start a new order for each item you wanted to buy at a coffee shop?

![Ross and Rachel from friends, looking confused](https://media.giphy.com/media/fsDWfqajzoNR6Qpr5p/giphy.gif)

This is where a `has_many :through` relationship came in.

## Many to Many

If I wanted more than one menu item, per order, the result of `order.menu_items` needed to be an array. This is where the OrderItem model comes in.

OrderItem acts as a join table, with foreign keys to the Order and MenuItems models. In this example, think of each OrderItem record has a transaction instance, representing one Order and one MenuItem at a time.

An Order would essentially be a colletion of all OrderItem records with the same `:order_id`. I was a step closer to figuring out what I needed.

## But?

At first, an OrderItem model made sense.

Until, it didn’t.

Would I need to call `order.order_items.menu_items` to see all the items in that order? My app had a User model too. How do you build a `has_many through` a relationship when there are more than three models?

![Pheobe running around a bridal shop, shouting "What do I do?"](https://media.giphy.com/media/h5Kj3oDLtuh6NoRy5x/giphy.gif)

In reality, `has_many through` only works with three models. But, through other associations, it _extends_ the functionality of those models. If I wanted to know how many MenuItems were in the first order, created by a specific user I could call something like this: `user.orders.first.menu_items.count`.

Visually, I thought of the relationships between the four models as looking like this:

![](https://i1.wp.com/shannoncrabill.com/blog/wp-content/uploads/2019/12/cb-process-2.jpg?w=640&ssl=1)<figcaption>Visualization of the has_many through relationship of User, Order (which contains OrderItems) and Menu Items.</figcaption>

This was making sense!

I would not need to reference OrderItems directly. ActiveRecord does that work for me. Since an Order has many OrderItems, referencing the Order would gives me direct access to MenuItems.

My updated models now looked like this:

```ruby
class User < ApplicationRecord
  has_secure_password
  has_many :orders
end

class Order < ApplicationRecord
  belongs_to :user
  has_many :order_items
  has_many :menu_items, through: :order_items
end

class OrderItem < ApplicationRecord
  belongs_to :order
  belongs_to :menu_item
end

class MenuItem < ApplicationRecord
  has_many :order_items
  has_many :orders, through: :order_items
end
```
## Params

With the associations complete, I needed a form to create the Order object. At first, everything seemed to be working. But after looking closer at the console, I realized the transaction was getting rolled back and Order was not saving to the database.

I noticed the `:menu_items_id` key was listed in my strong params, but I was getting a _:menu_items_ids is not permitted_ error.

To try and resolve this, I worked in the console, testing things out, bit by bit until I could pinpoint where I was getting stuck. In the console, I could successfully do the following.

- **Create an order.** `order = Order.create(user_id: 1, name_for_pickup: "Rachel", menu_item_ids: [1,2,3])`
- **View the value of menu\_items**. `order.menu_items # [1,2,3]`
- **Add an item to an order**. `order.menu_items << item`
- **Save the order**. `order.save`

Then it hit me.

Ruby was right in not permitting the `menu_item_ids` param. I thought I needed to create an order. Instead, I needed to create an order, _find the menu items by id (menu\_items\_id, which was the unpermitted params) and shovel them into the array_. I updated my create order method.

From this:

```ruby
def create
  @order = Order.create(order_params)
  if @order.save
    redirect_to order_path(@order)
  else
    render :new
  end
end
```

To this

```ruby
 def create
  @order = Order.create(order_params)

  items_to_add = params[:order][:menu_item_ids]

  items_to_add.each do |item_id|
    if item_id != ""
      item_id = item_id.to_i
      item_to_add = MenuItem.find_by_id(item_id)
      @order.menu_items << item_to_add
    end
  end

  if @order.save
    redirect_to order_path(@order)
  else
    render :new
  end

end
```

And it worked!

![Chandler jumping on a coffee table and doing a happy dance](https://media.giphy.com/media/Gf3fU0qPtI6uk/giphy.gif)

## Lessons Learned

In summary, if you are running into issues with object relationships, try the following:

### Verify that the params are correct

Typos can instantly cause object creation to fail. Pluralization like `menu_item_id` vs `menu_item_ids` are also something to look out for. [All params are strings](https://www.rubyguides.com/2019/06/rails-params/), which may cause downstream effects if you are expecting an integer or boolean.

### All model attributes are listed in strong params

Strong params help to prevent users from injecting harmful data into your database via the form. If strong params lists only `:name`, `:email` and `:password` can be submitted in a User model, the transaction will fail (and not write to your database, yay!) if an attribute of `:not_a_hacker` was within your params.

### Use `.create!` instead of `.create` when testing

`create!` will give more information into what validations may be causing errors. For example, in my app, an Order must have as User (barista) `id` associated with it. Running `Order.create()` in the console would not tell me much, but running `Order.create!()` would print out an error like `A User must exist`.

### Append `.valid?` to objects

An object may be updating, but, is it saving properly to the database? For example, if `order` referenced `Order.create()` (an empty object, which will not validate because there is no `:user_id`), adding `.valid?` will return false.

## Summary

Model associations can be difficult and frustrating, but not impossible! 

## Resources

- [Active Record Associations](https://guides.rubyonrails.org/association_basics.html)

--

The post [Debugging Has Many, Through Relationships in Ruby on Rails](https://shannoncrabill.com/blog/ruby-on-rails-debugging-model-associations/) appeared first on [Shannon Crabill's Blog](https://shannoncrabill.com/blog).
Photo by [Annie Spratt](https://unsplash.com/@anniespratt) on [Unsplash](https://unsplash.com/photos/PM4Vu1B0gxk)