Delve into this comprehensive tutorial on Ruby on Rails, covering topics such as calculating the subtotal, using delegates, fixing the order summary, and displaying the number of items in a cart.
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.
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 will work on getting product prices to display accurately. Each item in the cart should have a subtotal that reflects the quantity. We also need to make the order summary functional.
-
If you completed the previous exercises, you can skip the following sidebar. We recommend you finish the previous exercises (8A-9C) before starting this one. If you haven’t finished them, do the following sidebar.
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
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 9C
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.
Displaying an Accurate Product Price with Subtotals
-
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 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>
-
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
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.
Delegates
-
In your code editor, open nutty > app > models > line_item.rb
Wouldn’t it be handy if we could simply call
line_item.price
? Remember thatline_item
doesn’t have its own price field; the product always provides it. Wouldn’t it be nice if we didn’t have to add a separate price field to our model? We don’t have to, thanks to delegation. To delegate is a way of having related models pass their methods down to each other. -
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 end
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, showing that things still work. As you can see, delegates are very handy and help you write nice, tidy code. We can delegate other methods as well.
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: :product
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 see that it still looks good.
Making the Order Summary Functional
Next, let’s get the Order Summary section working. For now, we’ll just set Total equal to Subtotal. That way in the future, when we add Shipping and Taxes, we’ll already have a total
method set up and the only code change we will need to make will be the total
method in our cart model file.
In your code editor, open nutty > app > models > cart.rb
-
Add the following bold code to add
subtotal
andtotal
methods: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
subtotal
method, we’re summing up the subtotal of all our line items. (The ampersand means apply the method calledsubtotal
to each line item.) - We set up a separate method for
total
. This gives us something to build off of if we need to add more later.
- 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!
Optional Bonus: Adding a Customers Who Want to Buy This Product Field
Next, let’s update the back-end of our site to reflect some of the things we’ve been doing on the front end.
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’d like to see a list of all the customers here who have added this product to their cart.
In your code editor, open nutty > app > models > cart.rb
-
Around line 7, add a delegate for
:email
so 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 subtotal
Save 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 :description
NOTE: The cool thing about this bit of code is that it’s going through four models with a single line. We’re only referring to two models explicitly. Thanks to the
has_many, through
relationship, we can go directly from products to carts without invokingline_items
. When we callmap
by email, we’re invisibly invoking the customer model. Many-to-many relationships and delegates allow you to write very concise, easy-to-work-with code. 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 play with this further, you can sign up for other user accounts and use them to add products to the cart. Then their emails should be displayed as wanting to buy those products on the admin page.
Optional Bonus: Displaying the Number of Items in the Cart
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 3 next to it. This is not an accurate reflection of the items in the cart, just a part of the mockup design.
There are a number of ways one could go about implementing this feature. We will show you one approach. Feel free to experiment and find alternate methods on your own.
In your code editor, open nutty > app > controllers > application_controller.rb
-
Look at the
load_cart_or_redirect_customer
method (around line 7). It combines two functionalities into one.We need two separate methods: one that loads the cart and one that redirects the customer. In the products_controller, we only want to try to load the cart, not redirect the customer. We do need to redirect the customer in the cart_controller and the line_items_controller.
-
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 end
-
Paste 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_customer
-
Now 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 end end
-
Edit the pasted code as shown and delete the
else
around 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 end
Save the file, then close it.
In your code editor, open nutty > app > controllers > products_controller.rb.
-
Add the code in bold:
before_action :load_cart
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>
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. Awesome!
Want an extra challenge? This implementation counts the number of line items in the cart. Some people would prefer that this badge show the total quantity instead. So, as implemented here, if we have two items in our cart, the badge will read “2” regardless of their quantity. Supposed we have 11 Pantone Toothbrushes and 4 Finger Tentacles in our cart. Can you get the badge to say “15”?
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.