There's more than one way to build a cart
You know when you want to buy something online and you click a button to "add to basket" or something along those lines? I've been thinking about this process pretty extensively lately, and I've built a number of different carts. I'd like to think that now I know a few advantages and disadvantages to some different styles of cart. Consider this post a primer.
The Session Hash Cart
This is a very simple way to build a shopping cart and utilizes the Rails session hash. The idea is that you can add a new hash to the session session[:cart] = {}
and you can then add items by id and quantity. An add to cart method might look something like this:
def add_to_cart(item, quantity)
session[:cart][item.id] = quantity
end
In my opinion, this falls into the category of "cute" programming. It utilizes a part of rails in an interesting and different way, but isn't my recommended shopping cart technique.
This method is fast, clean, and simple. Depending on your requirements, it might be a good solution.
The cons outweigh the pros. The fact that this technique relies on the session hash means that it will get reset frequently. Many users will expect a cart to persist between visits and across devices. You will also still need a separate order class.
The verdict for me is that this is interesting, but not actually useful.
The Standalone Cart Model
This technique requires building a new model for your cart. It would probably be tied to a particular user or current_user. You could then add items and quantities. If necessary, you could add and access other cart attributes like the price at the time the suer added an item to the cart or the time something was added or removed.
class Cart < ActiveRecord::Base
belongs_to :user
has many :items
def add_item(item)
self.items << item
end
def remove_item(item)
self.items.delete(item)
end
end
This method is very good for separation of concerns, but is not very dry. This sort of cart overcomes the problems of the last method, in that authenticated users can always retrieve their carts. Amazon has trained us to expect our carts to behave a certain way.
The problem I have with this kind of cart is that it isn't DRY once you add in some kind of order model as a part of your checkout process. An order will necessarily be pretty similar to a cart with one difference.
The Order-As-Cart
This is my favorite method. You basically build an order model, add items to an order, and use a state machine to keep track of the whole business. It's clean, DRY, and slick. Here's an example of how the model might look:
class Order < ActiveRecord::Base
include AASM
belongs_to :user
has_many :order_items
has_many :items, through: :order_items
aasm do
state :cart, :initial => true
state :ordered
state :completed
event :order do
transitions :from => :basket, :to => :ordered
end
event :complete do
transitions :from => :ordered, :to => :complete
end
end
def add_item(item)
self.items << item
end
def remove_item(item)
self.items.delete(item)
end
This example uses the excellent aasm gem to handle the state machine duties, but those can be the topic of another post. The advantage is that this one module takes care of both cart and order duties. When the user checks out, her cart simply changes from a cart to an order. This cuts down on repeated code and is generally simpler.
This type of solution would also be very easy to build without a state machine. You would just add a status column to your order model and then recreate the needed state-change methods. I would also recommend adding in scopes to easily access orders in different states.
Extenuating Circumstances
Sometimes the order-as-cart doesn't make the best solution. I recently worked on a project where users needed to be able to make a contribution to a loan. That is very different than a normal item/cart scenario so we build a "cart" that could handle those specific needs. As usual with programming, figuring out your problem is half the problem