This tutorial guides you through the process of creating a has_many, through relationship using Ruby on Rails, with an emphasis on setting quantity and the agile development approach.
This exercise is excerpted from Noble Desktop’s past web development training materials. Noble Desktop now teaches JavaScript and the MERN Stack in our Full Stack Development Certificate. To learn current skills in web development, check out our coding bootcamps in NYC and live online.
Note: These materials are provided to give prospective students a sense of how we structure our class exercises and supplementary materials. During the course, you will get access to the accompanying class files, live instructor demonstrations, and hands-on instruction.
Topics covered in this Ruby on Rails tutorial:
The has_many, through relationship, Setting quantity
Exercise Overview
With has_and_belongs_to_many
, every time you relate two model objects together, an entry is made in the join table, which relates those two objects. However, when you use the has_and_belongs_to_many
relationship type, there isn’t really any way to access the contents of the join table directly (such as to add additional fields to it). We’ve hit a wall with our initial approach.
No need to feel bad if you reach this point in your own development work. Part of working in Ruby on Rails is being Agile in your development approach. In short, that means that when we encounter a obstacle, we pause, retool our approach, and forge ahead.
Agile Development
Agile software development is a huge topic, and a course in its own right.
In essence, Agile development just means that when implementing a new feature, we try our best to build only what’s needed in the moment. If, later on, it turns out that our approach wasn’t adequate, we can always go back and revise it to make it better. It’s easy to plan software to death drawing up database diagrams and API specs and zillions of mockups and never actually write a line of code. Agile proposes that it’s not only possible to take what’s written on the back of a napkin and just start building it in code—it can even be preferable. Why? Because the process of actually building a site often turns up all kinds of problems and challenges that you didn’t anticipate—and a fair number of the scenarios you did worry about might turn out not to be important at all.
Rails facilitates Agile development by providing a whole host of tools. Foremost among them is the Ruby language with its simple syntax, but other examples include the generators which build out huge blocks of standard code quickly, database migrations that let you keep track of and even reverse database changes, and support for integrated testing to ensure that changes to one part of the site don’t break existing features elsewhere.
So here, what we’re going to do is create another model and call it line_item
. As shown in the diagram below, the line_item
model will be used to connect the products to the carts. This type of relationship is called has_many, through
. We’re going to say product has_many :carts
through line_items
. That way, we have a full-fledged model in between the ends of the many-to-many relationship. The quantity
field can be a property of that line_item
model.
No longer a mere join table, our Line Item will become an important player on the Rails stage.
-
If you completed the previous exercises, you can skip the following sidebar. We recommend you finish the previous exercises (8A–9A) before starting this one. If you haven’t finished them, do the following sidebar.
If You Did Not Do the Previous Exercises (8A–9A)
- Close any files you may have open.
- Open the Finder and navigate to Class Files > yourname-Rails Class
- Open Terminal.
- Type
cd
and a single space (do NOT press Return yet). - Drag the yourname-Rails Class folder from the Finder to the Terminal window and press ENTER.
- Run
rm -rf nutty
to delete your copy of the nutty site. - Run
git clone https://bitbucket.org/noble-desktop/nutty.git
to copy the That Nutty Guy git repository. - Type
cd nutty
to enter the new directory. - Type
git checkout 9A
to bring the site up to the end of the previous exercise. - Run
bundle
to install any necessary gems. - Run
yarn install --check-files
to install JavaScript dependencies.
Setting Up the has_many, through Relationship
-
For this exercise, we’ll continue working with the nutty folder located in Desktop > Class Files > yourname-Rails Class > nutty
If you haven’t already done so, we suggest opening the nutty folder in your code editor if it allows you to (like Sublime Text does).
-
You should still have a window with two tabs open in Terminal from the last exercise, the first of which is running the server. If you don’t, complete the following sidebar.
Restarting the Rails Server
- In Terminal,
cd
into the nutty folder:
- Type
cd
and a space. - Drag the nutty folder from Desktop > Class Files > yourname-Rails Class onto the Terminal window (so it will type out the path for you).
- In Terminal, hit Return to change directory.
-
In Terminal, type the following:
rails s
- Open a new tab (Cmd–T) leaving our server running in the old tab.
- In the new tab,
cd
into the nutty folder:
- Type
cd
and a space. - Drag the nutty folder from Desktop > Class Files > yourname-Rails Class onto the Terminal window (so it will type out the path for you).
- In Terminal, hit Return to change directory.
- In Terminal,
-
In the Terminal tab where the server isn’t running, type the following to roll back (undo) the most recent migration that we ran:
rails db:rollback
Next we need to delete the migration file we just rolled back. On the Desktop, navigate to Class Files > yourname-Rails Level 2 Class > nutty > db > migrate
Find the file that ends with create_carts_products.rb
-
In Terminal, type the following command to delete the migration file:
rails destroy migration create_carts_products
-
Create the
line_item
model by typing:rails g model line_item quantity:integer cart:references product:references rails db:migrate
We need to go back to our models and update the relationships. Let’s start with the cart. In your code editor, open nutty > app > models > cart.rb
-
Delete the following line of code (around line 4):
has_and_belongs_to_many :products
-
Add the following bold code:
class Cart < ActiveRecord::Base belongs_to :customer has_many :line_items has_many :products, through: :line_items end
Now we have a choice when working with the cart. We can call
cart.line_items
and then access the product property of each line item as we step through them. Or we can callcart.products
and basically traverse directly over the entireline_items
table and get to the other side of the many-to-many relationship. Save the file, then close it.
Let’s do the same thing with the product model. In your code editor, open:
nutty > app > models > product.rb-
Delete the following line of code (around line 5):
has_and_belongs_to_many :carts
-
Add the following bold code:
class Product < ActiveRecord::Base validates :title, :sku, :price, presence: true validates :price, numericality: true has_many :line_items has_many :carts, through: :line_items has_one_attached :image
Save the file, then close it.
Go to or reload localhost:3000 in the browser.
Click the Cart link at the top right. The cart is now empty because when we rolled back the migration, the items were deleted from the cart.
Go back to the homepage and click on a product.
For that product, click Add To Cart. The product should now appear in the cart. Notice that we didn’t have to update any of the code in the Cart controller. Nifty!
Implementing the Quantity field
Let’s work on getting the quantity field to work.
-
In your code editor, open nutty > app > controllers > cart_controller.rb
We need to modify the
create
method here to deal with quantities instead of just adding a single instance of a product. -
Around line 10, highlight the following code that adds the product directly to
cart.products
after the product is loaded:@cart.products << product
-
Delete it, replacing it with the following bold code that refers to
line_item
:def create product = Product.find(params[:product_id]) @cart.line_items.build(product: product, quantity: params[:quantity]) @cart.save
Save the file.
-
In your code editor, open nutty > app > views > cart > index.html.erb
If we want to display the quantity accurately on the cart screen, we’re going to have to step through
line_items
instead ofproducts
. The reason we need to do that is that, again, we need to access theline_item
directly. -
Around line 20, edit the code as shown in bold:
<tbody> <% @cart.line_items.each do |line_item| %> <tr>
-
In that same block of code starting around line 21, add
line_item.
beforeproduct
as shown below (don’t forget the period).Sublime Text Users Only: If you select the word
product
then hit Ctrl–Cmd–G you can select all instances ofproduct
on the page. Then you just have to hit the Left Arrow key once to move the cursors to just before each instance of the word product and typeline_item.
to quickly make the changes below.<% @cart.line_items.each do |line_item| %> <tr> <td id="thumbnail-div" class="hidden-xs"> <%= link_to line_item. product do %> <%= image_tag url_for(line_item.product.image.variant(resize_to_limit: [200, 200])), alt: product.title, class: 'cart-thumbnail' %> <% end %> </td> <td> <%= link_to line_item. product do %> <p><%= line_item. product.title %></p> <% end %> <p class="gray-text">Item #<%= line_item. product.sku %></p> </td> <td><input type="number" name="quantity" min="1" max="100" value="1"> <a href="#"><span class="glyphicon glyphicon-refresh"></span>update</a> <a href="#"><span class="glyphicon glyphicon-remove"></span>remove</a> </td> <td class="hidden-xs"><%= number_to_currency line_item. product.price %></td> <td class="total-price"><%= number_to_currency line_item. product.price %></td> </tr> <% end %>
-
Select the bold code around line 33:
<td><input type="number" name="quantity" min="1" max="100" value="1">
Hit Return to delete it and create a new line.
-
Add the following bold code around line 34 to implement the quantity field:
<td> <%= number_field_tag :quantity, line_item.quantity, min: 1, max: 100 %> <a href="#"><span class="glyphicon glyphicon-refresh"></span>update</a>
Save the file.
In the browser, reload: localhost:3000
Click on a product.
-
On the product page, try setting the quantity to 8 then click Add To Cart.
You should be taken to the Cart and see that it added the product with a quantity of 8. However, you may notice that the first item we added has no quantity because we added it before we set the quantity code. We need to get that remove link working, which we’ll do in the next exercise.