This post is part of a series of reviews on the book Agile Web Development With Rails. Check out the Introduction post for a full table of contents along with some initial notes.

Every e-commerce application needs to have a cart feature, and that’s what this task is all about.

First we need to understand how Rails manages sessions. Basically, you access sessions by using a collection similar to a hash called session, so in our example we’ll use session[:cart]. Although it’s not being used anymore, the book suggests that we create a database to store sessions, so, as obedient students as we are, we’ll keep following the book’s instructions, but keep in mind that this is NOT the way it’s done nowadays. At your application directory, execute:

rake db:sessions:create

rake db:migrate

Restart your server by hitting control+c on the command prompt and typing in ruby script/server (as you should know). Now all sessions are being stored at your database.

Getting down to the application code, we’ll add a method to actually use the concepts we just learned, so, editing your store controller file:

private

def find_cart
  session[:cart] ||= Cart.new
end 

This is a pretty cool way to do it. By using the conditional assignment operator, we’re telling Rails to create a new cart and assign it to the session collection case he doesn’t find one, or return that cart case he does. Notice the method is private, making it unaccessible as an action to the controller.

So, we’re now storing our cart in a session. Just as a notice, serious applications do not store their cart applications on sessions due to security matters, they use a database for that, which is interesting because we are, in fact, using a database to store sessions, which comes down to the same thing, but it doesn’t because as said on the beggining, storing sessions in databases is not appropriate (we sure are rogue developers, aren’t we).

Moving on with what matters, we told our store controller to create a new cart case one isn’t found in a session, that’s all good except the fact that there isn’t a cart model to create, so guess what we’ll be doing next:

class Cart
  attr_reader :items ֒

  def initialize
    @items = []
  end

  def add_product(product)
    @items << product
  end

end

This is quite simple, our cart is nothing more than an array of items, which is initialized as soon as the object is created and added to when the add_product method is called with a product as its parameter.

As you might (should) remember from Task B, we added an ‘Add to Cart’ button which links to an action named add_to_cart. As promised then, let’s add that function to our store controller:

def add_to_cart
  @cart = find_cart
  product = Product.find(params[:id])
  @cart.add_product(product)
end

As defined earlier, find_cart gets the cart associated with the session[:cart], and as also defined earlier, the Cart model has an add_product method which adds a product to it (session[:cart] is a Cart object, which is an array of items, remember?), so what add_cart basically does is add a product to that array, and he knows which product to add by searching through the products table to find which one has the same id as the one passed to the URL (the button_to helper in the store index view file passes the product id as a parameter).

If you tried adding a product to your cart, you should have come up with a ‘template missing’ error message, because that’s what makes sense since we didn’t define a view file to the add_to_cart controller:

<h2>Your Pragmatic Cart</h2>
<ul>
<% for item in @cart.items %>
<li><%=h item.title %></li>
<% end %>
</ul>

That’s a pretty basic cart right there, but we want something prettier and more intelligent (like me, haha), so let’s make a v2.0 of what we just did. First thing we need to do is create a new model named CartItem, which will represent a specific item of one specific product, that way we can provide a better view of how many items of each product a client is ordering:

class CartItem
  attr_reader :product, :quantity

  def initialize(product)
    @product = product
    @quantity = 1
  end

  def increment_quantity
    @quantity += 1
  end

  def title
    @product.title
  end

  def price
    @product.price * @quantity
  end

end

Now we need to change the way that a product is added, since we’re no longer adding a product itself, but a specific item of a product, so this is what we should have after modifying the add_product method on our cart model file:

def add_product(product)
  current_item = @items.find {|item| item.product == product}
  if current_item
    current_item.increment_quantity
  else
    @items << CartItem.new(product)
  end
