Home arrow Ruby-on-Rails arrow Page 5 - Finishing a Shopping Cart Implementation
RUBY-ON-RAILS

Finishing a Shopping Cart Implementation


In this second part of a two-part series on implementing a shopping cart for an online bookstore, we'll create a floating cart that will show its contents to the user, and set up its capabilities. This article is excerpted from chapter five of the book Practical Rails Projects, written by Eldon Alameda (Apress; ISBN: 1590597818).

Author Info:
By: Apress Publishing
Rating: 5 stars5 stars5 stars5 stars5 stars / 11
June 04, 2010
TABLE OF CONTENTS:
  1. · Finishing a Shopping Cart Implementation
  2. · Implementing the Add Items to the Cart User Story
  3. · Ajax’ing It
  4. · Drag-and-Drop
  5. · Implementing the Remove Items from the Cart User Story
  6. · Implementing the Clear the Cart User Story

print this article
SEARCH DEVARTICLES

Finishing a Shopping Cart Implementation - Implementing the Remove Items from the Cart User Story
(Page 5 of 6 )

Removing items from the shopping cart is pretty much the inverse action of adding an item. Therefore, we can duplicate much of the code we did for the adding part, with some slight modifications. First, we extend the functional test case in test/functional/cart_controller_test.rbto also test for item removal:  

def test_removing
 
post :add, :id => 4
 
assert_equal [Book.find(4)], Cart.find(@request.session[:cart_id]).books

  post :remove, :id => 4
 
assert_equal [], Cart.find(@request.session[:cart_id]).books
end

def test_removing_with_xhr
 
post :add, :id => 4
 
assert_equal [Book.find(4)], Cart.find(@request.session[:cart_id]).books

  xhr :post, :remove, :id => 4
 
assert_equal [], Cart.find(@request.session[:cart_id]).books
end

Just as with the addition, the first test checks that the removal of items works correctly with the traditional way and the second one tests the Ajax functionality.

We start implementing the removal functionality by adding aremovemethod to theCart class inapp/models/cart.rb:

class Cart < ActiveRecord::Base
 
has_many :cart_items
 
has_many :books, :through => :cart_items

  def add(book_id)
    items = cart_items.find_all_by_book_id(book_id)
    book = Book.find(book_id)

    if items.size < 1
     
ci = cart_items.create(:book_id => book_id,
                            
:amount => 1,
                            
:price => book.price)
   
else
     
ci = items.first
     
ci.update_attribute(:amount, ci.amount + 1)
   
end
    ci
  end

  def remove(book_id)
    ci = cart_items.find_by_book_id(book_id)

    if ci.amount > 1 
      ci.update_attribute(:amount, ci.amount - 1)
    else
     
CartItem.destroy(ci.id)
    end
    return ci
  end

  def total
    cart_items.inject(0) {|sum, n| n.price * n.amount + sum}
  end
end

Theremovemethod first uses a magicalfind_by_attribute_namefinder method to find the cart item in the current cart that holds a certain title. Then the method uses theupdate_attributemethod to make theamountattribute one smaller, except if the amount was already one (or less, but that shouldn’t be possible). In that case, the entire cart item is deleted from the cart.

We continue the copying and slightly modifying path inapp/controllers/cart_controller.rb:

class CartController < ApplicationController
  layout "catalog"
  before_filter :initialize_cart

  def add
    @book = Book.find(params[:id])

    if request.xhr?
      @item = @cart.add(params[:id]) 
      flash.now[:cart_notice] = "Added <em>#{@item.book.title}</em>"
      render :action => "add_with_ajax"
    elsif request.post?
      @item = @cart.add(params[:id])
      flash[:cart_notice] = "Added <em>#{@item.book.title}</em>" 
      redirect_to:controller => "catalog"
    else
      render
    end
  end

  def remove
    @book = Book.find(params[:id])

    if request.xhr?
      @item = @cart.remove(params[:id])
      flash.now[:cart_notice] = "Removed 1 <em>#{@item.book.title}</em>"
      render :action => "remove_with_ajax"
   
elsif request.post?
      @item = @cart.remove(params[:id])
      flash[:cart_notice] = "Removed 1 <em>#{@item.book.title}</em>"
      redirect_to :controller => "catalog"
    else
      render
    end
  end
end

We do the same kind of request-type sniffing here as with theaddaction. The method is almost a duplicate of theaddaction, except that this time when Ajax is used, we render theremove_with_ajaxtemplate, and of course, we call theCart#removemethod instead ofadd.

We now need to create the corresponding views, starting withapp/views/cart/ remove_with_ajax.rjs:

page.insert_html :top, "shopping_cart", :partial => "cart/cart_notice"
if @cart.books.include?(@book) 
  page.replace_html "cart_item_#{@book.id}", :partial => "cart/item"
  page.visual_effect :highlight, "cart_item_#{@book.id}", :duration => 3
else
  page.visual_effect :fade, "cart_item_#{@book.id}", :duration => 1.5
end
page.replace_html "cart_total", "<strong>Total: $#{@cart.total}</strong>" page.visual_effect :fade, 'cart_notice', :duration => 3

This time, the view is a bit more involved. We don’t replace the whole shopping cart with one updated partial, but instead modify its individual objects, as follows:

  1. We add the notice element to the top of the cart.
  2. If there are still items representing the book from which we just removed one item, we update that element to show the correct amount and highlight the element. If the item was the last one of the given book, we instead fade out and finally remove the whole list item from the cart. 
     
  3. We update the subtotal to match the current state of the cart. 
     
  4. Finally, we slowly fade out the notice that we added in the beginning of the template.

Next, we create the view for non-Ajax operation,cart/remove.rhtml, which will be used as the confirmation page of theremoveaction, just as we did withadd.

<strong>Please confirm removing one <em><%= @book.title %></em>
from your shopping cart.</strong>
<%= button_to "Confirm", :action => "remove", :id => params[:id] %>

We already created a helper for the remove link, so all we need to do to enable the functionality is to call that helper. We do that in thecart/_item.rhtmlpartial that we’re using to show every item in the shopping cart:

<%= link_to item.book.title, :action => "show",
     
:controller => "catalog", :id => item.book.id %>
<%= pluralize(item.amount, "pc", "pcs") %>, $<%= item.price * item.amount %>

(<%= remove_book_link("-", item.book) %>)

Figure 5-3 shows how the removed element is faded when the remove link next to it is clicked.


Figure 5-3.  Removing an item from the cart


blog comments powered by Disqus
RUBY-ON-RAILS ARTICLES

- Ruby-on-Rails Faces Second Security Flaw in ...
- Ruby 2.0 Prepped for February 2013 Release
- Why LinkedIn Switched from Ruby on Rails
- Adding Style with Action Pack
- Handling HTML in Templates with Action Pack
- Filters, Controllers and Helpers in Action P...
- Action Pack and Controller Filters
- Action Pack Categories and Events
- Logging Out, Events and Templates with Actio...
- Action Pack Sessions and Architecture
- More on Action Pack Partial Templates
- Action Pack Partial Templates
- Displaying Error Messages with the Action Pa...
- Action Pack Request Parameters
- Creating an Action Pack Registration Form

Watch our Tech Videos 
Dev Articles Forums 
 RSS  Articles
 RSS  Forums
 RSS  All Feeds
Write For Us 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Contact Us 
Site Map 
Privacy Policy 
Support 

Developer Shed Affiliates

 




© 2003-2018 by Developer Shed. All rights reserved. DS Cluster - Follow our Sitemap
Popular Web Development Topics
All Web Development Tutorials