Many-to-Many Relationships: Part Two
Master Advanced Ruby on Rails Database Relationships
This tutorial builds directly on Part One. You'll learn to overcome limitations of has_and_belongs_to_many relationships by implementing the more powerful has_many through pattern.
Relationship Types: HABTM vs Has Many Through
| Feature | has_and_belongs_to_many | has_many through |
|---|---|---|
| Join Table Access | Limited | Full Model |
| Additional Fields | Not Possible | Supported |
| Complexity | Simple | Moderate |
| Flexibility | Basic | Advanced |
To solve our current limitation, we'll implement a more sophisticated approach: creating a dedicated line_item model. This model will serve as an intelligent intermediary between products and carts, transforming our simple join table into a full-featured Rails model. This architectural pattern is called has_many, through, where products have many carts through line_items, and vice versa. By elevating our join table to a proper model, we gain the ability to store additional attributes like quantity directly on the line_item itself.
This transformation elevates our Line Item from a simple database relationship to a central business logic component, enabling sophisticated e-commerce functionality that scales with your application's growth.

If you completed the previous exercises (8A–9A), you can proceed directly to the next section. For those who haven't completed the prerequisites, follow the setup instructions below to ensure your development environment matches the expected state.
Rails Tools for Agile Development
Ruby Language
Simple syntax enables rapid prototyping and iteration. Clean code structure makes refactoring straightforward.
Generators
Build standard code blocks quickly with built-in generators. Scaffold entire features in minutes.
Database Migrations
Track and reverse database changes safely. Version control your schema evolution.
Integrated Testing
Ensure changes don't break existing features. Automated testing catches regressions early.
“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.
Agile development philosophy emphasizes building over extensive planning
In the Terminal tab where the server isn't running, rollback the most recent migration to prepare for our architectural changes:
rails db:rollback
This command undoes the last migration, allowing us to replace our simple join table with the more sophisticated line_item model.
Navigate to the migration files directory: Class Files > yourname-Rails Level 2 Class > nutty > db > migrate
Locate the migration file ending with create_carts_products.rb and note its filename for the next step.
Remove the outdated migration file by running:
rails destroy migration create_carts_products
This command removes both the migration file and any associated references, ensuring a clean slate for our new approach.
Generate the new line_item model with proper relationships and attributes:
rails generate model line_item quantity:integer cart:references product:references
rails db:migrate
The references type automatically creates foreign key columns and indexes, establishing proper database relationships while maintaining referential integrity.
Update the Cart model to use the new relationship pattern. Open nutty > app > models > cart.rb
Remove the obsolete relationship declaration (around line 4):
has_and_belongs_to_many :productsReplace it with the has_many, through relationship:
class Cart < ApplicationRecord
belongs_to :customer
has_many :line_items, dependent: :destroy
has_many :products, through: :line_items
end
This configuration provides two access patterns: cart.line_items for direct line item manipulation (including quantity), and cart.products for traversing directly to associated products. The dependent: :destroy ensures line items are automatically removed when a cart is deleted, maintaining database integrity.
Save and close the cart.rb file.
Apply the same relationship pattern to the Product model. Open nutty > app > models > product.rb
Remove the outdated relationship (around line 5):
has_and_belongs_to_many :cartsAdd the corresponding has_many, through relationships:
class Product < ApplicationRecord
validates :title, :sku, :price, presence: true
validates :price, numericality: true
has_many :line_items, dependent: :destroy
has_many :carts, through: :line_items
has_one_attached :image
endSave and close the product.rb file.
Test the new relationship structure by navigating to localhost:3000 in your browser.
Click the Cart link in the navigation. The cart appears empty because rolling back the migration removed the existing cart data—this is expected behavior during architectural changes.
Return to the homepage and select any product.
Click Add To Cart for that product. The item should appear in your cart, demonstrating that our new relationship structure works seamlessly with existing controller logic—a testament to Rails' consistent interface design.
Controller and View Updates
Modify Cart Controller
Update create method to use @cart.line_items.build instead of direct product assignment
Update Cart View
Change iteration from @cart.products to @cart.line_items for quantity access
Fix Product References
Add line_item prefix to all product references in the view template
Implement Quantity Field
Replace static HTML input with number_field_tag using line_item.quantity
Use CTRL-Cmd-G to select all instances of 'product' then add 'line_item.' prefix efficiently across the entire file.
Key Takeaways