Rader on Rails

Dispatches from my web development journey.

A Shopping Cart for Your Web App

This was posted on the DevShop blog on July 25

If you’re looking to build an e-commerce web application, there is no shortage of tools you can use, from Spree to Shopify. However, it’s not too difficult to build your own – which is something we were tasked with over the past week.

In this post, I’ll cover creating a shopping cart that will keep track of products and their quantity and allow users to remove items.

Having read Agile Web Development with Rails 4, I decided to use that book’s shopping cart implementation. I’d encourage you to go through the book, especially if you’re new to Ruby on Rails, but I’ll give you an outline of everything required to make a robust shopping cart for your web application.

At least four models are necessary for an e-commerce site: Products, Carts, Orders and Ordered Items (which is a join table between products and orders and products and carts).

Creating and retrieving our cart

Because setting a cart is functionality we’ll need to access among different controllers, a great place to put it is inside the concerns folder provided by default in Rails 4. In here, you can create a module where you retrieve the right cart object based on the cart ID stored in the session, or you create the cart if it doesn’t yet exist.

app/controllers/concerns/current_cart.rb
1
2
3
4
5
6
7
8
9
10
11
12
module CurrentCart
  extend ActiveSupport::Concern

    private

      def set_cart
        @cart = Cart.find(session[:cart_id])
      rescue ActiveRecord::RecordNotFound
        @cart = Cart.create
        session[:cart_id] = @cart.id
      end
end

The astute reader may look at this and say, wait, what happens when a customer creates a cart but then abandons it? That’s a great question, and there are a number of things you could do to handle this scenario. You could create a cron job that checks how old the cart is and determine an expiration date, upon which the job destroys those cart records. On the other hand, you may not want to destroy the carts, especially if you add users later on and associate carts with users. Abandoned carts will help you determine which users aren’t checking out, which can be useful information.

Adding products to our cart

A good time to call our set_cart method would be at the moment a customer decides to add an item to their cart, or in terms of our web application’s functionality, right before creating an Ordered Item record:

ordered_items_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class OrderedItemsController < ApplicationController
  include CurrentCart
  before_action :set_cart, only: [:create, :update, :destroy]

  def create
    @cart.add_product(params)

    if @cart.save
      render partial: 'carts/shopping_bag', locals: { order: @cart }
    else
      flash[:error] = 'There was a problem adding this item to your shopping bag.'
      redirect :back
    end
  end
end

To include the current cart functionality, all you need to do is include CurrentCart in the controller. Then you can use a before_action filter to specify which actions need to retrieve or create a cart.

You probably also noticed the add_product() method on cart. This is a special method we create on the cart model that allows us to create initial Ordered Items records, as well as keep track of the quantity of the same product. If we simply used @cart.ordered_items.build, we’d be creating individual records of the same product each time.

Let’s take a look at the add_product() method:

cart.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
class Cart < ActiveRecord::Base
  def add_product(product_args)
    current_item = ordered_items.find_by(product_id: product_args[:product_id])

    if current_item
      current_item.quantity += product_args[:quantity].to_i
      current_item.save
    else
      current_item = ordered_items.build(product_args)
    end
      current_item
  end
end

This method differed a bit from the one in Agile Web Development. In our application, users could choose the quantity of items they wanted to add, rather than adding them one at a time. So a quantity parameter was always being passed to the controller action inside of product_args.

add_product() checks for the existence of the Ordered Item record in the cart. If we have the same product in our cart, it simply updates the quantity and saves the record. Otherwise, we build the relationship and return the item.

Adding and removing products

Like most e-commerce sites, we built a page where customers can look at their cart and update the quantity of items or remove them entirely. This calls for two more actions in our Ordered Items controller: update and destroy:

ordered_items_controller.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class OrderedItemsController < ApplicationController
  include CurrentOrder
  before_action :set_cart, only: [:create, :update, :destroy]
  before_action :set_ordered_item, only: [:update, :destroy]

  # ...

  def update
    @ordered_item.update(quantity: params[:quantity]) if @ordered_item

    if @ordered_item.save
      render json: { itemPrice: @ordered_item.quantity * @ordered_item.product.price, subtotal: @cart.total_price }
    else
      flash.now[:error] = 'There was a problem updating your shopping bag.'
    end
  end

  def destroy
    @ordered_item.destroy
    render json: { order_total: "$%.2f" % @cart.total_price }
  end

  private

    def set_ordered_item
      @ordered_item = @cart.ordered_items.find_by(id: params[:item_id])
    end
end

Now we have everything we need to keep track of items in a customer’s cart.

In my next post, I’ll cover how we handled check out and processing orders with ActiveMerchant and Authorize.net.

Comments