Agile Web Development with Rails, Edition 5

10.2 Iteration E2: Handling Errors 9.4 Playtime

10.1 Iteration E1: Creating a Smarter Cart

Expected exactly 3 elements matching "main li", found 6.
<3> expected but was
<6>.

Traceback:
  /home/rubys/git/awdwr/edition4/checkdepot.rb:201:in `block in <class:DepotTest>'

Change the cart to track the quantity of each product.

Add a few products to the order.

get /
The Pragmatic Bookshelf

Your Pragmatic Catalog

  • Rails, Angular, Postgres, and Bootstrap

    Powerful, Effective, and Efficient Full-Stack Web Development As a Rails developer, you care about user experience and performance, but you also want simple and maintainable code. Achieve all that by embracing the full stack of web development, from styling with Bootstrap, building an interactive user interface with AngularJS, to storing data quickly and reliably in PostgreSQL. Take a holistic view of full-stack development to create usable, high-performing applications, and learn to use these technologies effectively in a Ruby on Rails environment.

    $45.00
  • Ruby Performance Optimization

    Why Ruby Is Slow, and How to Fix It You don’t have to accept slow Ruby or Rails performance. In this comprehensive guide to Ruby optimization, you’ll learn how to write faster Ruby code—but that’s just the beginning. See exactly what makes Ruby and Rails code slow, and how to fix it. Alex Dymo will guide you through perils of memory and CPU optimization, profiling, measuring, performance testing, garbage collection, and tuning. You’ll find that all those “hard” things aren’t so difficult after all, and your code will run orders of magnitude faster.

    $46.00
  • Seven Mobile Apps in Seven Weeks

    Native Apps, Multiple Platforms Answer the question “Can we build this for ALL the devices?” with a resounding YES. This book will help you get there with a real-world introduction to seven platforms, whether you’re new to mobile or an experienced developer needing to expand your options. Plus, you’ll find out which cross-platform solution makes the most sense for your needs.

    $26.00
post /line_items?product_id=2
You are being redirected.
get http://localhost:3000/carts/1
The Pragmatic Bookshelf

Your Pragmatic Cart

  • Seven Mobile Apps in Seven Weeks
  • Seven Mobile Apps in Seven Weeks
  • Rails, Angular, Postgres, and Bootstrap
get /
The Pragmatic Bookshelf

Your Pragmatic Catalog

  • Rails, Angular, Postgres, and Bootstrap

    Powerful, Effective, and Efficient Full-Stack Web Development As a Rails developer, you care about user experience and performance, but you also want simple and maintainable code. Achieve all that by embracing the full stack of web development, from styling with Bootstrap, building an interactive user interface with AngularJS, to storing data quickly and reliably in PostgreSQL. Take a holistic view of full-stack development to create usable, high-performing applications, and learn to use these technologies effectively in a Ruby on Rails environment.

    $45.00
  • Ruby Performance Optimization

    Why Ruby Is Slow, and How to Fix It You don’t have to accept slow Ruby or Rails performance. In this comprehensive guide to Ruby optimization, you’ll learn how to write faster Ruby code—but that’s just the beginning. See exactly what makes Ruby and Rails code slow, and how to fix it. Alex Dymo will guide you through perils of memory and CPU optimization, profiling, measuring, performance testing, garbage collection, and tuning. You’ll find that all those “hard” things aren’t so difficult after all, and your code will run orders of magnitude faster.

    $46.00
  • Seven Mobile Apps in Seven Weeks

    Native Apps, Multiple Platforms Answer the question “Can we build this for ALL the devices?” with a resounding YES. This book will help you get there with a real-world introduction to seven platforms, whether you’re new to mobile or an experienced developer needing to expand your options. Plus, you’ll find out which cross-platform solution makes the most sense for your needs.

    $26.00
post /line_items?product_id=2
You are being redirected.
get http://localhost:3000/carts/1
The Pragmatic Bookshelf

Your Pragmatic Cart

  • Seven Mobile Apps in Seven Weeks
  • Seven Mobile Apps in Seven Weeks
  • Rails, Angular, Postgres, and Bootstrap
  • Rails, Angular, Postgres, and Bootstrap
get /
The Pragmatic Bookshelf

Your Pragmatic Catalog

  • Rails, Angular, Postgres, and Bootstrap

    Powerful, Effective, and Efficient Full-Stack Web Development As a Rails developer, you care about user experience and performance, but you also want simple and maintainable code. Achieve all that by embracing the full stack of web development, from styling with Bootstrap, building an interactive user interface with AngularJS, to storing data quickly and reliably in PostgreSQL. Take a holistic view of full-stack development to create usable, high-performing applications, and learn to use these technologies effectively in a Ruby on Rails environment.

    $45.00
  • Ruby Performance Optimization

    Why Ruby Is Slow, and How to Fix It You don’t have to accept slow Ruby or Rails performance. In this comprehensive guide to Ruby optimization, you’ll learn how to write faster Ruby code—but that’s just the beginning. See exactly what makes Ruby and Rails code slow, and how to fix it. Alex Dymo will guide you through perils of memory and CPU optimization, profiling, measuring, performance testing, garbage collection, and tuning. You’ll find that all those “hard” things aren’t so difficult after all, and your code will run orders of magnitude faster.

    $46.00
  • Seven Mobile Apps in Seven Weeks

    Native Apps, Multiple Platforms Answer the question “Can we build this for ALL the devices?” with a resounding YES. This book will help you get there with a real-world introduction to seven platforms, whether you’re new to mobile or an experienced developer needing to expand your options. Plus, you’ll find out which cross-platform solution makes the most sense for your needs.

    $26.00
post /line_items?product_id=3
You are being redirected.
get http://localhost:3000/carts/1
The Pragmatic Bookshelf

Your Pragmatic Cart

  • Seven Mobile Apps in Seven Weeks
  • Seven Mobile Apps in Seven Weeks
  • Rails, Angular, Postgres, and Bootstrap
  • Rails, Angular, Postgres, and Bootstrap
  • Seven Mobile Apps in Seven Weeks

Add a quantity column to the line_item table in the database.

rails generate migration add_quantity_to_line_items quantity:integer
      invoke  active_record
      create    db/migrate/20170920194059_add_quantity_to_line_items.rb

Modify the migration to add a default value for the new column

edit db/migrate/20170920194059_add_quantity_to_line_items.rb
class AddQuantityToLineItems < ActiveRecord::Migration[5.2]
  def change
    add_column :line_items, :quantity, :integer, default: 1
  end
end

Apply the migration

rails db:migrate
mv 20170920194059_add_quantity_to_line_items.rb 20170920000004_add_quantity_to_line_items.rb
== 20170920000004 AddQuantityToLineItems: migrating ===========================
-- add_column(:line_items, :quantity, :integer, {:default=>1})
   -> 0.0005s
== 20170920000004 AddQuantityToLineItems: migrated (0.0005s) ==================
 

Create a method to add a product to the cart by either incrementing the quantity of an existing line item, or creating a new line item.

edit app/models/cart.rb
 
  def add_product(product)
    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)
    end
    current_item
  end

Replace the call to LineItem.new with a call to the new method.

edit app/controllers/line_items_controller.rb
  def create
    product = Product.find(params[:product_id])
    @line_item = @cart.add_product(product)
 
    respond_to do |format|
      if @line_item.save
        format.html { redirect_to @line_item.cart,
          notice: 'Line item was successfully created.' }
        format.json { render :show,
          status: :created, location: @line_item }
      else
        format.html { render :new }
        format.json { render json: @line_item.errors,
          status: :unprocessable_entity }
      end
    end
  end

Update the view to show both columns.

edit app/views/carts/show.html.erb
<% if notice %>
  <aside id="notice"><%= notice %></aside>
<% end %>
 
<h2>Your Pragmatic Cart</h2>
<ul>    
  <% @cart.line_items.each do |item| %>
    <li><%= item.quantity %> &times; <%= item.product.title %></li>
  <% end %>
</ul>

Look at the cart, and see that's not exactly what we intended

get /carts/1
The Pragmatic Bookshelf

Your Pragmatic Cart

  • 1 × Seven Mobile Apps in Seven Weeks
  • 1 × Seven Mobile Apps in Seven Weeks
  • 1 × Rails, Angular, Postgres, and Bootstrap
  • 1 × Rails, Angular, Postgres, and Bootstrap
  • 1 × Seven Mobile Apps in Seven Weeks

Generate a migration to combine/separate items in carts.

rails generate migration combine_items_in_cart
      invoke  active_record
      create    db/migrate/20170920194101_combine_items_in_cart.rb

Fill in the self.up method

edit db/migrate/20170920194101_combine_items_in_cart.rb
  def up
    # replace multiple items for a single product in a cart with a
    # single item
    Cart.all.each do |cart|
      # count the number of each product in the cart
      sums = cart.line_items.group(:product_id).sum(:quantity)
 
      sums.each do |product_id, quantity|
        if quantity > 1
          # remove individual items
          cart.line_items.where(product_id: product_id).delete_all
 
          # replace with a single item
          item = cart.line_items.build(product_id: product_id)
          item.quantity = quantity
          item.save!
        end
      end
    end
  end

Combine entries

rails db:migrate
mv 20170920194101_combine_items_in_cart.rb 20170920000005_combine_items_in_cart.rb
== 20170920000005 CombineItemsInCart: migrating ===============================
== 20170920000005 CombineItemsInCart: migrated (0.0257s) ======================
 

Verify that the entries have been combined.

get /carts/1
The Pragmatic Bookshelf

Your Pragmatic Cart

  • 2 × Rails, Angular, Postgres, and Bootstrap
  • 3 × Seven Mobile Apps in Seven Weeks
get /carts/2

HTTP Response Code: 404

ActiveRecord::RecordNotFound in CartsController#show

Couldn't find Cart with 'id'=2

Extracted source (around line #67):
65
66
67
68
69
70
              
# Use callbacks to share common setup or constraints between actions.
def set_cart
@cart = Cart.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.

Rails.root: /home/rubys/git/awdwr/edition4/work/depot

Application Trace | Framework Trace | Full Trace

Request

Parameters:

{"id"=>"2"}

Response

Headers:

None
get /carts/3

HTTP Response Code: 404

ActiveRecord::RecordNotFound in CartsController#show

Couldn't find Cart with 'id'=3

Extracted source (around line #67):
65
66
67
68
69
70
              
# Use callbacks to share common setup or constraints between actions.
def set_cart
@cart = Cart.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.

Rails.root: /home/rubys/git/awdwr/edition4/work/depot

Application Trace | Framework Trace | Full Trace

Request

Parameters:

{"id"=>"3"}

Response

Headers:

None

Fill in the self.down method

edit db/migrate/20170920000005_combine_items_in_cart.rb
  def down
    # split items with quantity>1 into multiple items
    LineItem.where("quantity>1").each do |line_item|
      # add individual items
      line_item.quantity.times do 
        LineItem.create(
          cart_id: line_item.cart_id,
          product_id: line_item.product_id,
          quantity: 1
        )
      end
 
      # remove original item
      line_item.destroy
    end
  end

Separate out individual items.

rails db:rollback
== 20170920000005 CombineItemsInCart: reverting ===============================
== 20170920000005 CombineItemsInCart: reverted (0.0295s) ======================
 
rails db:migrate:status
 
database: /home/rubys/git/awdwr/edition4/work/depot/db/development.sqlite3
 
 Status   Migration ID    Migration Name
--------------------------------------------------
   up     20170920000001  Create products
   up     20170920000002  Create carts
   up     20170920000003  Create line items
   up     20170920000004  Add quantity to line items
  down    20170920000005  Combine items in cart
 
mv db/migrate/20170920000005_combine_items_in_cart.rb db/migrate/20170920000005_combine_items_in_cart.bak

Every item should (once again) only have a quantity of one.

get /carts/1
The Pragmatic Bookshelf

Your Pragmatic Cart

  • 1 × Rails, Angular, Postgres, and Bootstrap
  • 1 × Rails, Angular, Postgres, and Bootstrap
  • 1 × Seven Mobile Apps in Seven Weeks
  • 1 × Seven Mobile Apps in Seven Weeks
  • 1 × Seven Mobile Apps in Seven Weeks
get /carts/2

HTTP Response Code: 404

ActiveRecord::RecordNotFound in CartsController#show

Couldn't find Cart with 'id'=2

Extracted source (around line #67):
65
66
67
68
69
70
              
# Use callbacks to share common setup or constraints between actions.
def set_cart
@cart = Cart.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.

Rails.root: /home/rubys/git/awdwr/edition4/work/depot

Application Trace | Framework Trace | Full Trace

Request

Parameters:

{"id"=>"2"}

Response

Headers:

None
get /carts/3

HTTP Response Code: 404

ActiveRecord::RecordNotFound in CartsController#show

Couldn't find Cart with 'id'=3

Extracted source (around line #67):
65
66
67
68
69
70
              
# Use callbacks to share common setup or constraints between actions.
def set_cart
@cart = Cart.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.

Rails.root: /home/rubys/git/awdwr/edition4/work/depot

Application Trace | Framework Trace | Full Trace

Request

Parameters:

{"id"=>"3"}

Response

Headers:

None

Recombine the item data.

mv db/migrate/20170920000005_combine_items_in_cart.bak db/migrate/20170920000005_combine_items_in_cart.rb
rails db:migrate
== 20170920000005 CombineItemsInCart: migrating ===============================
== 20170920000005 CombineItemsInCart: migrated (0.0254s) ======================
 

Add a few products to the order.

post /line_items?product_id=2
You are being redirected.
get http://localhost:3000/carts/1
The Pragmatic Bookshelf

Your Pragmatic Cart

  • 3 × Rails, Angular, Postgres, and Bootstrap
  • 3 × Seven Mobile Apps in Seven Weeks
post /line_items?product_id=3
You are being redirected.
get http://localhost:3000/carts/1
The Pragmatic Bookshelf

Your Pragmatic Cart

  • 3 × Rails, Angular, Postgres, and Bootstrap
  • 4 × Seven Mobile Apps in Seven Weeks

fix the test case

edit test/controllers/line_items_controller_test.rb
  test "should create line_item" do
    assert_difference('LineItem.count') do
      post line_items_url, params: { product_id: products(:ruby).id }
    end
 
    follow_redirect!
 
    assert_select 'h2', 'Your Pragmatic Cart'
    assert_select 'li', "1 \u00D7 Programming Ruby 1.9"
  end

rerun tests

rails test
Run options: --seed 12367
 
# Running:
 
............................
 
Finished in 0.480741s, 58.2435 runs/s, 120.6472 assertions/s.
28 runs, 58 assertions, 0 failures, 0 errors, 0 skips

Try something malicious.

get /carts/wibble

HTTP Response Code: 404

ActiveRecord::RecordNotFound in CartsController#show

Couldn't find Cart with 'id'=wibble

Extracted source (around line #67):
65
66
67
68
69
70
              
# Use callbacks to share common setup or constraints between actions.
def set_cart
@cart = Cart.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.

Rails.root: /home/rubys/git/awdwr/edition4/work/depot

Application Trace | Framework Trace | Full Trace

Request

Parameters:

{"id"=>"wibble"}

Response

Headers:

None

10.2 Iteration E2: Handling Errors 9.4 Playtime