Topics Covered in This Ruby on Rails Tutorial:
Calculating the Subtotal, Delegates, Fixing the Order Summary, Displaying the Number of Items in the Cart
Exercise Overview
In this exercise, we'll tackle one of the most critical aspects of e-commerce development: ensuring accurate price calculations throughout your shopping cart system. You'll learn how to implement proper subtotal calculations that reflect item quantities, create functional order summaries, and leverage Rails' powerful delegation pattern to write cleaner, more maintainable code. These are foundational skills that every professional Rails developer must master when building commercial applications.
If you completed the previous exercises, you can skip the following sidebar. We strongly recommend completing exercises 8A-9C before starting this one, as they establish the essential cart infrastructure we'll be building upon. If you haven't finished them, follow the setup instructions below.
Exercise Objectives
0/4Display accurate product prices with quantity-based subtotalsEach cart item should reflect the correct total based on quantity
Implement Rails delegates for cleaner codeReduce redundancy by delegating methods between related models
Create functional order summary calculationsBuild proper subtotal and total methods for cart functionality
Add dynamic cart item counterDisplay real-time count of items in the shopping cart
If You Did Not Do the Previous Exercises (8A-9C)
- Close any files you may have open.
- Open the Finder and navigate to Class Files > yourname-Rails Class
- Open Terminal.
- Type
cdand 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 nuttyto delete your copy of the nutty site. - Run
Git clone https://bitbucket.org/Noble Desktop/nutty.Gitto copy the That Nutty Guy Git repository. - Type
cd nuttyto enter the new directory. - Type
Git checkout 9Cto bring the site up to the end of the previous exercise. - Run
bundleto install any necessary gems. - Run
yarn install—check-filesto install JavaScript dependencies.
Git Repository Setup Process
Navigate to Project Directory
Open Terminal and use cd command with drag-and-drop to navigate to your Rails class folder
Clone Repository
Run git clone command to copy the Nutty Guy repository from Bitbucket
Checkout Specific Version
Use git checkout 9C to bring the site up to the end of the previous exercise
Install Dependencies
Run bundle and yarn install commands to set up all necessary gems and JavaScript dependencies
Displaying an Accurate Product Price with Subtotals
Currently, our cart displays individual product prices regardless of quantity—a critical flaw that would confuse customers and potentially cost your business money. Let's implement proper subtotal calculations that multiply price by quantity for each line item.
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). Modern editors like VS Code, RubyMine, or Sublime Text with proper folder navigation will significantly improve your development workflow.
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 to get your development environment back up and running.
Model Method ImplementationThe subtotal method in LineItem calculates accurate pricing by multiplying product.price * quantity, ensuring each cart item displays the correct total based on quantity selected.
Restarting the Rails Server
- In Terminal,
cdinto the nutty folder:
- Type
cdand 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,
cdinto the nutty folder:
- Type
cdand 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 your code editor, open nutty > app > views > cart > index.html.erb
Look around line 41. This is the code that sets the Total Price column:
<td class="total-price"><%= number_to_currency line_item.product.price %></td>
Notice how this currently shows just the individual product price, not accounting for quantity. This is exactly the kind of bug that would frustrate customers and undermine trust in your application.
Edit that line as shown in bold:
<td class="total-price"><%= number_to_currency line_item.subtotal %></td>Save the file.
In your code editor, open nutty > app > models > line_item.rb
Add the following code shown in bold:
class LineItem < ApplicationRecord
belongs_to :cart
belongs_to :product
def subtotal
product.price * quantity
end
end
This method encapsulates the business logic for calculating subtotals, keeping our view clean and our calculations centralized. This approach follows the principle of putting business logic in models, not views.
Save the file.
Go to the browser where localhost:3000/cart should be open and reload the page. If you closed the browser or didn't do the last exercise:
- Go to localhost:3000 and click on any product.
- Set the Quantity to 9 and click Add to Cart.
The Total Price of the products in the cart now accurately reflects their quantities. This is the foundation of reliable e-commerce functionality.
Delegates
Now that we have working subtotals, let's explore one of Rails' most elegant features: delegation. This pattern allows you to write cleaner, more intuitive code by letting related models pass methods to each other seamlessly.
In your code editor, open nutty > app > models > line_item.rb
Wouldn't it be more intuitive if we could simply call
line_item.priceinstead of the verboseline_item.product.price? Remember thatline_itemdoesn't have its own price field; the product always provides it. Rather than duplicating data or writing repetitive accessor methods, Rails gives us delegation—a powerful pattern that lets related models seamlessly pass their methods to each other.
This approach reduces coupling between your models while maintaining clean, readable code—a hallmark of professional Rails development.
Edit the code as shown in bold:
class LineItem < ApplicationRecord belongs_to :cart belongs_to :product delegate :price, to: :product def subtotal price * quantity end endNotice how much cleaner our subtotal method becomes when we can reference
pricedirectly instead ofproduct.price.Save the file.
In your code editor, open nutty > app > views > cart > index.html.erb.
Around line 40, delete the bold code (don't miss the period):
<td class="hidden-xs"><%= number_to_currency line_item.product.price %></td>Save the file once it looks like the following:
<td class="hidden-xs"><%= number_to_currency line_item.price %></td>Go back to the browser and reload localhost:3000/cart
Everything should remain the same, demonstrating that our refactoring worked perfectly. This is the power of delegation: cleaner code with identical functionality. In professional development, this kind of code clarity becomes crucial as your applications grow in complexity.
In your code editor, open nutty > app > models > line_item.rb.
Add the following bold code around line 5:
delegate :price, :title, :image, :sku, to: :productWe can delegate multiple attributes in a single line, making our model even more convenient to work with throughout the application.
Save the file.
In your code editor, open nutty > app > views > cart > index.html.erb.
Delete the instances of
product.(including the period after it) around lines 24, 29, and 31:<td id="thumbnail-div" class="hidden-xs"> <%= link_to line_item.product do %> <%= image_tag line_item.product.image.url(:medium), 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>Save the file.
Reload localhost:3000/cart in the browser to verify that everything still displays correctly.
To delegate is a way of having related models pass their methods down to each otherBenefits and Considerations of Rails Delegates
Making the Order Summary Functional
With individual line item calculations working, it's time to implement the order summary functionality. We'll architect this thoughtfully, creating separate methods for subtotal and total so we can easily add shipping, taxes, and discounts later—exactly how you'd approach this in a production e-commerce system.
In your code editor, open nutty > app > models > cart.rb
Add the following bold code to add
subtotalandtotalmethods:has_many :products, through: :line_items def subtotal line_items.sum(&:subtotal) end def total subtotal end end- Let's break down the code you just wrote:
- For the
subtotalmethod, we're summing up the subtotal of all our line items using Ruby's elegantsummethod with the ampersand syntax. The&:subtotalsyntax is shorthand for{ |item| item.subtotal }—a more concise way to apply the subtotal method to each line item. - We created a separate
totalmethod that currently mirrors subtotal. This architectural decision gives us a clean foundation for future enhancements like shipping calculations, tax computation, or discount applications without breaking existing code.
- For the
Save the file, then close it.
In your code editor, open nutty > app > views > cart > index.html.erb.
Edit the Subtotal and Total fields (around lines 55 and 67):
<tr> <td>Subtotal</td> <td><%= number_to_currency @cart.subtotal %></td> </tr>Code Omitted To Save Space
<tr> <td>Estimated Tax</td> <td>$0.00</td> </tr> <tr class="total-price"> <td>Total</td> <td><%= number_to_currency @cart.total %></td> </tr>Save the file.
Reload localhost:3000/cart in the browser to see that now the Subtotal and Total in the Order Summary section are working perfectly!
Setting up separate subtotal and total methods now creates a foundation for future enhancements. When you add shipping and taxes later, you'll only need to modify the total method without changing the entire codebase.
Cart Model Methods
Subtotal Method
Uses line_items.sum(&:subtotal) to calculate the sum of all line item subtotals in the cart.
Total Method
Currently returns subtotal but provides extensibility for adding shipping, taxes, and discounts later.
Optional Bonus: Adding a Customers Who Want to Buy This Product Field
Let's enhance our admin interface with valuable business intelligence. We'll add functionality to show which customers have added specific products to their carts—the kind of insight that's invaluable for inventory planning and marketing decisions.
In the browser go to: localhost:3000/admin and sign in.
Click the Products link at the top.
To the far right of any product you've added to your cart, click View.
On this page, we'll add a section showing all customers who have expressed interest in this product by adding it to their cart. This kind of customer intelligence is crucial for understanding demand patterns.
In your code editor, open nutty > app > models > cart.rb
Around line 7, add a delegate for
:emailso we can refer directly to the customer's username (their email) from the cart:has_many :products, through: :line_items delegate :email, to: :customer def subtotalSave the file, then close it.
In your code editor, open nutty > app > admin > product.rb
Around line 30, add a new row:
row :title row :sku row 'Customers who want to buy this product' do product.carts.map(&:email).join(", ") end row :price row :descriptionNOTE: This elegant line of code demonstrates Rails' power by traversing four models seamlessly. We're going from Product → LineItem → Cart → Customer with minimal code. The
has_many :throughrelationship lets us jump directly from products to carts, while delegation handles the email access. When we callmap(&:email), we're invisibly invoking the customer model through our cart delegate. This is the kind of concise, expressive code that makes Rails so powerful for rapid development.Save the file.
Go back to the browser and reload the product page in the admin. You should now see a new Customers who want to buy… row with your email in it (since you're the only one who's added anything so far)!
If you want to explore this feature further, you can create additional customer accounts and use them to add products to their carts. Their emails will then appear in this business intelligence display, giving you insight into customer demand patterns.
This implementation demonstrates traversing four models with a single line of code. The has_many through relationship allows direct access from products to carts without explicitly invoking line_items.
Optional Bonus: Displaying the Number of Items in the Cart
Finally, let's implement a professional touch: a dynamic cart counter that updates across your entire application. This seemingly small feature significantly improves user experience by providing constant feedback about cart contents.
In the browser, go to localhost:3000
Click on Cart to go to the cart page.
Notice that the Cart icon on the top right has a static 3 next to it. This is currently just part of the mockup design and doesn't reflect actual cart contents—the kind of detail that makes the difference between a prototype and a production-ready application.
There are several approaches to implementing this feature, from simple server-side rendering to complex JavaScript solutions with real-time updates. We'll show you a solid, reliable server-side approach that works consistently across all browsers and connection types.
In your code editor, open nutty > app > controllers > application_controller.rb
Look at the
load_cart_or_redirect_customermethod (around line 7). Currently, this method combines two distinct responsibilities: loading the cart and handling authentication redirects.Following the single responsibility principle, we should separate these concerns. The products controller needs cart loading capability but shouldn't trigger authentication redirects, while cart and line_items controllers need both functionalities.
Cut (Cmd–X) the code shown in bold (around lines 8–11):
private def load_cart_or_redirect_customer if customer_signed_in? current_customer.create_cart if current_customer.cart.nil? @cart = current_customer.cart else redirect_to new_customer_session_path, alert: "Please sign in to access your cart." and return endPaste the cut code starting around line 7:
private if customer_signed_in? current_customer.create_cart if current_customer.cart.nil? @cart = current_customer.cart else def load_cart_or_redirect_customerNow add the code shown in bold:
private def load_cart if customer_signed_in? current_customer.create_cart if current_customer.cart.nil? @cart = current_customer.cart else end def load_cart_or_redirect_customer unless load_cart redirect_to new_customer_session_path, alert: "Please sign in to access your cart." and return endEdit the pasted code as shown and delete the
elsearound line 11:private def load_cart return false unless customer_signed_in? current_customer.create_cart if current_customer.cart.nil? @cart = current_customer.cart endSave the file, then close it.
In your code editor, open nutty > app > controllers > products_controller.rb.
Add the code in bold:
before_action :load_cartThis before_action ensures that cart data is available on all product pages, enabling our cart counter to display accurate information throughout the shopping experience.
Save the file, then close it.
In your code editor, open nutty > app > views > layouts > application.html.erb.
Starting around line 45, edit the code as shown:
<a id="cart" href="/cart"> <span></span>Cart <% if @cart %> <span class="badge"><%= @cart.line_items.count %></span> <% end %> </a>The conditional check ensures the badge only appears when a cart exists, preventing errors on pages where customers aren't signed in.
Save the file, then close it.
In the browser, go to localhost:3000
Try adding more products to the cart to see that the Cart icon at the top updates accordingly. This provides immediate visual feedback to users about their cart status—a crucial UX improvement!
Want an extra challenge? This implementation counts the number of unique line items in the cart. Some e-commerce sites prefer showing the total quantity instead. So if you have 11 Pantone Toothbrushes and 4 Finger Tentacles in your cart, the current implementation shows "2" while the alternative would show "15". Can you modify the code to display total quantity? Hint: You'll need to sum the quantities of all line items rather than just counting the line items themselves.
Leave your code editor and browser open, as well as the server running in Terminal, so we can continue with them in the following exercises. You've just implemented professional-grade e-commerce functionality that forms the foundation of any successful online store.
Controller Refactoring Process
Separate Concerns
Split load_cart_or_redirect_customer into two distinct methods for better code organization
Create Load Cart Method
Implement load_cart method that returns false if customer not signed in, true otherwise
Update Controllers
Add before_action :load_cart to products_controller for cart access across all product pages
Update View Logic
Modify application layout to display dynamic cart count using @cart.line_items.count
The current implementation counts line items. For displaying total quantity (e.g., 11 toothbrushes + 4 tentacles = 15), you would need to sum the quantity field across all line items instead of just counting them.