end
This code basically checks to see if the product being added already exists, if it does, then its quantity is bumped, if not, a new item is created. Now that we have the logic all setup, it’s time to edit the view file to our smarter approach:
<h2>Your Pragmatic Cart</h2>
<ul>
<% for item in @cart.items %>
<li><%= item.quantity %> &times; <%=h item.title %></li>
<% end %>
</ul>
Since we changed the core way that the session was dealing with the cart (it went from products to product items), the cart will no longer work and print out errors, but that’s what’s supposed to happen since its storing products instead of items. To fix that, a simple command will do:
rake db:sessions:clear
Now our session table is all cleared up, so now we can add fresh data to it. This is the sort of thing that we wouldn’t need to do if sessions weren’t being stored in a database, we’d simply need to clean browser cookies and we’d be all set, but lets excuse this for now and continue with our work. If you go back to the store index page and try adding a cart, you’ll see the page now cleverly showing the name of the product along with how much of that product you ordered (try refreshing the page to see that number getting bumped).
Heading to our next problem: error handling. Common programming sense tells us that the client will do the best it can to screw up your application, and we need to be ready for that. If you take a look at the URL generated after clicking the ‘Add to Cart’ button, you’ll notice there’s a pattern: controller/action/product_id, and if instead of adding an id that actually exists you add something like ‘woot’, Rails will print out a huge error screen. Clients don’t like that, and hackers can take advantage of that screen to get a lot of information about your application, so we need to shield ourselves against that.
Before we get to the code, let’s first undestand the concept of flash data that Rails supplies us with.  Flash data is basically a hash-like collection that can be stored until the next request, which is perfect for what we’re about to use it for, which is error messages, we don’t need them to be stored in a database nor is it needed to be stored in a session, all we need it for is to last long enough to show a message somewhere and then be gone. If it’s not clear yet, then you’ll get it when we code it. Our goal at the moment is to shield ourselves against url modifiers on the store controller, specifically on the add_to_cart method:
def add_to_cart
  product = Product.find(params[:id])
  rescue ActiveRecord::RecordNotFound
    logger.error("Attempt to access invalid product #{params[:id]}")
    redirect_to_index("Invalid Product")
  else
    @cart = find_cart
    @cart.add_product(product)
end

After handling the RecordNotFound exception, which comes up if Rails fails to find the product by it’s ID, an error message is attached to the log file (development.log file in logs directory) and a redirect method is called, which we need to define (still on the store controller):

private

def redirect_to_index(msg)
  flash[:notice] = msg
  redirect_to :action=>:index
end
It basically adds a message that’s passed as a parameter to the flash collection and redirects the user to the index page. Rewinding what we just did, three things will happen if the user types a different product to the add_to_cart method:
1. An error will be attached to the log file with a specific message.
2. A flash notice will be created.
3. The user will get redirected to the main index page.
Now we need that flash[:notice] to actually appear somewhere, but where? If you remember from Task B (you should), Rails supplies us with the possibility of creating a layout file for each controller we have, and we did create a layout for our store controller, which is where our cart is at, so that’s the perfect place for a notice to show up:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd" >
<html>
<head>
<title>Pragprog Books Online Store</title>
<%= stylesheet_link_tag "tutorial_rails" , :media => "all" %>
</head>
<body id="store">
<div id="banner">
<%= image_tag("logo.png" ) %>
<%= @page_title || "Pragmatic Bookshelf" %>
</div>
<div id="columns">
<div id="side">
<a href="http://www....">Home</a><br />
<a href="http://www..../faq">Questions</a><br />
<a href="http://www..../news">News</a><br />
<a href="http://www..../contact">Contact</a><br />
</div>
<div id="main">
<% if flash[:notice] -%>
<div id="notice"> <%=flash[:notice]%> </div>
<%  end -%>
<%= yield :layout %>
</div>
</div>
</body> </html>
The new code showing the flash content is in lines 21-23. Don’t worry about the CSS, it should already be all set if you copied the whole CSS code from Task B. To finish off our super mega cart with a grand finale, let’s add two more things: an empty_cart method that will basically clean up the session and a total_price method that will tell us how much is being spent on the cart.
Since the empty_cart will be an action triggered by a button we’ll later create, it goes in the store controller file:
def empty_cart
  session[:cart] = nil
  redirect_to_index('Your cart is currently empty')
end
Notice we used the redirect_to_index function we defined earlier (it adds the message to the flash[:notice] collection and redirects the user to the index page), now to the total_price method that should be defined on the cart model file, since its a method regarding the cart logic:
def total_price
  @items.sum {|item| item.price}
end
Now that we’re all set, lets end it by editing the add_to_cart view file and apply all the new intelligence we gave to it:
<div class="cart-title">Your Cart</div>
<table>
<% for cart_item in @cart.items %>
<tr>
<td><%= cart_item.quantity %>&times;</td>
<td><%= h(cart_item.title) %></td>
<td class="item-price"><%= number_to_currency(cart_item.price) %></td>
</tr>
<% end %>
<tr class="total-line">
<td colspan="2">Total</td>
<td class="total-cell"><%= number_to_currency(@cart.total_price) %></td>
</tr>
</table>
<%= button_to "Empty cart" , :action => :empty_cart %>
It’s now all organized with a table, showing the product quantity and name, the total price and a nice little button that links to our empty_cart function. We now have a nice looking and pretty intelligent cart, but there is still more to it on the next task, see you then!