Front End Software Engineer
Working with model associations can be difficult.
While building my mock Central Perk, Ruby on Rails application.
relationships made sense, but the addition to many-to-many—
—relationships are where it got weird.
For my project, I planned on having three models: Users (baristas), Orders and Menu Items (products). A user could have many orders. An order, which belongs to a user, could have many Menu Items
With those models, programmatically, I wanted to be able to do the following:
After setting up my models, everything seemed to be working up until
. Currently, an Order with an attribute of
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 looking confused
This is where a
relationship came in.
has many through
If I wanted more than one MenuItem, per Order, the result of
needed to be an array. This is where a four,
model comes in.
acts as a join table, with foreign keys to the
models. In this example, think of each OrderItem record has a transaction instance, representing one
at a time.
An Order would essentially be a collection of all
records with the same
. I was a step closer to figuring out what I needed.
At first, an
model made sense.
Until, it didn’t.
Would I need to call
to see all the items in that order? My app had a
model too. How do you build a has_many through a relationship when there are more than three models?
Pheobe running around screaming "What do I do?"
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:
Visually, I thought of the relationships between the four models as looking like this:
Visualization of the has_many through relationship of User, Order (which contains OrderItems) and Menu Items
This was making sense!
I would not need to reference
directly. ActiveRecord does that work for me. Since an
, referencing the
would gives me direct access to
My finalized models now looked like this:
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
With the associations complete, I needed a form to create the
object. At first, everything seemed to be working. But after looking closer at the console, I realized the transaction was getting rolled back and orders were not being saved to the database.
I noticed the
key was listed in my strong params, but I was getting a "
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.
order = Order.create(user_id: 1, name_for_pickup: "Rachel", menu_item_ids: [1,2,3])
order.menu_items // [1,2,3]
order.menu_items << item
Then it hit me.
Rails was right in not permitting the
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 param) and shovel them into the array
I updated my create order method from this:
def create @order = Order.create(order_params) if @order.save redirect_to order_path(@order) else render :new end end
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
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
are also something to look out for. All params are strings, 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
can be submitted in a
model, the transaction will fail (and not write to your database, yay!) if an attribute of
was within your params.
Use .create! when testing
will give more information into what validations may be causing errors. For example, in my app, an
must have as
(barista) user_id associated with it. Running
in the console would not tell me much, but running
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
(an empty object, which will not validate because there is no
order = Order.create()
will return false.
Model associations can be difficult and frustrating, but not impossible to work with. With some careful debugging, you can be a master of model associations in no time.
Previously published at https://shannoncrabill.com/blog/ruby-on-rails-debugging-model-associations/