Agile Web Development with Rails, Edition 4

11.1 Iteration F1: Moving the Cart 10.3 Iteration E3: Finishing the Cart

10.4 Playtime

Once again, get the tests working, and add tests for the smarter cart.

See that the tests fail.

rake test
[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message.
[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message.
Loaded suite /home/rubys/.rvm/gems/ruby-1.9.2-p320/gems/rake-10.1.1/lib/rake/rake_test_loader
Started
 
ProductTest:
     PASS image url (0.26s) 
     PASS product attributes must not be empty (0.00s) 
     PASS product is not valid without a unique title (0.00s) 
     PASS product is not valid without a unique title - i18n (0.00s) 
     PASS product price must be positive (0.00s) 
 
Finished in 0.278930 seconds.
 
5 tests, 23 assertions, 0 failures, 0 errors, 0 skips
[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message.
Loaded suite /home/rubys/.rvm/gems/ruby-1.9.2-p320/gems/rake-10.1.1/lib/rake/rake_test_loader
Started
 
CartsControllerTest:
     PASS should create cart (0.19s) 
     FAIL should destroy cart (0.04s) 
          "Cart.count" didn't change by -1.
<1> expected but was
<2>.
          /home/rubys/git/rails/activesupport/lib/active_support/testing/assertions.rb:60:in `block in assert_difference'
 
     PASS should get edit (0.06s) 
     PASS should get index (0.01s) 
     PASS should get new (0.01s) 
     PASS should show cart (0.01s) 
     PASS should update cart (0.01s) 
 
LineItemsControllerTest:
     PASS should create line item (0.03s) 
     PASS should destroy line item (0.01s) 
     PASS should get edit (0.04s) 
     PASS should get index (0.01s) 
     PASS should get new (0.01s) 
     PASS should show line item (0.01s) 
     PASS should update line item (0.01s) 
 
ProductsControllerTest:
     PASS should create product (0.02s) 
     PASS should destroy product (0.01s) 
     PASS should get edit (0.01s) 
     PASS should get index (0.04s) 
     PASS should get new (0.01s) 
     PASS should show product (0.01s) 
     PASS should update product (0.01s) 
 
StoreControllerTest:
     PASS should get index (0.05s) 
 
Finished in 0.581573 seconds.
 
22 tests, 34 assertions, 1 failures, 0 errors, 0 skips
Errors running test:functionals!

Substitute names of products and carts for numbers

edit test/fixtures/line_items.yml
# Read about fixtures at
# http://api.rubyonrails.org/classes/ActiveRecord/Fixtures.html
 
one:
  product: ruby
  cart: one
 
two:
  product: ruby
  cart: one

Update expected target of redirect: Cart#destroy.

edit test/functional/carts_controller_test.rb
  test "should destroy cart" do
    assert_difference('Cart.count', -1) do
      session[:cart_id] = @cart.id
      delete :destroy, id: @cart.to_param
    end
 
    assert_redirected_to store_path
  end

Test both unique and duplicate products.

edit test/unit/cart_test.rb
require 'test_helper'
 
class CartTest < ActiveSupport::TestCase
  test "add unique products" do
    cart = Cart.create
    book_one = products(:one)
    book_two  = products(:two)
    cart.add_product(book_one.id).save!
    cart.add_product(book_two.id).save!
    assert_equal 2, cart.line_items.size
    assert_equal book_one.price + book_two.price, cart.total_price
  end
  
  test "add_duplicate_product" do
    cart = Cart.create
    ruby_book = products(:ruby)
    cart.add_product(ruby_book.id).save!
    cart.add_product(ruby_book.id).save!
    assert_equal 2*book_one.price, cart.total_price
    assert_equal 1, cart.line_items.size
    assert_equal 2, cart.line_items[0].quantity
  end 
end
ruby -I test test/unit/cart_test.rb
[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message.
Loaded suite test/unit/cart_test
Started
 
CartTest:
    ERROR add duplicate product (0.28s) 
          NameError: undefined local variable or method `book_one' for #<CartTest:0x00000003bc1300>
          test/unit/cart_test.rb:24:in `block in <class:CartTest>'
 
     PASS add unique products (0.03s) 
 
Finished in 0.321148 seconds.
 
2 tests, 2 assertions, 0 failures, 1 errors, 0 skips

Refactor.

edit test/unit/cart_test.rb
require 'test_helper'
 
class CartTest < ActiveSupport::TestCase
  def setup
    @cart  = Cart.create
    @book_one = products(:ruby)
    @book_two  = products(:two)
  end
  
  test "add unique products" do
    @cart.add_product(@book_one.id).save!
    @cart.add_product(@book_two.id).save!
    assert_equal 2, @cart.line_items.size
    assert_equal @book_one.price + @book_two.price, @cart.total_price
  end
 
  test "add duplicate product" do
    @cart.add_product(@book_one.id).save!
    @cart.add_product(@book_one.id).save!
    assert_equal 2*@book_one.price, @cart.total_price
    assert_equal 1, @cart.line_items.size
    assert_equal 2, @cart.line_items[0].quantity
  end 
end
ruby -I test test/unit/cart_test.rb
[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message.
Loaded suite test/unit/cart_test
Started
 
CartTest:
     PASS add duplicate product (0.29s) 
     PASS add unique products (0.01s) 
 
Finished in 0.304555 seconds.
 
2 tests, 5 assertions, 0 failures, 0 errors, 0 skips

Verify that the tests pass.

rake test
[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message.
[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message.
Loaded suite /home/rubys/.rvm/gems/ruby-1.9.2-p320/gems/rake-10.1.1/lib/rake/rake_test_loader
Started
 
CartTest:
     PASS add duplicate product (0.23s) 
     PASS add unique products (0.01s) 
 
ProductTest:
     PASS image url (0.04s) 
     PASS product attributes must not be empty (0.00s) 
     PASS product is not valid without a unique title (0.00s) 
     PASS product is not valid without a unique title - i18n (0.00s) 
     PASS product price must be positive (0.00s) 
 
Finished in 0.288348 seconds.
 
7 tests, 28 assertions, 0 failures, 0 errors, 0 skips
[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message.
Loaded suite /home/rubys/.rvm/gems/ruby-1.9.2-p320/gems/rake-10.1.1/lib/rake/rake_test_loader
Started
 
CartsControllerTest:
     PASS should create cart (0.23s) 
     PASS should destroy cart (0.07s) 
     PASS should get edit (0.04s) 
     PASS should get index (0.01s) 
     PASS should get new (0.01s) 
     PASS should show cart (0.02s) 
     PASS should update cart (0.01s) 
 
LineItemsControllerTest:
     PASS should create line item (0.01s) 
     PASS should destroy line item (0.00s) 
     PASS should get edit (0.03s) 
     PASS should get index (0.01s) 
     PASS should get new (0.01s) 
     PASS should show line item (0.01s) 
     PASS should update line item (0.01s) 
 
ProductsControllerTest:
     PASS should create product (0.02s) 
     PASS should destroy product (0.01s) 
     PASS should get edit (0.01s) 
     PASS should get index (0.04s) 
     PASS should get new (0.01s) 
     PASS should show product (0.01s) 
     PASS should update product (0.01s) 
 
StoreControllerTest:
     PASS should get index (0.05s) 
 
Finished in 0.594886 seconds.
 
22 tests, 35 assertions, 0 failures, 0 errors, 0 skips

Add a test ensuring that non-empty carts can't be deleted.

edit test/functional/products_controller_test.rb
  test "can't delete product in cart" do
    assert_difference('Product.count', 0) do
      delete :destroy, id: products(:ruby).to_param
    end
 
    assert_redirected_to products_path
  end
 
  test "should destroy product" do
    assert_difference('Product.count', -1) do
      delete :destroy, id: @product.to_param
    end
 
    assert_redirected_to products_path
  end

Now the tests should pass.

rake test
[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message.
[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message.
Loaded suite /home/rubys/.rvm/gems/ruby-1.9.2-p320/gems/rake-10.1.1/lib/rake/rake_test_loader
Started
 
CartTest:
     PASS add duplicate product (0.30s) 
     PASS add unique products (0.01s) 
 
ProductTest:
     PASS image url (0.04s) 
     PASS product attributes must not be empty (0.00s) 
     PASS product is not valid without a unique title (0.00s) 
     PASS product is not valid without a unique title - i18n (0.00s) 
     PASS product price must be positive (0.00s) 
 
Finished in 0.366767 seconds.
 
7 tests, 28 assertions, 0 failures, 0 errors, 0 skips
[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message.
Loaded suite /home/rubys/.rvm/gems/ruby-1.9.2-p320/gems/rake-10.1.1/lib/rake/rake_test_loader
Started
 
CartsControllerTest:
     PASS should create cart (0.14s) 
     PASS should destroy cart (0.08s) 
     PASS should get edit (0.04s) 
     PASS should get index (0.01s) 
     PASS should get new (0.01s) 
     PASS should show cart (0.02s) 
     PASS should update cart (0.01s) 
 
LineItemsControllerTest:
     PASS should create line item (0.01s) 
     PASS should destroy line item (0.01s) 
     PASS should get edit (0.04s) 
     PASS should get index (0.01s) 
     PASS should get new (0.01s) 
     PASS should show line item (0.01s) 
     PASS should update line item (0.01s) 
 
ProductsControllerTest:
     PASS can't delete product in cart (0.01s) 
     PASS should create product (0.02s) 
     PASS should destroy product (0.01s) 
     PASS should get edit (0.01s) 
     PASS should get index (0.04s) 
     PASS should get new (0.01s) 
     PASS should show product (0.01s) 
     PASS should update product (0.01s) 
 
StoreControllerTest:
     PASS should get index (0.05s) 
 
Finished in 0.549422 seconds.
 
23 tests, 37 assertions, 0 failures, 0 errors, 0 skips

Add price to line item

rails generate migration add_price_to_line_item price:decimal
[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message.
      invoke  active_record
      create    db/migrate/20140204195921_add_price_to_line_item.rb
edit db/migrate/20140204195921_add_price_to_line_item.rb
class AddPriceToLineItem < ActiveRecord::Migration
  def change
    add_column :line_items, :price, :decimal
    LineItem.all.each do |li|
      li.price = li.product.price
    end
  end
end
rake db:migrate
mv 20140204195921_add_price_to_line_item.rb 20140204000006_add_price_to_line_item.rb
[deprecated] I18n.enforce_available_locales will default to true in the future. If you really want to skip validation of your locale you can set I18n.enforce_available_locales = false to avoid this message.
==  AddPriceToLineItem: migrating =============================================
-- add_column(:line_items, :price, :decimal)
   -> 0.0005s
==  AddPriceToLineItem: migrated (0.1014s) ====================================
 
edit app/models/cart.rb
class Cart < ActiveRecord::Base
  has_many :line_items, dependent: :destroy
 
  def add_product(product_id)
    current_item = line_items.find_by_product_id(product_id)
    if current_item
      current_item.quantity += 1
    else
      current_item = line_items.build(product_id: product_id)
      current_item.price = current_item.product.price
    end
    current_item
  end
 
  def total_price
    line_items.to_a.sum { |item| item.total_price }
  end
end
git commit -a -m "Adding a Cart"
[master 9ceb605] Adding a Cart
 5 files changed, 72 insertions(+), 11 deletions(-)
git tag iteration-d

11.1 Iteration F1: Moving the Cart 10.3 Iteration E3: Finishing the Cart