Many-to-Many Relationships: Part Two

Free Ruby on Rails Tutorial

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

Full-Stack Web Development Certificate: Live & Hands-on, In NYC or Online, 0% Financing, 1-on-1 Mentoring, Free Retake, Job Prep. Named a Top Bootcamp by Forbes, Fortune, & Time Out. Noble Desktop. Learn More.

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.

relationship diagram has_many through

  1. 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)

    1. Close any files you may have open.
    2. Open the Finder and navigate to Class Files > yourname-Rails Class
    3. Open Terminal.
    4. Type cd and a single space (do NOT press Return yet).
    5. Drag the yourname-Rails Class folder from the Finder to the Terminal window and press ENTER.
    6. Run rm -rf nutty to delete your copy of the nutty site.
    7. Run git clone https://bitbucket.org/noble-desktop/nutty.git to copy the That Nutty Guy git repository.
    8. Type cd nutty to enter the new directory.
    9. Type git checkout 9A to bring the site up to the end of the previous exercise.
    10. Run bundle to install any necessary gems.
    11. Run yarn install --check-files to install JavaScript dependencies.

Setting Up the has_many, through Relationship

  1. 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).

  2. 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

    1. 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.
    1. In Terminal, type the following:

      rails s
      
    2. Open a new tab (Cmd–T) leaving our server running in the old tab.
    3. 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.
  3. 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
    
  4. 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

  5. Find the file that ends with create_carts_products.rb

  6. In Terminal, type the following command to delete the migration file:

    rails destroy migration create_carts_products
    
  7. Create the line_item model by typing:

    rails g model line_item quantity:integer cart:references product:references
    rails db:migrate
    
  8. 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

  9. Delete the following line of code (around line 4):

    has_and_belongs_to_many :products
    
  10. 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 call cart.products and basically traverse directly over the entire line_items table and get to the other side of the many-to-many relationship.

  11. Save the file, then close it.

  12. Let’s do the same thing with the product model. In your code editor, open:
    nutty > app > models > product.rb

  13. Delete the following line of code (around line 5):

    has_and_belongs_to_many :carts
    
  14. 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
    
  15. Save the file, then close it.

  16. Go to or reload localhost:3000 in the browser.

  17. 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.

  18. Go back to the homepage and click on a product.

  19. 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.

  1. 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.

  2. Around line 10, highlight the following code that adds the product directly to cart.products after the product is loaded:

    @cart.products << product
    
  3. 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
    
  4. Save the file.

  5. 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 of products. The reason we need to do that is that, again, we need to access the line_item directly.

  6. Around line 20, edit the code as shown in bold:

    <tbody>
       <% @cart.line_items.each do |line_item| %>
          <tr>
    
  7. In that same block of code starting around line 21, add line_item. before product 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 of product 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 type line_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 %>
    
  8. Select the bold code around line 33:

    <td><input type="number" name="quantity" min="1" max="100" value="1">
    
  9. Hit Return to delete it and create a new line.

  10. 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>
    
  11. Save the file.

  12. In the browser, reload: localhost:3000

  13. Click on a product.

  14. 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.

How to Learn Coding

Master coding with hands-on training. Learning how to code in JavaScript, Python, and other popular languages can pave the way to a job in tech, such as web development, data science & analytics, or software engineering.

Yelp Facebook LinkedIn YouTube Twitter Instagram