Agile Web Development with Rails, Edition 4

Agile Web Development with Rails, Edition 4

Table of Contents

4 Instant Gratification

We start with a simple "hello world!" demo application and in the process verify that everything is installed correctly.

Create the application

ruby /home/rubys/git/rails/railties/bin/rails demo1
      create  
      create  README
      create  Rakefile
      create  config.ru
      create  Gemfile
      create  app
      create  app/helpers/application_helper.rb
      create  app/controllers/application_controller.rb
      create  app/views/layouts
      create  app/models
      create  config
      create  config/routes.rb
      create  config/application.rb
      create  config/environment.rb
      create  config/environments
      create  config/environments/development.rb
      create  config/environments/test.rb
      create  config/environments/production.rb
      create  config/initializers
      create  config/initializers/mime_types.rb
      create  config/initializers/new_rails_defaults.rb
      create  config/initializers/inflections.rb
      create  config/initializers/session_store.rb
      create  config/initializers/backtrace_silencers.rb
      create  config/locales
      create  config/locales/en.yml
      create  config/boot.rb
      create  config/database.yml
      create  db
      create  db/seeds.rb
      create  doc
      create  doc/README_FOR_APP
      create  lib
      create  lib/tasks
      create  log
      create  log/server.log
      create  log/production.log
      create  log/development.log
      create  log/test.log
      create  public
      create  public/500.html
      create  public/robots.txt
      create  public/favicon.ico
      create  public/422.html
      create  public/404.html
      create  public/index.html
      create  public/images
      create  public/images/rails.png
      create  public/stylesheets
      create  public/javascripts
      create  public/javascripts/prototype.js
      create  public/javascripts/dragdrop.js
      create  public/javascripts/effects.js
      create  public/javascripts/controls.js
      create  public/javascripts/application.js
      create  script
      create  script/server
      create  script/console
      create  script/generate
      create  script/destroy
      create  script/runner
      create  script/plugin
      create  script/performance/benchmarker
      create  script/performance/profiler
      create  script/dbconsole
      create  script/about
      create  test
      create  test/test_helper.rb
      create  test/performance/browsing_test.rb
      create  test/fixtures
      create  test/unit
      create  test/functional
      create  test/integration
      create  tmp
      create  tmp/sessions
      create  tmp/sockets
      create  tmp/cache
      create  tmp/pids
      create  vendor/plugins
ln -s /home/rubys/git/rails vendor/rails

See what files were created

ls -p
app/	 config.ru  doc/     lib/  public/   README   test/  vendor/
config/  db/	    Gemfile  log/  Rakefile  script/  tmp/

Create a simple controller

ruby script/generate controller say hello
      create  app/controllers/say_controller.rb
      invoke  erb
      create    app/views/say
      create    app/views/say/hello.html.erb
      invoke  test_unit
      create    test/functional/say_controller_test.rb
      invoke  helper
      create    app/helpers/say_helper.rb
      invoke    test_unit
      create      test/unit/helpers/say_helper_test.rb

Start the server.

Attempt to fetch the file - note that it is missing

get /say/hello

Say#hello

Find me in app/views/say/hello.html.erb

Replace file with a simple hello world

edit app/views/say/hello.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>Hello Rails</title>
  </head>
  <body>
    <h1>Hello from Rails!</h1>
  </body>
</html>
get /say/hello

Hello from Rails!

pub work/demo1

Add a simple expression

edit app/views/say/hello.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>Hello Rails</title>
  </head>
  <body>
    <h1>Hello from Rails!</h1>
    <p>
      It is now <%= Time.now %>
    </p>
  </body>
</html>
get /say/hello

Hello from Rails!

It is now Sun Dec 20 03:10:18 -0500 2009

pub work/demo2

Evaluate the expression in the controller.

edit app/controllers/say_controller.rb
class SayController < ApplicationController
  def hello
    @time = Time.now
  end
 
end

Reference the result in the view.

edit app/views/say/hello.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>Hello Rails</title>
  </head>
  <body>
    <h1>Hello from Rails!</h1>
    <p>
      It is now <%= @time %>
    </p>
  </body>
</html>
get /say/hello

Hello from Rails!

It is now Sun Dec 20 03:10:19 -0500 2009

pub work/demo3

Add a goodbye action

edit app/controllers/say_controller.rb
class SayController < ApplicationController
  def hello
    @time = Time.now
  end
 
  def goodbye
  end
end

Add a goodbye template

edit app/views/say/goodbye.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>See You Later!</title>
  </head>
  <body>
    <h1>Goodbye!</h1>
    <p>
      It was nice having you here.
    </p>
  </body>
</html>
get /say/goodbye

Goodbye!

It was nice having you here.

pub work/demo4

Add a link from the hello page to the goodbye page

edit app/views/say/hello.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>Hello Rails</title>
  </head>
  <body>
    <h1>Hello from Rails!</h1>
    <p>
      It is now <%= @time %>
    </p>
    <p>
      Time to say
      <%= link_to "Goodbye!", :action => "goodbye" %>
    </p>
  </body>
</html>
get /say/hello

Hello from Rails!

It is now Sun Dec 20 03:10:20 -0500 2009

Time to say Goodbye!

Add a link back to the hello page

edit app/views/say/goodbye.html.erb
<!DOCTYPE html>
<html>
  <head>
    <title>See You Later!</title>
  </head>
  <body>
    <h1>Goodbye!</h1>
    <p>
      It was nice having you here.
    </p>
    <p>
      Say <%= link_to "Hello", :action => "hello" %> again.
    </p>
  </body>
</html>
get /say/goodbye

Goodbye!

It was nice having you here.

Say Hello again.

pub work/demo5

6.1 Iteration A1: Configuring a Rails Application

This section mostly covers database configuration options for those users that insist on using MySQL. SQLite3 users will skip most of it.

Create the application.

ruby /home/rubys/git/rails/railties/bin/rails depot
      create  
      create  README
      create  Rakefile
      create  config.ru
      create  Gemfile
      create  app
      create  app/helpers/application_helper.rb
      create  app/controllers/application_controller.rb
      create  app/views/layouts
      create  app/models
      create  config
      create  config/routes.rb
      create  config/application.rb
      create  config/environment.rb
      create  config/environments
      create  config/environments/development.rb
      create  config/environments/test.rb
      create  config/environments/production.rb
      create  config/initializers
      create  config/initializers/mime_types.rb
      create  config/initializers/new_rails_defaults.rb
      create  config/initializers/inflections.rb
      create  config/initializers/session_store.rb
      create  config/initializers/backtrace_silencers.rb
      create  config/locales
      create  config/locales/en.yml
      create  config/boot.rb
      create  config/database.yml
      create  db
      create  db/seeds.rb
      create  doc
      create  doc/README_FOR_APP
      create  lib
      create  lib/tasks
      create  log
      create  log/server.log
      create  log/production.log
      create  log/development.log
      create  log/test.log
      create  public
      create  public/500.html
      create  public/robots.txt
      create  public/favicon.ico
      create  public/422.html
      create  public/404.html
      create  public/index.html
      create  public/images
      create  public/images/rails.png
      create  public/stylesheets
      create  public/javascripts
      create  public/javascripts/prototype.js
      create  public/javascripts/dragdrop.js
      create  public/javascripts/effects.js
      create  public/javascripts/controls.js
      create  public/javascripts/application.js
      create  script
      create  script/server
      create  script/console
      create  script/generate
      create  script/destroy
      create  script/runner
      create  script/plugin
      create  script/performance/benchmarker
      create  script/performance/profiler
      create  script/dbconsole
      create  script/about
      create  test
      create  test/test_helper.rb
      create  test/performance/browsing_test.rb
      create  test/fixtures
      create  test/unit
      create  test/functional
      create  test/integration
      create  tmp
      create  tmp/sessions
      create  tmp/sockets
      create  tmp/cache
      create  tmp/pids
      create  vendor/plugins
ln -s /home/rubys/git/rails vendor/rails

Look at the files created.

ls -p
app/	 config.ru  doc/     lib/  public/   README   test/  vendor/
config/  db/	    Gemfile  log/  Rakefile  script/  tmp/

Database configuration options (generally not required for sqlite3)

cat config/database.yml
# SQLite version 3.x
#   gem install sqlite3-ruby (not necessary on OS X Leopard)
development:
  adapter: sqlite3
  database: db/development.sqlite3
  pool: 5
  timeout: 5000
 
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  adapter: sqlite3
  database: db/test.sqlite3
  pool: 5
  timeout: 5000
 
production:
  adapter: sqlite3
  database: db/production.sqlite3
  pool: 5
  timeout: 5000

6.2 Iteration A2: Creating the Products Maintenance Application

Generate scaffolding for a real model, modify a template, and do our first bit of data entry.

Generating our first model and associated scaffolding

ruby script/generate scaffold product title:string description:text image_url:string price:decimal
      invoke  active_record
      create    db/migrate/20091220081022_create_products.rb
      create    app/models/product.rb
      invoke    test_unit
      create      test/unit/product_test.rb
      create      test/fixtures/products.yml
       route  resources :products
      invoke  scaffold_controller
      create    app/controllers/products_controller.rb
      invoke    erb
      create      app/views/products
      create      app/views/products/index.html.erb
      create      app/views/products/edit.html.erb
      create      app/views/products/show.html.erb
      create      app/views/products/new.html.erb
      create      app/views/products/_form.html.erb
      create      app/views/layouts/products.html.erb
      invoke    test_unit
      create      test/functional/products_controller_test.rb
      invoke    helper
      create      app/helpers/products_helper.rb
      invoke      test_unit
      create        test/unit/helpers/products_helper_test.rb
      invoke  stylesheets
      create    public/stylesheets/scaffold.css

Restart the server.

Add precision and scale to the price

edit db/migrate/20091220081022_create_products.rb
class CreateProducts < ActiveRecord::Migration
  def self.up
    create_table :products do |t|
      t.string :title
      t.text :description
      t.string :image_url
      t.decimal :price, :precision => 8, :scale => 2
 
      t.timestamps
    end
  end
 
  def self.down
    drop_table :products
  end
end

Apply the migration

rake db:migrate
mv 20091220081022_create_products.rb 20100301000001_create_products.rb
(in /home/rubys/svn/rails4/Book/util/work/depot)
==  CreateProducts: migrating =================================================
-- create_table(:products)
   -> 0.0019s
==  CreateProducts: migrated (0.0020s) ========================================
 

Get an (empty) list of products

get /products

Listing products

Title Description Image url Price

New product

Show (and modify) one of the templates produced

edit app/views/products/_form.html.erb
<% form_for(@product) do |f| %>
  <%= f.error_messages %>
 
  <div class="field">
    <%= f.label :title %><br />
    <%= f.text_field :title %>
  </div>
  <div class="field">
    <%= f.label :description %><br />
    <%= f.text_area :description, :rows => 6 %>
  </div>
  <div class="field">
    <%= f.label :image_url %><br />
    <%= f.text_field :image_url %>
  </div>
  <div class="field">
    <%= f.label :price %><br />
    <%= f.text_field :price %>
  </div>
  <div class="actions">
    <% if @product.new_record? %>
      <%= f.submit 'Create' %>
    <% else %>
      <%= f.submit 'Update' %>
    <% end %>
  </div>
<% end %>

Create a product

get /products/new

New product





Back
post /products
You are being redirected.
get http://127.0.0.1:3000/products/1

Product was successfully created.

Title: Pragmatic Version Control

Description: <p> This book is a recipe-based approach to using Subversion that will get you up and running quickly---and correctly. All projects need version control: it's a foundational piece of any project's infrastructure. Yet half of all project teams in the U.S. dont use any version control at all. Many others dont use it well, and end up experiencing time-consuming problems. </p>

Image url: /images/svn.jpg

Price: 29.95

Edit | Back

Verify that the product has been added

get /products

Listing products

Title Description Image url Price
Pragmatic Version Control <p> This book is a recipe-based approach to using Subversion that will get you up and running quickly---and correctly. All projects need version control: it's a foundational piece of any project's infrastructure. Yet half of all project teams in the U.S. dont use any version control at all. Many others dont use it well, and end up experiencing time-consuming problems. </p> /images/svn.jpg 29.95 Show Edit Destroy

New product

And, just to verify that we haven't broken anything

rake test
(in /home/rubys/svn/rails4/Book/util/work/depot)
DEPRECATION WARNING: ActiveSupport::DeprecatedCallbacks has been deprecated in favor of ActiveSupport::Callbacks. (called from included at /home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:7)
Loaded suite /home/rubys/.rvm/gems/ruby/1.8.7/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
.
Finished in 0.071877 seconds.
 
1 tests, 1 assertions, 0 failures, 0 errors
DEPRECATION WARNING: ActiveSupport::DeprecatedCallbacks has been deprecated in favor of ActiveSupport::Callbacks. (called from included at /home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:7)
Loaded suite /home/rubys/.rvm/gems/ruby/1.8.7/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
.......
Finished in 0.204108 seconds.
 
7 tests, 10 assertions, 0 failures, 0 errors
pub depot_a

6.3 Rails on the Inside

"Rails on the Inside" sections that can be skipped on first reading, they give a peek at is going on beneath the hood... in this case it looks at databases amd models.

Take a peek inside the database.

sqlite3> .schema
CREATE TABLE "products" ("id" INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar(255), "description" text, "image_url" varchar(255), "price" decimal(8,2), "created_at" datetime, "updated_at" datetime);
CREATE TABLE "schema_migrations" ("version" varchar(255) NOT NULL);
CREATE UNIQUE INDEX "unique_schema_migrations" ON "schema_migrations" ("version");

See what migrations have been applied.

sqlite3> select version from schema_migrations
version = 20100301000001

Look at the model....

cat app/models/product.rb
class Product < ActiveRecord::Base
end

Get a list of column names.

echo "Product.column_names" | IRBRC=tmp/irbrc ruby script/console
Loading development environment (Rails 3.0.pre)
>> Product.column_names
=> ["id", "title", "description", "image_url", "price", "created_at", "updated_at"]
>> 

Details on the price colum.

echo "Product.columns_hash[\"price\"]" | IRBRC=tmp/irbrc ruby script/console
Loading development environment (Rails 3.0.pre)
>> Product.columns_hash["price"]
=> #<ActiveRecord::ConnectionAdapters::SQLiteColumn:0xb76c98ac @sql_type="decimal(8,2)", @type=:decimal, @precision=8, @primary=false, @name="price", @default=nil, @limit=8, @null=true, @scale=2>
>> 
echo "p=Product.find(1)\\nputs p.title\\np.title+= \"Using Git\"" | IRBRC=tmp/irbrc ruby script/console
Loading development environment (Rails 3.0.pre)
>> p=Product.find(1)
=> #<Product id: 1, title: "Pragmatic Version Control", description: "<p>\nThis book is a recipe-based approach to\nusing S...", image_url: "/images/svn.jpg", price: #<BigDecimal:b76d588c,'0.2995E2',8(8)>, created_at: "2009-12-20 08:10:32", updated_at: "2009-12-20 08:10:32">
>> puts p.title
Pragmatic Version Control
=> nil
>> p.title+= "Using Git"
=> "Pragmatic Version ControlUsing Git"
>> 

Look at the proct data itself.

sqlite3> select * from products limit 1
         id = 1
      title = Pragmatic Version Control
description = <p>
This book is a recipe-based approach to
using Subversion that will get you up
and running quickly---and correctly. All
projects need version control: it's a
foundational piece of any project's
infrastructure. Yet half of all project
teams in the U.S.  dont use any version
control at all. Many others dont use it
well, and end up experiencing
time-consuming problems.
</p>
 
  image_url = /images/svn.jpg
      price = 29.95
 created_at = 2009-12-20 08:10:32.291690
 updated_at = 2009-12-20 08:10:32.291690

Look at raw column values.

echo "Product.find(:first).price_before_type_cast" | IRBRC=tmp/irbrc ruby script/console
Loading development environment (Rails 3.0.pre)
>> Product.find(:first).price_before_type_cast
=> "29.95"
>> 
echo "Product.find(:first).updated_at_before_type_cast" | IRBRC=tmp/irbrc ruby script/console
Loading development environment (Rails 3.0.pre)
>> Product.find(:first).updated_at_before_type_cast
=> "2009-12-20 08:10:32.291690"
>> 

6.4 Playtime

Demonstrate a few techniques that will help readers get back to a known state if they ever stray too far from the script.

Roll back a migration.

rake db:rollback
(in /home/rubys/svn/rails4/Book/util/work/depot)
==  CreateProducts: reverting =================================================
-- drop_table(:products)
   -> 0.0008s
==  CreateProducts: reverted (0.0009s) ========================================
 

Reapply migration.

rake db:migrate
(in /home/rubys/svn/rails4/Book/util/work/depot)
==  CreateProducts: migrating =================================================
-- create_table(:products)
   -> 0.0020s
==  CreateProducts: migrated (0.0021s) ========================================
 

Configure Git.

git repo-config --get-regexp user.*
user.name Sam Ruby
user.email rubys@intertwingly.net

Tell Git what files to ignore.

edit .gitignore
db/*.sqlite3
log/*.log
tmp/**/*

Initialize repository.

git init
Initialized empty Git repository in .git/

Add all the files.

git add .

Initial commit.

git commit -m "Depot Scaffold"
Created initial commit ede8cdc: Depot Scaffold
 63 files changed, 8781 insertions(+), 0 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 Gemfile
 create mode 100644 README
 create mode 100644 Rakefile
 create mode 100644 app/controllers/application_controller.rb
 create mode 100644 app/controllers/products_controller.rb
 create mode 100644 app/helpers/application_helper.rb
 create mode 100644 app/helpers/products_helper.rb
 create mode 100644 app/models/product.rb
 create mode 100644 app/views/layouts/products.html.erb
 create mode 100644 app/views/products/_form.html.erb
 create mode 100644 app/views/products/edit.html.erb
 create mode 100644 app/views/products/index.html.erb
 create mode 100644 app/views/products/new.html.erb
 create mode 100644 app/views/products/show.html.erb
 create mode 100644 config.ru
 create mode 100644 config/application.rb
 create mode 100644 config/boot.rb
 create mode 100644 config/database.yml
 create mode 100644 config/environment.rb
 create mode 100644 config/environments/development.rb
 create mode 100644 config/environments/production.rb
 create mode 100644 config/environments/test.rb
 create mode 100644 config/initializers/backtrace_silencers.rb
 create mode 100644 config/initializers/inflections.rb
 create mode 100644 config/initializers/mime_types.rb
 create mode 100644 config/initializers/new_rails_defaults.rb
 create mode 100644 config/initializers/session_store.rb
 create mode 100644 config/locales/en.yml
 create mode 100644 config/routes.rb
 create mode 100644 db/migrate/20100301000001_create_products.rb
 create mode 100644 db/schema.rb
 create mode 100644 db/seeds.rb
 create mode 100644 doc/README_FOR_APP
 create mode 100644 public/404.html
 create mode 100644 public/422.html
 create mode 100644 public/500.html
 create mode 100644 public/favicon.ico
 create mode 100644 public/images/rails.png
 create mode 100644 public/index.html
 create mode 100644 public/javascripts/application.js
 create mode 100644 public/javascripts/controls.js
 create mode 100644 public/javascripts/dragdrop.js
 create mode 100644 public/javascripts/effects.js
 create mode 100644 public/javascripts/prototype.js
 create mode 100644 public/robots.txt
 create mode 100644 public/stylesheets/scaffold.css
 create mode 100755 script/about
 create mode 100755 script/console
 create mode 100755 script/dbconsole
 create mode 100755 script/destroy
 create mode 100755 script/generate
 create mode 100755 script/performance/benchmarker
 create mode 100755 script/performance/profiler
 create mode 100755 script/plugin
 create mode 100755 script/runner
 create mode 100755 script/server
 create mode 100644 test/fixtures/products.yml
 create mode 100644 test/functional/products_controller_test.rb
 create mode 100644 test/performance/browsing_test.rb
 create mode 100644 test/test_helper.rb
 create mode 100644 test/unit/helpers/products_helper_test.rb
 create mode 100644 test/unit/product_test.rb
 create mode 120000 vendor/rails

7.1 Iteration B1: Validate!

Augment the model with a few vailidity checks.

Various validations: required, numeric, positive, and unique

edit app/models/product.rb
class Product < ActiveRecord::Base
  validates_presence_of :title, :description, :image_url
  validates_numericality_of :price
  validate :price_must_be_at_least_a_cent
  validates_uniqueness_of :title
  validates_format_of :image_url, :allow_blank => true,
                      :with    => %r{\.(gif|jpg|png)$}i,
                      :message => 'must be a URL for GIF, JPG ' +
                                  'or PNG image.'
 
protected
  def price_must_be_at_least_a_cent
    errors.add(:price, 'should be at least 0.01') if price.nil? ||
                       price < 0.00
  end
 
end

Demonstrate failures.

get /products/new

New product





Back
post /products

New product

3 errors prohibited this product from being saved

There were problems with the following fields:

  • Title can't be blank
  • Description can't be blank
  • Image url can't be blank




Back

Demonstrate more failures.

get /products/new

New product





Back
post /products

New product

1 error prohibited this product from being saved

There were problems with the following fields:

  • Price is not a number




Back
edit app/models/product.rb
class Product < ActiveRecord::Base
  validates_presence_of :title, :description, :image_url
  validates_numericality_of :price
  validate :price_must_be_at_least_a_cent
  validates_uniqueness_of :title
  validates_format_of :image_url,
                      :with    => %r{\.(gif|jpg|png)$}i,
                      :message => 'must be a URL for GIF, JPG ' +
                                  'or PNG image.'
 
protected
  def price_must_be_at_least_a_cent
    errors.add(:price, 'should be at least 0.01') if price.nil? ||
                       price < 0.01
  end
 
end
edit app/views/layouts/products.html.erb
pub depot_b

7.2 Iteration B2: Unit Testing

Introduce the importance of unit testing.

Now run the tests... and watch them fail :-(

rake test
(in /home/rubys/svn/rails4/Book/util/work/depot)
DEPRECATION WARNING: ActiveSupport::DeprecatedCallbacks has been deprecated in favor of ActiveSupport::Callbacks. (called from included at /home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:7)
Loaded suite /home/rubys/.rvm/gems/ruby/1.8.7/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
.
Finished in 0.075616 seconds.
 
1 tests, 1 assertions, 0 failures, 0 errors
DEPRECATION WARNING: ActiveSupport::DeprecatedCallbacks has been deprecated in favor of ActiveSupport::Callbacks. (called from included at /home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:7)
Loaded suite /home/rubys/.rvm/gems/ruby/1.8.7/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
F.....F
Finished in 0.227155 seconds.
 
  1) Failure:
test_should_create_product(ProductsControllerTest) [/test/functional/products_controller_test.rb:16]:
"Product.count" didn't change by 1.
<3> expected but was
<2>.
 
  2) Failure:
test_should_update_product(ProductsControllerTest) [/test/functional/products_controller_test.rb:35]:
Expected response to be a <:redirect>, but was <200>
 
7 tests, 9 assertions, 2 failures, 0 errors
Errors running test:functionals!

Solution is simple, provide valid data.

edit test/functional/products_controller_test.rb
require 'test_helper'
 
class ProductsControllerTest < ActionController::TestCase
  TEST_DATA = {
    :title       => "My Book Title",
    :description => "yyy",
    :image_url   => "zzz.jpg",
    :price       => 12.50
  }
  # ...
 
  test "should create product" do
    assert_difference('Product.count') do
      post :create, :product => TEST_DATA
    end
 
    assert_redirected_to product_path(assigns(:product))
  end
  # ...
 
  test "should update product" do
    put :update, :id => products(:one).to_param, :product => TEST_DATA
    assert_redirected_to product_path(assigns(:product))
  end
  # ...
end

Tests now pass again :-)

rake test
(in /home/rubys/svn/rails4/Book/util/work/depot)
DEPRECATION WARNING: ActiveSupport::DeprecatedCallbacks has been deprecated in favor of ActiveSupport::Callbacks. (called from included at /home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:7)
Loaded suite /home/rubys/.rvm/gems/ruby/1.8.7/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
.
Finished in 0.072551 seconds.
 
1 tests, 1 assertions, 0 failures, 0 errors
DEPRECATION WARNING: ActiveSupport::DeprecatedCallbacks has been deprecated in favor of ActiveSupport::Callbacks. (called from included at /home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:7)
Loaded suite /home/rubys/.rvm/gems/ruby/1.8.7/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
.......
Finished in 0.212912 seconds.
 
7 tests, 10 assertions, 0 failures, 0 errors

Add some unit tests for new function.

edit test/unit/product_test.rb
require 'test_helper'
 
class ProductTest < ActiveSupport::TestCase
  
  fixtures :products
  
  test "invalid with empty attributes" do
    product = Product.new
    assert !product.valid?
    assert product.errors[:title].any?
    assert product.errors[:description].any?
    assert product.errors[:price].any?
    assert product.errors[:image_url].any?
  end
 
  test "positive price" do
    product = Product.new(:title       => "My Book Title",
                          :description => "yyy",
                          :image_url   => "zzz.jpg")
    product.price = -1
    assert !product.valid?
    assert_equal "should be at least 0.01", product.errors[:price].join
 
    product.price = 0
    assert !product.valid?
    assert_equal "should be at least 0.01", product.errors[:price].join
 
    product.price = 1
    assert product.valid?
  end
 
  test "image url" do
    ok = %w{ fred.gif fred.jpg fred.png FRED.JPG FRED.Jpg
             http://a.b.c/x/y/z/fred.gif }
    bad = %w{ fred.doc fred.gif/more fred.gif.more }
    
    ok.each do |name|
      product = Product.new(:title       => "My Book Title",
                            :description => "yyy",
                            :price       => 1,
                            :image_url   => name)
      assert product.valid?, product.errors.full_messages
    end
 
    bad.each do |name|
      product = Product.new(:title => "My Book Title",
                            :description => "yyy",
                            :price => 1,
                            :image_url => name)
      assert !product.valid?, "saving #{name}"
    end
  end
 
  test "unique title" do
    product = Product.new(:title       => products(:one).title,
                          :description => "yyy", 
                          :price       => 1, 
                          :image_url   => "fred.gif")
 
    assert !product.save
    assert_equal "has already been taken", product.errors[:title].join
  end
 
  test "unique title1" do
    product = Product.new(:title       => products(:one).title,
                          :description => "yyy", 
                          :price       => 1, 
                          :image_url   => "fred.gif")
 
    assert !product.save
    assert_equal I18n.translate('activerecord.errors.messages.taken'),
                 product.errors[:title].join
  end
  
end

Modify the fixture.

edit test/fixtures/products.yml
one:
  title:       Title One
  description: Dummy description
  price:       1234
  image_url:   one.png
 
two:
  title:       Title Two
  description: Dummy description
  price:       2345
  image_url:   two.png
  

Tests pass!

rake test
(in /home/rubys/svn/rails4/Book/util/work/depot)
DEPRECATION WARNING: ActiveSupport::DeprecatedCallbacks has been deprecated in favor of ActiveSupport::Callbacks. (called from included at /home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:7)
Loaded suite /home/rubys/.rvm/gems/ruby/1.8.7/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
.....
Finished in 0.132445 seconds.
 
5 tests, 23 assertions, 0 failures, 0 errors
DEPRECATION WARNING: ActiveSupport::DeprecatedCallbacks has been deprecated in favor of ActiveSupport::Callbacks. (called from included at /home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:7)
Loaded suite /home/rubys/.rvm/gems/ruby/1.8.7/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
.......
Finished in 0.212924 seconds.
 
7 tests, 10 assertions, 0 failures, 0 errors

7.3 Playtime

Save our work

Show what files we changed.

git status
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#
#	modified:   app/models/product.rb
#	modified:   app/views/layouts/products.html.erb
#	modified:   test/fixtures/products.yml
#	modified:   test/functional/products_controller_test.rb
#	modified:   test/unit/product_test.rb
#
no changes added to commit (use "git add" and/or "git commit -a")

Commit changes using -a shortcut

git commit -a -m "Validation!"
Created commit b2b7600: Validation!
 5 files changed, 148 insertions(+), 19 deletions(-)

8.1 Iteration C1: Making Prettier Listings

Show the relationship between various artifacts: seed data, stylesheets, html, and images.

Load some "seed" data

edit db/seeds.rb
Product.delete_all
 
Product.create(:title => 'Pragmatic Version Control',
  :description =>
      %{<p>
         This book is a recipe-based approach to using Subversion that will 
         get you up and running quickly---and correctly. All projects need
         version control: it's a foundational piece of any project's 
         infrastructure. Yet half of all project teams in the U.S. don't use
         any version control at all. Many others don't use it well, and end 
         up experiencing time-consuming problems.
      </p>},
  :image_url => '/images/svn.jpg',
  :price => 28.50)
  # . . .
rake db:seed
(in /home/rubys/svn/rails4/Book/util/work/depot)

Link to the stylesheet in the layout

edit app/views/layouts/products.html.erb
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="content-type" content="text/html; charset=UTF-8" />
  <title>Products: <%= controller.action_name %></title>
  <%= stylesheet_link_tag 'scaffold', 'depot' %>
</head>

Replace the scaffold generated view with some custom HTML

edit app/views/products/index.html.erb
<div id="product_list">
  <h1>Listing products</h1>
 
  <table>
  <% @products.each do |product| %>
    <tr class="<%= cycle('list_line_odd', 'list_line_even') %>">
 
      <td>
        <%= image_tag product.image_url, :class => 'list_image' %>
      </td>
 
      <td class="list_description">
        <dl>
          <dt><%= product.title %></dt>
          <dd><%= truncate(product.description.gsub(/<.*?>/,''),
                 :length => 80) %></dd>
        </dl>
      </td>
 
      <td class="list_actions">
        <%= link_to 'Show', product %><br/>
        <%= link_to 'Edit', edit_product_path(product) %><br/>
        <%= link_to 'Destroy', product, 
                    :confirm => 'Are you sure?',
                    :method => :delete %>
      </td>
    </tr>
  <% end %>
  </table>
</div>
 
<br />
 
<%= link_to 'New product', new_product_path %>

Copy some images and a stylesheet

cp -v /home/rubys/svn/rails4/Book/util/data/images/* public/images/
`/home/rubys/svn/rails4/Book/util/data/images/auto.jpg' -> `public/images/auto.jpg'
`/home/rubys/svn/rails4/Book/util/data/images/logo.png' -> `public/images/logo.png'
`/home/rubys/svn/rails4/Book/util/data/images/rails.png' -> `public/images/rails.png'
`/home/rubys/svn/rails4/Book/util/data/images/svn.jpg' -> `public/images/svn.jpg'
`/home/rubys/svn/rails4/Book/util/data/images/utc.jpg' -> `public/images/utc.jpg'
cp -v /home/rubys/svn/rails4/Book/util/data/depot.css public/stylesheets
`/home/rubys/svn/rails4/Book/util/data/depot.css' -> `public/stylesheets/depot.css'

See the finished result

get /products

Listing products

Auto
Pragmatic Project Automation
Pragmatic Project Automation shows you how to improve the con...
Show
Edit
Destroy
Svn
Pragmatic Version Control
This book is a recipe-based approach to using Subversion that will ...
Show
Edit
Destroy
Utc
Pragmatic Unit Testing (C#)
Pragmatic programmers use feedback to drive their development and ...
Show
Edit
Destroy

New product
pub depot_c

8.2 Iteration C2: Create the Catalog Listing

Show the model, view, and controller working together.

Create a second controller with a single index action

ruby script/generate controller store index
      create  app/controllers/store_controller.rb
      invoke  erb
      create    app/views/store
      create    app/views/store/index.html.erb
      invoke  test_unit
      create    test/functional/store_controller_test.rb
      invoke  helper
      create    app/helpers/store_helper.rb
      invoke    test_unit
      create      test/unit/helpers/store_helper_test.rb

Demonstrate that everything is wired together

get /store

Store#index

Find me in app/views/store/index.html.erb

In the controller, get a list of products from the model

edit app/controllers/store_controller.rb
class StoreController < ApplicationController
  def index
    @products = Product.all
  end
 
end

In the model, define a default sort order

edit app/models/product.rb
class Product < ActiveRecord::Base
  default_scope :order => 'title'
 
  # validation stuff...

In the view, display a list of products

edit app/views/store/index.html.erb
<h1>Your Pragmatic Catalog</h1>
 
<% @products.each do |product| %>
  <div class="entry">
    <%= image_tag(product.image_url) %>
    <h3><%= product.title %></h3>
    <%=raw product.description %>
    <div class="price_line">
      <span class="price"><%= product.price %></span>
    </div>
  </div>
<% end %>

Show our first (ugly) catalog page

get /store

Your Pragmatic Catalog

Auto

Pragmatic Project Automation

Pragmatic Project Automation shows you how to improve the consistency and repeatability of your project's procedures using automation to reduce risk and errors.

Simply put, we're going to put this thing called a computer to work for you doing the mundane (but important) project stuff. That means you'll have more time and energy to do the really exciting---and difficult---stuff, like writing quality code.

29.95
Utc

Pragmatic Unit Testing (C#)

Pragmatic programmers use feedback to drive their development and personal processes. The most valuable feedback you can get while coding comes from unit testing.

Without good tests in place, coding can become a frustrating game of "whack-a-mole." That's the carnival game where the player strikes at a mechanical mole; it retreats and another mole pops up on the opposite side of the field. The moles pop up and down so fast that you end up flailing your mallet helplessly as the moles continue to pop up where you least expect them.

27.75
Svn

Pragmatic Version Control

This book is a recipe-based approach to using Subversion that will get you up and running quickly---and correctly. All projects need version control: it's a foundational piece of any project's infrastructure. Yet half of all project teams in the U.S. don't use any version control at all. Many others don't use it well, and end up experiencing time-consuming problems.

28.5
pub depot_d

8.3 Iteration C3: Add a Page Layout

Demonstrate layouts.

Add a layout

edit app/views/layouts/store.html.erb
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Pragprog Books Online Store</title>
  <%= stylesheet_link_tag "depot", :media => "all" %><!-- <label id="code.slt"/> -->
</head>
<body id="store">
  <div id="banner">
    <%= image_tag("logo.png") %>
    <%= @page_title || "Pragmatic Bookshelf" %><!-- <label id="code.depot.e.title"/> -->
  </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">
      <%= yield %><!-- <label id="code.depot.e.include"/> -->
    </div>
  </div>
</body>
</html>

Modify the stylesheet

edit public/stylesheets/depot.css
/* Styles for main page */
 
#banner {
  background: #9c9;
  padding-top: 10px;
  padding-bottom: 10px;
  border-bottom: 2px solid;
  font: small-caps 40px/40px "Times New Roman", serif;
  color: #282;
  text-align: center;
}
 
#banner img {
  float: left;
}
 
#columns {
  background: #141;
}
 
#main {
  margin-left: 13em;
  padding-top: 4ex;
  padding-left: 2em;
  background: white;
}
 
#side {
  float: left;
  padding-top: 1em;
  padding-left: 1em;
  padding-bottom: 1em;
  width: 12em;
  background: #141;
}
 
#side a {
  color: #bfb;
  font-size: small;
}

Show the results.

get /store

Your Pragmatic Catalog

Auto

Pragmatic Project Automation

Pragmatic Project Automation shows you how to improve the consistency and repeatability of your project's procedures using automation to reduce risk and errors.

Simply put, we're going to put this thing called a computer to work for you doing the mundane (but important) project stuff. That means you'll have more time and energy to do the really exciting---and difficult---stuff, like writing quality code.

29.95
Utc

Pragmatic Unit Testing (C#)

Pragmatic programmers use feedback to drive their development and personal processes. The most valuable feedback you can get while coding comes from unit testing.

Without good tests in place, coding can become a frustrating game of "whack-a-mole." That's the carnival game where the player strikes at a mechanical mole; it retreats and another mole pops up on the opposite side of the field. The moles pop up and down so fast that you end up flailing your mallet helplessly as the moles continue to pop up where you least expect them.

27.75
Svn

Pragmatic Version Control

This book is a recipe-based approach to using Subversion that will get you up and running quickly---and correctly. All projects need version control: it's a foundational piece of any project's infrastructure. Yet half of all project teams in the U.S. don't use any version control at all. Many others don't use it well, and end up experiencing time-consuming problems.

28.5

8.4 Iteration C4: Use a Helper to Format the Price

Demonstrate helpers.

Format the price using a built-in helper.

edit app/views/store/index.html.erb
<h1>Your Pragmatic Catalog</h1>
 
<% @products.each do |product| %>
  <div class="entry">
    <%= image_tag(product.image_url) %>
    <h3><%= product.title %></h3>
    <%=raw product.description %>
    <div class="price_line">
      <span class="price"><%= number_to_currency(product.price) %></span>
    </div>
  </div>
<% end %>

Show the results.

get /store

Your Pragmatic Catalog

Auto

Pragmatic Project Automation

Pragmatic Project Automation shows you how to improve the consistency and repeatability of your project's procedures using automation to reduce risk and errors.

Simply put, we're going to put this thing called a computer to work for you doing the mundane (but important) project stuff. That means you'll have more time and energy to do the really exciting---and difficult---stuff, like writing quality code.

$29.95
Utc

Pragmatic Unit Testing (C#)

Pragmatic programmers use feedback to drive their development and personal processes. The most valuable feedback you can get while coding comes from unit testing.

Without good tests in place, coding can become a frustrating game of "whack-a-mole." That's the carnival game where the player strikes at a mechanical mole; it retreats and another mole pops up on the opposite side of the field. The moles pop up and down so fast that you end up flailing your mallet helplessly as the moles continue to pop up where you least expect them.

$27.75
Svn

Pragmatic Version Control

This book is a recipe-based approach to using Subversion that will get you up and running quickly---and correctly. All projects need version control: it's a foundational piece of any project's infrastructure. Yet half of all project teams in the U.S. don't use any version control at all. Many others don't use it well, and end up experiencing time-consuming problems.

$28.50

8.5 Iteration C5: Functional Testing

Demonstrate use of assert_select to test views.

Verify that the tests still pass.

rake test
(in /home/rubys/svn/rails4/Book/util/work/depot)
DEPRECATION WARNING: ActiveSupport::DeprecatedCallbacks has been deprecated in favor of ActiveSupport::Callbacks. (called from included at /home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:7)
Loaded suite /home/rubys/.rvm/gems/ruby/1.8.7/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
.....
Finished in 0.129776 seconds.
 
5 tests, 23 assertions, 0 failures, 0 errors
DEPRECATION WARNING: ActiveSupport::DeprecatedCallbacks has been deprecated in favor of ActiveSupport::Callbacks. (called from included at /home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:7)
Loaded suite /home/rubys/.rvm/gems/ruby/1.8.7/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
........
Finished in 0.281477 seconds.
 
8 tests, 11 assertions, 0 failures, 0 errors

Review the test data.

cat test/fixtures/products.yml
#START:ruby
one:
  title:       Title One
  description: Dummy description
  price:       1234
  image_url:   one.png
#END:ruby
 
two:
  title:       Title Two
  description: Dummy description
  price:       2345
  image_url:   two.png
 

Add tests for layout, product display, and formatting, using counts, string comparisons, and regular expressions.

edit test/functional/store_controller_test.rb
require 'test_helper'
 
class StoreControllerTest < ActionController::TestCase
  test "index page" do
    get :index
    assert_response :success
 
    assert_select '#columns #side a', 4
    assert_select '#main .entry', 2
    assert_select 'h3', 'Title One'
    assert_select 'h3', 'Title Two'
    assert_select '.price', /\$[,\d]+\.\d\d/
  end
end

Show that the tests pass.

rake test:functionals
(in /home/rubys/svn/rails4/Book/util/work/depot)
DEPRECATION WARNING: ActiveSupport::DeprecatedCallbacks has been deprecated in favor of ActiveSupport::Callbacks. (called from included at /home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:7)
Loaded suite /home/rubys/.rvm/gems/ruby/1.8.7/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
........
Finished in 0.311119 seconds.
 
8 tests, 18 assertions, 0 failures, 0 errors
pub depot_e

9.1 Iteration D1: Finding a Cart

Create a cart. Put it in a session. Find it.

Create a cart.

ruby script/generate scaffold cart
      invoke  active_record
      create    db/migrate/20091220081122_create_carts.rb
      create    app/models/cart.rb
      invoke    test_unit
      create      test/unit/cart_test.rb
      create      test/fixtures/carts.yml
       route  resources :carts
      invoke  scaffold_controller
      create    app/controllers/carts_controller.rb
      invoke    erb
      create      app/views/carts
      create      app/views/carts/index.html.erb
      create      app/views/carts/edit.html.erb
      create      app/views/carts/show.html.erb
      create      app/views/carts/new.html.erb
      create      app/views/carts/_form.html.erb
      create      app/views/layouts/carts.html.erb
      invoke    test_unit
      create      test/functional/carts_controller_test.rb
      invoke    helper
      create      app/helpers/carts_helper.rb
      invoke      test_unit
      create        test/unit/helpers/carts_helper_test.rb
      invoke  stylesheets
   identical    public/stylesheets/scaffold.css
rake db:migrate
mv 20091220081122_create_carts.rb 20100301000002_create_carts.rb
(in /home/rubys/svn/rails4/Book/util/work/depot)
==  CreateCarts: migrating ====================================================
-- create_table(:carts)
   -> 0.0017s
==  CreateCarts: migrated (0.0019s) ===========================================
 

Implement find_cart, which creates a new cart if it can't find one.

edit app/controllers/application_controller.rb
# Filters added to this controller apply to all controllers in the application.
# Likewise, all the methods added will be available for all controllers.
 
class ApplicationController < ActionController::Base
  helper :all # include all helpers, all the time
  protect_from_forgery # See ActionController::RequestForgeryProtection for details
 
  # Scrub sensitive parameters from your log
  # filter_parameter_logging :password
 
private
 
  def find_cart 
    @cart = Cart.find(session[:cart_id]) rescue nil
 
    unless @cart
      @cart = Cart.create
      session[:cart_id] = @cart.id
    end
 
    @cart
  end
end

9.2 Iteration D2: Connecting Products to Carts

Create line item which connects products to carts'

Create the model object.

ruby script/generate scaffold line_item product_id:integer cart_id:integer
      invoke  active_record
      create    db/migrate/20091220081126_create_line_items.rb
      create    app/models/line_item.rb
      invoke    test_unit
      create      test/unit/line_item_test.rb
      create      test/fixtures/line_items.yml
       route  resources :line_items
      invoke  scaffold_controller
      create    app/controllers/line_items_controller.rb
      invoke    erb
      create      app/views/line_items
      create      app/views/line_items/index.html.erb
      create      app/views/line_items/edit.html.erb
      create      app/views/line_items/show.html.erb
      create      app/views/line_items/new.html.erb
      create      app/views/line_items/_form.html.erb
      create      app/views/layouts/line_items.html.erb
      invoke    test_unit
      create      test/functional/line_items_controller_test.rb
      invoke    helper
      create      app/helpers/line_items_helper.rb
      invoke      test_unit
      create        test/unit/helpers/line_items_helper_test.rb
      invoke  stylesheets
   identical    public/stylesheets/scaffold.css
rake db:migrate
mv 20091220081126_create_line_items.rb 20100301000003_create_line_items.rb
(in /home/rubys/svn/rails4/Book/util/work/depot)
==  CreateLineItems: migrating ================================================
-- create_table(:line_items)
   -> 0.0016s
==  CreateLineItems: migrated (0.0018s) =======================================
 

Cart has many line items.

edit app/models/cart.rb
class Cart < ActiveRecord::Base
  has_many :line_items, :dependent=>:destroy
end

Product has many line items.

edit app/models/product.rb
class Product < ActiveRecord::Base
  has_many :line_items
 
  before_destroy :ok_to_delete?
 
  # ensure that there are no line items referencing this product
  def ok_to_delete?
    if line_items.empty?
      return true
    else
      errors.add_to_base "Line Items present"
      return false
    end
  end
 
  #...

Line item belongs to both Cart and Product (But slightly more to the Cart). Also provide convenient access to the total price of the line item

edit app/models/line_item.rb
class LineItem < ActiveRecord::Base
  belongs_to :product
  belongs_to :cart, :touch => true
end

9.3 Iteration D3: Adding a button

Now we connect the model objects we created to the controller and the view.

Add the button, connecting it to the Line Item Controller, passing the product id.

edit app/views/store/index.html.erb
      <%= button_to 'Add to Cart', line_items_path(:product_id => product) %>

Add a bit of style to make it show all on one line

edit public/stylesheets/depot.css
#store .entry form, #store .entry form div {
  display: inline;
}

Update the LineItem.new call to use find_cart and the product id. Additionally change the logic so that redirection upon success goes to the cart instead of the line item.

edit app/controllers/line_items_controller.rb
  # POST /line_items
  # POST /line_items.xml
  def create
    @line_item = LineItem.new(:cart=>find_cart, :product_id=>params[:product_id])
 
    respond_to do |format|
      if @line_item.save
        format.html { redirect_to(@line_item.cart, :notice => 'LineItem was successfully created.') }
        format.xml  { render :xml => @line_item, :status => :created, :location => @line_item }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @line_item.errors, :status => :unprocessable_entity }
      end
    end
  end

Try it once, and see that the output isn't very useful yet.

get /store

Your Pragmatic Catalog

Auto

Pragmatic Project Automation

Pragmatic Project Automation shows you how to improve the consistency and repeatability of your project's procedures using automation to reduce risk and errors.

Simply put, we're going to put this thing called a computer to work for you doing the mundane (but important) project stuff. That means you'll have more time and energy to do the really exciting---and difficult---stuff, like writing quality code.

$29.95
Utc

Pragmatic Unit Testing (C#)

Pragmatic programmers use feedback to drive their development and personal processes. The most valuable feedback you can get while coding comes from unit testing.

Without good tests in place, coding can become a frustrating game of "whack-a-mole." That's the carnival game where the player strikes at a mechanical mole; it retreats and another mole pops up on the opposite side of the field. The moles pop up and down so fast that you end up flailing your mallet helplessly as the moles continue to pop up where you least expect them.

$27.75
Svn

Pragmatic Version Control

This book is a recipe-based approach to using Subversion that will get you up and running quickly---and correctly. All projects need version control: it's a foundational piece of any project's infrastructure. Yet half of all project teams in the U.S. don't use any version control at all. Many others don't use it well, and end up experiencing time-consuming problems.

$28.50
post /line_items?product_id=2
You are being redirected.
get http://127.0.0.1:3000/carts/1

LineItem was successfully created.

Edit | Back

Update the template that shows the Cart.

edit app/views/carts/show.html.erb
<h2>Your Pragmatic Cart</h2>
<ul>    
  <% for item in @cart.line_items %>
    <li><%= item.product.title %></li>
  <% end %>
</ul>

Tell the cart controller to use the common store layout.

edit app/controllers/carts_controller.rb
class CartsController < ApplicationController
  layout 'store'
  # ...

Try it once again, and see that the products in the cart.

get /store

Your Pragmatic Catalog

Auto

Pragmatic Project Automation

Pragmatic Project Automation shows you how to improve the consistency and repeatability of your project's procedures using automation to reduce risk and errors.

Simply put, we're going to put this thing called a computer to work for you doing the mundane (but important) project stuff. That means you'll have more time and energy to do the really exciting---and difficult---stuff, like writing quality code.

$29.95
Utc

Pragmatic Unit Testing (C#)

Pragmatic programmers use feedback to drive their development and personal processes. The most valuable feedback you can get while coding comes from unit testing.

Without good tests in place, coding can become a frustrating game of "whack-a-mole." That's the carnival game where the player strikes at a mechanical mole; it retreats and another mole pops up on the opposite side of the field. The moles pop up and down so fast that you end up flailing your mallet helplessly as the moles continue to pop up where you least expect them.

$27.75
Svn

Pragmatic Version Control

This book is a recipe-based approach to using Subversion that will get you up and running quickly---and correctly. All projects need version control: it's a foundational piece of any project's infrastructure. Yet half of all project teams in the U.S. don't use any version control at all. Many others don't use it well, and end up experiencing time-consuming problems.

$28.50
post /line_items?product_id=2
You are being redirected.
get http://127.0.0.1:3000/carts/1

Your Pragmatic Cart

  • Pragmatic Version Control
  • Pragmatic Version Control
pub depot_f

9.4 Iteration D4: Creating a Smarter Cart

Change the cart to track the quantity of each product.

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

ruby script/generate migration add_quantity_to_line_item quantity:integer
      invoke  active_record
      create    db/migrate/20091220081131_add_quantity_to_line_item.rb

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

edit db/migrate/20091220081131_add_quantity_to_line_item.rb
class AddQuantityToLineItem < ActiveRecord::Migration
  def self.up
    add_column :line_items, :quantity, :integer, :default => 1
  end
 
  def self.down
    remove_column :line_items, :quantity
  end
end

Apply the migration

rake db:migrate
mv 20091220081131_add_quantity_to_line_item.rb 20100301000004_add_quantity_to_line_item.rb
(in /home/rubys/svn/rails4/Book/util/work/depot)
==  AddQuantityToLineItem: migrating ==========================================
-- add_column(:line_items, :quantity, :integer, {:default=>1})
   -> 0.0070s
==  AddQuantityToLineItem: migrated (0.0072s) =================================
 

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_id)
    current_item = line_items.find_by_product_id(product_id)
    if current_item
      current_item.quantity += 1
    else
      current_item = LineItem.new(:product_id=>product_id)
      line_items << current_item
    end
    current_item
  end

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

edit app/controllers/line_items_controller.rb
  # POST /line_items
  # POST /line_items.xml
  def create
    @line_item = find_cart.add_product(params[:product_id])
 
    respond_to do |format|
      if @line_item.save
        format.html { redirect_to(@line_item.cart, :notice => 'LineItem was successfully created.') }
        format.xml  { render :xml => @line_item, :status => :created, :location => @line_item }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @line_item.errors, :status => :unprocessable_entity }
      end
    end
  end

Update the view to show both columns.

edit app/views/carts/show.html.erb
<h2>Your Pragmatic Cart</h2>
<ul>    
  <% for item in @cart.line_items %>
    <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

Your Pragmatic Cart

  • 1 × Pragmatic Version Control
  • 1 × Pragmatic Version Control

Generate a migration to combine/separate items in carts.

ruby script/generate migration combine_items_in_cart
      invoke  active_record
      create    db/migrate/20091220081135_combine_items_in_cart.rb
edit db/migrate/20091220081135_combine_items_in_cart.rb
class CombineItemsInCart < ActiveRecord::Migration
 
  def self.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
      items = LineItem.sum(:quantity, :conditions=>{:cart_id=>cart.id},
                           :group=>:product_id)
 
      items.each do |product, quantity|
        if quantity > 1
          # remove individual items
          LineItem.delete_all :cart_id=>cart.id, :product_id=>product
 
          # replace with a single item
          LineItem.create :cart_id=>cart.id, :product_id=>product,
                          :quantity=>quantity
        end
      end
    end
  end
 
  def self.down
    # split items with quantity>1 into multiple items
    LineItem.find(:all, :conditions => "quantity>1").each do |li|
      # add individual items
      li.quantity.times do 
        LineItem.create :cart_id=>li.cart_id, :product_id=>li.product_id,
                        :quantity=>1
      end
 
      # remove original item
      li.destroy
    end
  end
end

Combine entries

rake db:migrate
mv 20091220081135_combine_items_in_cart.rb 20100301000005_combine_items_in_cart.rb
(in /home/rubys/svn/rails4/Book/util/work/depot)
==  CombineItemsInCart: migrating =============================================
==  CombineItemsInCart: migrated (0.2134s) ====================================
 

Verify that the entries have been combined.

get /carts/1

Your Pragmatic Cart

  • 2 × Pragmatic Version Control

Separate out individual items.

rake db:rollback
(in /home/rubys/svn/rails4/Book/util/work/depot)
==  CombineItemsInCart: reverting =============================================
==  CombineItemsInCart: reverted (0.2196s) ====================================
 

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

get /carts/1

Your Pragmatic Cart

  • 1 × Pragmatic Version Control
  • 1 × Pragmatic Version Control

Recombine the item data.

rake db:migrate
(in /home/rubys/svn/rails4/Book/util/work/depot)
==  CombineItemsInCart: migrating =============================================
==  CombineItemsInCart: migrated (0.2138s) ====================================
 

Add a few products to the order.

post /line_items?product_id=1
You are being redirected.
get http://127.0.0.1:3000/carts/1

Your Pragmatic Cart

  • 2 × Pragmatic Version Control
  • 1 × Pragmatic Project Automation
post /line_items?product_id=2
You are being redirected.
get http://127.0.0.1:3000/carts/1

Your Pragmatic Cart

  • 3 × Pragmatic Version Control
  • 1 × Pragmatic Project Automation
pub depot_g

Try something malicious.

get /carts/wibble

ActiveRecord::RecordNotFound in CartsController#show

Couldn't find Cart with ID=wibble

Rails.root: /home/rubys/svn/rails4/Book/util/work/depot

Application Trace | Framework Trace | Full Trace
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activerecord/lib/active_record/base.rb:1610:in `find_one'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activerecord/lib/active_record/base.rb:1593:in `find_from_ids'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activerecord/lib/active_record/base.rb:649:in `find'
/home/rubys/svn/rails4/Book/util/work/depot/app/controllers/carts_controller.rb:22:in `show'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/action_controller/base.rb:43:in `send_action'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/action_controller/base.rb:43:in `send_action'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/abstract_controller/base.rb:116:in `process_action'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/abstract_controller/callbacks.rb:18:in `process_action'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/callbacks.rb:432:in `_run__636904803__process_action__453433196__callbacks'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/callbacks.rb:402:in `send'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/callbacks.rb:402:in `_run_process_action_callbacks'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/callbacks.rb:87:in `send'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/callbacks.rb:87:in `run_callbacks'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/abstract_controller/callbacks.rb:17:in `process_action'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/abstract_controller/logger.rb:38:in `process_action'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/notifications/instrumenter.rb:14:in `instrument'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/notifications.rb:72:in `__send__'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/notifications.rb:72:in `instrument'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/notifications.rb:48:in `__send__'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/notifications.rb:48:in `instrument'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/abstract_controller/logger.rb:36:in `process_action'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/action_controller/metal/rendering_controller.rb:12:in `process_action'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/action_controller/metal/benchmarking.rb:34:in `process_action'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/core_ext/benchmark.rb:17:in `ms'
/home/rubys/.rvm/ruby-1.8.7-p174/lib/ruby/1.8/benchmark.rb:308:in `realtime'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/core_ext/benchmark.rb:17:in `ms'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/action_controller/metal/benchmarking.rb:34:in `process_action'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/action_controller/metal/compatibility.rb:76:in `process_action'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/action_controller/metal/flash.rb:178:in `process_action'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/action_controller/metal/rescue.rb:8:in `process_action'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/abstract_controller/base.rb:95:in `process'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/action_controller/metal/filter_parameter_logging.rb:62:in `process'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/action_controller/metal.rb:74:in `dispatch'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/action_controller/metal/rack_convenience.rb:15:in `dispatch'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/action_controller/metal.rb:98:in `call'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/action_dispatch/routing/route_set.rb:27:in `call'
/home/rubys/.rvm/gems/ruby/1.8.7/gems/rack-mount-0.3.2/lib/rack/mount/recognition/route_set.rb:71:in `call'
/home/rubys/.rvm/gems/ruby/1.8.7/gems/rack-mount-0.3.2/lib/rack/mount/recognition/code_generation.rb:76:in `recognize'
/home/rubys/.rvm/gems/ruby/1.8.7/gems/rack-mount-0.3.2/lib/rack/mount/recognition/code_generation.rb:60:in `optimized_each'
/home/rubys/.rvm/gems/ruby/1.8.7/gems/rack-mount-0.3.2/lib/rack/mount/recognition/code_generation.rb:75:in `recognize'
/home/rubys/.rvm/gems/ruby/1.8.7/gems/rack-mount-0.3.2/lib/rack/mount/recognition/route_set.rb:66:in `call'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/action_dispatch/routing/route_set.rb:407:in `call'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activerecord/lib/active_record/query_cache.rb:29:in `call'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/query_cache.rb:34:in `cache'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activerecord/lib/active_record/query_cache.rb:9:in `cache'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activerecord/lib/active_record/query_cache.rb:28:in `call'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb:365:in `call'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/action_dispatch/middleware/string_coercion.rb:25:in `call'
/home/rubys/git/rack/lib/rack/head.rb:9:in `call'
/home/rubys/git/rack/lib/rack/methodoverride.rb:24:in `call'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/action_dispatch/middleware/params_parser.rb:19:in `call'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb:106:in `call'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/action_dispatch/middleware/callbacks.rb:46:in `call'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/callbacks.rb:413:in `_run_call_callbacks'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/callbacks.rb:87:in `send'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/callbacks.rb:87:in `run_callbacks'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/action_dispatch/middleware/callbacks.rb:44:in `call'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/action_dispatch/middleware/show_exceptions.rb:44:in `call'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/actionpack/lib/action_dispatch/middleware/static.rb:30:in `call'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/railties/lib/rails/application.rb:119:in `call'
/home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/railties/lib/rails/application.rb:45:in `call'
/home/rubys/git/rack/lib/rack/builder.rb:77:in `call'
/home/rubys/git/rack/lib/rack/content_length.rb:13:in `call'
/home/rubys/git/rack/lib/rack/handler/webrick.rb:48:in `service'
/home/rubys/.rvm/ruby-1.8.7-p174/lib/ruby/1.8/webrick/httpserver.rb:104:in `service'
/home/rubys/.rvm/ruby-1.8.7-p174/lib/ruby/1.8/webrick/httpserver.rb:65:in `run'
/home/rubys/.rvm/ruby-1.8.7-p174/lib/ruby/1.8/webrick/server.rb:173:in `start_thread'
/home/rubys/.rvm/ruby-1.8.7-p174/lib/ruby/1.8/webrick/server.rb:162:in `start'
/home/rubys/.rvm/ruby-1.8.7-p174/lib/ruby/1.8/webrick/server.rb:162:in `start_thread'
/home/rubys/.rvm/ruby-1.8.7-p174/lib/ruby/1.8/webrick/server.rb:95:in `start'
/home/rubys/.rvm/ruby-1.8.7-p174/lib/ruby/1.8/webrick/server.rb:92:in `each'
/home/rubys/.rvm/ruby-1.8.7-p174/lib/ruby/1.8/webrick/server.rb:92:in `start'
/home/rubys/.rvm/ruby-1.8.7-p174/lib/ruby/1.8/webrick/server.rb:23:in `start'
/home/rubys/.rvm/ruby-1.8.7-p174/lib/ruby/1.8/webrick/server.rb:82:in `start'
/home/rubys/git/rack/lib/rack/handler/webrick.rb:14:in `run'
/home/rubys/git/gorp/lib/gorp.rb:592:in `restart_server'
makedepot.rb:176
makedepot.rb:2465

Request

Parameters:

{"id"=>"wibble"}

Show session dump

Response

Headers:

None

9.5 Iteration D5: Handling Errors

Log errors and show them on the screen.

Rescue error: log, flash, and redirect.

edit app/controllers/carts_controller.rb
  # GET /carts/1
  # GET /carts/1.xml
  def show
    @cart = Cart.find(params[:id])
 
    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @cart }
    end
  rescue ActiveRecord::RecordNotFound
    logger.error("Attempt to access invalid cart #{params[:id]}")
    redirect_to({:controller => :store}, :notice => 'Invalid cart')
  end

Reproduce the error.

get /carts/wibble
You are being redirected.
get http://127.0.0.1:3000/store

Your Pragmatic Catalog

Auto

Pragmatic Project Automation

Pragmatic Project Automation shows you how to improve the consistency and repeatability of your project's procedures using automation to reduce risk and errors.

Simply put, we're going to put this thing called a computer to work for you doing the mundane (but important) project stuff. That means you'll have more time and energy to do the really exciting---and difficult---stuff, like writing quality code.

$29.95
Utc

Pragmatic Unit Testing (C#)

Pragmatic programmers use feedback to drive their development and personal processes. The most valuable feedback you can get while coding comes from unit testing.

Without good tests in place, coding can become a frustrating game of "whack-a-mole." That's the carnival game where the player strikes at a mechanical mole; it retreats and another mole pops up on the opposite side of the field. The moles pop up and down so fast that you end up flailing your mallet helplessly as the moles continue to pop up where you least expect them.

$27.75
Svn

Pragmatic Version Control

This book is a recipe-based approach to using Subversion that will get you up and running quickly---and correctly. All projects need version control: it's a foundational piece of any project's infrastructure. Yet half of all project teams in the U.S. don't use any version control at all. Many others don't use it well, and end up experiencing time-consuming problems.

$28.50

Inspect the log.

tail -25 log/development.log
  builder (2.1.2) lib/builder/xmlbase.rb:134:in `call'
  builder (2.1.2) lib/builder/xmlbase.rb:134:in `_nested_structures'
  builder (2.1.2) lib/builder/xmlbase.rb:58:in `method_missing'
  /home/rubys/git/gorp/lib/gorp.rb:626
  makedepot.rb:2465
 
Attempt to access invalid cart wibble
  *[4;36;1msql (0.0ms)*[0m   *[0;1mSELECT *
FROM "carts"
WHERE (("carts"."id" = 0))*[0m
Redirected to http://127.0.0.1:3000/store
 
 
Processing CartsController#show to */* (for ::ffff:127.0.0.1 at 2009-12-20 03:11:43) [GET]
Completed in 14ms (DB: 0) | 302 [unknown]
  Parameters: {"id"=>"wibble"}
Rendering app/views/store/index.html.erb
  *[4;35;1msql (0.0ms)*[0m   *[0mSELECT *
FROM "products"
ORDER BY title*[0m
Rendering template within app/views/layouts/store.html.erb
 
 
Processing StoreController#index to */* (for ::ffff:127.0.0.1 at 2009-12-20 03:11:43) [GET]
Completed in 33ms (View: 26, DB: 0) | 200 [unknown]

Update the store layout to include a flash message.

edit app/views/layouts/store.html.erb
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Pragprog Books Online Store</title>
  <%= stylesheet_link_tag "depot", :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 %>
    </div>
  </div>
</body>
</html>

Update the css to place bold text in a red box.

edit public/stylesheets/depot.css
#notice {
  border: 2px solid red;
  padding: 1em;
  margin-bottom: 2em;
  background-color: #f0f0f0;
  font: bold smaller sans-serif;
}

Try one last time.

get /carts/wibble
You are being redirected.
get http://127.0.0.1:3000/store
Invalid cart

Your Pragmatic Catalog

Auto

Pragmatic Project Automation

Pragmatic Project Automation shows you how to improve the consistency and repeatability of your project's procedures using automation to reduce risk and errors.

Simply put, we're going to put this thing called a computer to work for you doing the mundane (but important) project stuff. That means you'll have more time and energy to do the really exciting---and difficult---stuff, like writing quality code.

$29.95
Utc

Pragmatic Unit Testing (C#)

Pragmatic programmers use feedback to drive their development and personal processes. The most valuable feedback you can get while coding comes from unit testing.

Without good tests in place, coding can become a frustrating game of "whack-a-mole." That's the carnival game where the player strikes at a mechanical mole; it retreats and another mole pops up on the opposite side of the field. The moles pop up and down so fast that you end up flailing your mallet helplessly as the moles continue to pop up where you least expect them.

$27.75
Svn

Pragmatic Version Control

This book is a recipe-based approach to using Subversion that will get you up and running quickly---and correctly. All projects need version control: it's a foundational piece of any project's infrastructure. Yet half of all project teams in the U.S. don't use any version control at all. Many others don't use it well, and end up experiencing time-consuming problems.

$28.50
pub depot_h

9.6 Iteration D6: Finishing the Cart

Add empty cart button, remove flash for line item create, add totals to view.

Add button to the view.

edit app/views/carts/show.html.erb
<h2>Your Pragmatic Cart</h2>
<ul>    
  <% for item in @cart.line_items %>
    <li><%= item.quantity %> &times; <%= item.product.title %></li>
  <% end %>
</ul>
 
<%= button_to 'Empty cart', @cart, :method => :delete,
    :confirm => 'Are you sure?' %>

Clear session and add flash notice when cart is destroyed.

edit app/controllers/carts_controller.rb
  # DELETE /carts/1
  # DELETE /carts/1.xml
  def destroy
    @cart = Cart.find(params[:id])
    @cart.destroy
    session[:cart_id] = nil
 
    respond_to do |format|
      format.html { redirect_to({:controller => :store}, :notice => 'Your cart is currently empty') }
      format.xml  { head :ok }
    end
  end

Try it out.

get /carts/1

Your Pragmatic Cart

  • 3 × Pragmatic Version Control
  • 1 × Pragmatic Project Automation
post /carts/1
You are being redirected.
get http://127.0.0.1:3000/store
Your cart is currently empty

Your Pragmatic Catalog

Auto

Pragmatic Project Automation

Pragmatic Project Automation shows you how to improve the consistency and repeatability of your project's procedures using automation to reduce risk and errors.

Simply put, we're going to put this thing called a computer to work for you doing the mundane (but important) project stuff. That means you'll have more time and energy to do the really exciting---and difficult---stuff, like writing quality code.

$29.95
Utc

Pragmatic Unit Testing (C#)

Pragmatic programmers use feedback to drive their development and personal processes. The most valuable feedback you can get while coding comes from unit testing.

Without good tests in place, coding can become a frustrating game of "whack-a-mole." That's the carnival game where the player strikes at a mechanical mole; it retreats and another mole pops up on the opposite side of the field. The moles pop up and down so fast that you end up flailing your mallet helplessly as the moles continue to pop up where you least expect them.

$27.75
Svn

Pragmatic Version Control

This book is a recipe-based approach to using Subversion that will get you up and running quickly---and correctly. All projects need version control: it's a foundational piece of any project's infrastructure. Yet half of all project teams in the U.S. don't use any version control at all. Many others don't use it well, and end up experiencing time-consuming problems.

$28.50
pub depot_h

Remove scaffolding generated flash notice for line item create.

edit app/controllers/line_items_controller.rb
  # POST /line_items
  # POST /line_items.xml
  def create
    @line_item = find_cart.add_product(params[:product_id])
 
    respond_to do |format|
      if @line_item.save
        format.html { redirect_to(@line_item.cart) }
        format.xml  { render :xml => @line_item, :status => :created, :location => @line_item }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @line_item.errors, :status => :unprocessable_entity }
      end
    end
  end

Update the view to add totals.

edit app/views/carts/show.html.erb
<div class="cart_title">Your Cart</div>
<table>
  <% for item in @cart.line_items %>
    <tr>
      <td><%= item.quantity %>&times;</td>
      <td><%= item.product.title %></td>
      <td class="item_price"><%= number_to_currency(item.total_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', @cart, :method => :delete,
    :confirm => 'Are you sure?' %>

Add a method to compute the total price of a single line item.

edit app/models/line_item.rb

Add a method to compute the total price of the items in the cart.

edit app/models/cart.rb
  def total_price
    @line_items.to_a.sum { |item| item.total_price }
  end

Add some style.

edit public/stylesheets/depot.css
/* Styles for the cart in the main page */
 
.cart-title {
  font: 120% bold;
}
 
.item-price, .total-line {
  text-align: right;
}
 
.total-line .total-cell {
  font-weight: bold;
  border-top: 1px solid #595;
}

Add a product to the cart, and see the total.

get /store

Your Pragmatic Catalog

Auto

Pragmatic Project Automation

Pragmatic Project Automation shows you how to improve the consistency and repeatability of your project's procedures using automation to reduce risk and errors.

Simply put, we're going to put this thing called a computer to work for you doing the mundane (but important) project stuff. That means you'll have more time and energy to do the really exciting---and difficult---stuff, like writing quality code.

$29.95
Utc

Pragmatic Unit Testing (C#)

Pragmatic programmers use feedback to drive their development and personal processes. The most valuable feedback you can get while coding comes from unit testing.

Without good tests in place, coding can become a frustrating game of "whack-a-mole." That's the carnival game where the player strikes at a mechanical mole; it retreats and another mole pops up on the opposite side of the field. The moles pop up and down so fast that you end up flailing your mallet helplessly as the moles continue to pop up where you least expect them.

$27.75
Svn

Pragmatic Version Control

This book is a recipe-based approach to using Subversion that will get you up and running quickly---and correctly. All projects need version control: it's a foundational piece of any project's infrastructure. Yet half of all project teams in the U.S. don't use any version control at all. Many others don't use it well, and end up experiencing time-consuming problems.

$28.50
post /line_items?product_id=1
You are being redirected.
get http://127.0.0.1:3000/carts/2
Your Cart
Pragmatic Project Automation $29.95
Total $29.95

Add a few more products, and watch the totals climb!

post /line_items?product_id=1
You are being redirected.
get http://127.0.0.1:3000/carts/2
Your Cart
Pragmatic Project Automation $59.90
Total $59.90
post /line_items?product_id=2
You are being redirected.
get http://127.0.0.1:3000/carts/2
Your Cart
Pragmatic Project Automation $59.90
Pragmatic Version Control $28.50
Total $88.40
pub depot_i

9.7 Playtime

Once again, get the tests working, and also build a migration to combine (and separate) line items in a cart.

See that the tests fail.

rake test
(in /home/rubys/svn/rails4/Book/util/work/depot)
DEPRECATION WARNING: ActiveSupport::DeprecatedCallbacks has been deprecated in favor of ActiveSupport::Callbacks. (called from included at /home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:7)
Loaded suite /home/rubys/.rvm/gems/ruby/1.8.7/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
.......
Finished in 0.21151 seconds.
 
7 tests, 25 assertions, 0 failures, 0 errors
DEPRECATION WARNING: ActiveSupport::DeprecatedCallbacks has been deprecated in favor of ActiveSupport::Callbacks. (called from included at /home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:7)
Loaded suite /home/rubys/.rvm/gems/ruby/1.8.7/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
.F.....F..............
Finished in 1.968497 seconds.
 
  1) Failure:
test_should_destroy_cart(CartsControllerTest) [/test/functional/carts_controller_test.rb:43]:
Expected response to be a redirect to <http://test.host/carts> but was a redirect to <http://test.host/store>.
 
  2) Failure:
test_should_create_line_item(LineItemsControllerTest) [/test/functional/line_items_controller_test.rb:20]:
Expected response to be a redirect to <http://test.host/line_items/2053932786> but was a redirect to <http://test.host/carts/2053932786>.
 
22 tests, 40 assertions, 2 failures, 0 errors
Errors running test:functionals!

Substitute names of products and carts for numbers

edit test/fixtures/line_items.yml
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
 
one:
  product_id: one
  cart_id: one
 
two:
  product_id: one
  cart_id: 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
      delete :destroy, :id => carts(:one).to_param
    end
 
    assert_redirected_to :controller => :store
  end

Update expected target of redirect: LineItem#create.

edit test/functional/line_items_controller_test.rb
  test "should create line_item" do
    assert_difference('LineItem.count') do
      post :create, :line_item => { }
    end
 
    assert_redirected_to cart_path(LineItem.last.cart_id)
  end

Verify that the tests now pass.

rake test
(in /home/rubys/svn/rails4/Book/util/work/depot)
DEPRECATION WARNING: ActiveSupport::DeprecatedCallbacks has been deprecated in favor of ActiveSupport::Callbacks. (called from included at /home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:7)
Loaded suite /home/rubys/.rvm/gems/ruby/1.8.7/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
.......
Finished in 0.20842 seconds.
 
7 tests, 25 assertions, 0 failures, 0 errors
DEPRECATION WARNING: ActiveSupport::DeprecatedCallbacks has been deprecated in favor of ActiveSupport::Callbacks. (called from included at /home/rubys/svn/rails4/Book/util/work/depot/vendor/rails/activesupport/lib/active_support/testing/setup_and_teardown.rb:7)
Loaded suite /home/rubys/.rvm/gems/ruby/1.8.7/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
......................
Finished in 0.500283 seconds.
 
22 tests, 38 assertions, 0 failures, 0 errors

10.1 Iteration E1: Moving the Cart

Refactor the cart view into partials, and reference the result from the layout.

Create a "partial" view, for just one line item

edit app/views/carts/_line_item.html.erb
<tr>
  <td><%= line_item.quantity %>&times;</td>
  <td><%= line_item.product.title %></td>
  <td class="item_price"><%= number_to_currency(line_item.total_price) %></td>
</tr>

Replace that portion of the view with a callout to the partial

edit app/views/carts/show.html.erb
<div class="cart_title">Your Cart</div>
<table>
  <%= render(:partial => "line_item", :collection => @cart.line_items) if @cart %>  
  
  <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', @cart, :method => :delete,
    :confirm => 'Are you sure?' %>

Make a copy as a partial for the store controller

cp app/views/carts/show.html.erb app/views/store/_cart.html.erb

Modify the copy to reference the (sub)partial and take input from @cart

edit app/views/store/_cart.html.erb
<div class="cart_title">Your Cart</div>
<table>
  <%= render(:partial => "carts/line_item", :collection => cart.line_items) if cart %>  
  
  <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', cart, :method => :delete,
    :confirm => 'Are you sure?' %>

Insert a call in the controller to find the cart

edit app/controllers/store_controller.rb
  def index
    @products = Product.all
    @cart = find_cart
  end

Reference the partial from the layout.

edit app/views/layouts/store.html.erb
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Pragprog Books Online Store</title>
  <%= stylesheet_link_tag "depot", :media => "all" %>
</head>
<body id="store">
  <div id="banner">
    <%= image_tag("logo.png") %>
    <%= @page_title || "Pragmatic Bookshelf" %>
  </div>
  <div id="columns">
    <div id="side">
      <div id="cart">
        <%= render(:partial => "store/cart", :object => @cart) %>
      </div>
 
      <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 %>
    </div>
  </div>
</body>
</html>

Add a small bit of style.

edit public/stylesheets/depot.css
/* Styles for the cart in the sidebar */
 
#cart, #cart table {
  font-size: smaller;
  color:     white;
}
 
#cart table {
  border-top:    1px dotted #595;
  border-bottom: 1px dotted #595;
  margin-bottom: 10px;
}
pub depot_j

Change the redirect to be back to the store.

edit app/controllers/line_items_controller.rb
  # POST /line_items
  # POST /line_items.xml
  def create
    @line_item = find_cart.add_product(params[:product_id])
 
    respond_to do |format|
      if @line_item.save
        format.html { redirect_to(:controller => :store) }
        format.xml  { render :xml => @line_item, :status => :created, :location => @line_item }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @line_item.errors, :status => :unprocessable_entity }
      end
    end
  end

Purchase another product.

get /store
Your Cart
Pragmatic Project Automation $59.90
Pragmatic Version Control $28.50
Total $88.40
Home
Questions
News
Contact

Your Pragmatic Catalog

Auto

Pragmatic Project Automation

Pragmatic Project Automation shows you how to improve the consistency and repeatability of your project's procedures using automation to reduce risk and errors.

Simply put, we're going to put this thing called a computer to work for you doing the mundane (but important) project stuff. That means you'll have more time and energy to do the really exciting---and difficult---stuff, like writing quality code.

$29.95
Utc

Pragmatic Unit Testing (C#)

Pragmatic programmers use feedback to drive their development and personal processes. The most valuable feedback you can get while coding comes from unit testing.

Without good tests in place, coding can become a frustrating game of "whack-a-mole." That's the carnival game where the player strikes at a mechanical mole; it retreats and another mole pops up on the opposite side of the field. The moles pop up and down so fast that you end up flailing your mallet helplessly as the moles continue to pop up where you least expect them.

$27.75
Svn

Pragmatic Version Control

This book is a recipe-based approach to using Subversion that will get you up and running quickly---and correctly. All projects need version control: it's a foundational piece of any project's infrastructure. Yet half of all project teams in the U.S. don't use any version control at all. Many others don't use it well, and end up experiencing time-consuming problems.

$28.50
post /line_items?product_id=2
You are being redirected.
get http://127.0.0.1:3000/store
Your Cart
Pragmatic Project Automation $59.90
Pragmatic Version Control $57.00
Total $116.90
Home
Questions
News
Contact

Your Pragmatic Catalog

Auto

Pragmatic Project Automation

Pragmatic Project Automation shows you how to improve the consistency and repeatability of your project's procedures using automation to reduce risk and errors.

Simply put, we're going to put this thing called a computer to work for you doing the mundane (but important) project stuff. That means you'll have more time and energy to do the really exciting---and difficult---stuff, like writing quality code.

$29.95
Utc

Pragmatic Unit Testing (C#)

Pragmatic programmers use feedback to drive their development and personal processes. The most valuable feedback you can get while coding comes from unit testing.

Without good tests in place, coding can become a frustrating game of "whack-a-mole." That's the carnival game where the player strikes at a mechanical mole; it retreats and another mole pops up on the opposite side of the field. The moles pop up and down so fast that you end up flailing your mallet helplessly as the moles continue to pop up where you least expect them.

$27.75
Svn

Pragmatic Version Control

This book is a recipe-based approach to using Subversion that will get you up and running quickly---and correctly. All projects need version control: it's a foundational piece of any project's infrastructure. Yet half of all project teams in the U.S. don't use any version control at all. Many others don't use it well, and end up experiencing time-consuming problems.

$28.50
pub depot_k

10.2 Iteration E2: Creating an AJAX-Based Cart

edit app/views/store/index.html.erb
    <% form_remote_tag :url => { :controller => 'line_items', :action => 'create', :id => product } do %>
      <%= submit_tag "Add to Cart" %>
    <% end %>
edit app/views/layouts/store.html.erb
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
  <title>Pragprog Books Online Store</title>
  <%= stylesheet_link_tag "depot", :media => "all" %>
  <%= javascript_include_tag :defaults %>
</head>
edit app/controllers/line_items_controller.rb
  # POST /line_items
  # POST /line_items.xml
  def create
    @line_item = find_cart.add_product(params[:product_id])
 
    respond_to do |format|
      if @line_item.save
        format.html { redirect_to(:controller => :store) }
        format.js
        format.xml  { render :xml => @line_item, :status => :created, :location => @line_item }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @line_item.errors, :status => :unprocessable_entity }
      end
    end
  end
edit app/views/line_items/new.js.rjs
page.replace_html("cart", :partial => "cart", :object => @cart)
pub depot_l

10.3 Iteration E3: Highlighting Changes

edit app/controllers/line_items_controller.rb
edit app/views/carts/_line_item.html.erb
<% if line_item == @current_item %>
  <tr id="current_item">
<% else %>
  <tr>
<% end %>
  <td><%= line_item.quantity %>&times;</td>
  <td><%= line_item.product.title %></td>
  <td class="item_price"><%= number_to_currency(line_item.total_price) %></td>
</tr>
edit app/views/store/add_to_cart.js.rjs
 
 
page[:current_item].visual_effect :highlight,
                                  :startcolor => "#88ff88",
                                  :endcolor => "#114411"
pub depot_m

10.4 Iteration E4: Hide an Empty Cart

edit app/views/store/add_to_cart.js.rjs
 
 
page[:cart].visual_effect :blind_down if @cart.line_items.count == 1
 
page[:current_item].visual_effect :highlight,
                                  :startcolor => "#88ff88",
                                  :endcolor => "#114411"
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 = LineItem.new(:product_id=>product_id)
      line_items << current_item
    end
    current_item
  end
 
  def total_price
    @line_items.to_a.sum { |item| item.total_price }
  end
 
  def total_items
    @line_items.sum(:quantity)
  end
end
ls -p app
controllers/
helpers/
models/
views/
ls -p app/helpers
application_helper.rb
carts_helper.rb
line_items_helper.rb
products_helper.rb
store_helper.rb
edit app/views/layouts/store.html.erb
      <% hidden_div_if(@cart.line_items.empty?, :id => "cart") do %>
        <%= render(:partial => "store/cart", :object => @cart) %>
      <% end %>
edit app/helpers/store_helper.rb
module StoreHelper
  def hidden_div_if(condition, attributes = {}, &block)
    if condition
      attributes["style"] = "display: none"
    end
    content_tag("div", attributes, &block)
  end
end
edit app/controllers/carts_controller.rb
#<IndexError: regexp not matched>
  makedepot.rb:1254:in `[]='
  makedepot.rb:1254
  /home/rubys/git/gorp/lib/gorp.rb:211:in `edit'
  makedepot.rb:1252
  /home/rubys/git/gorp/lib/gorp.rb:699:in `call'
  /home/rubys/git/gorp/lib/gorp.rb:699
  /home/rubys/git/gorp/lib/gorp.rb:689:in `each'
  /home/rubys/git/gorp/lib/gorp.rb:689
  /home/rubys/.rvm/gems/ruby/1.8.7/gems/builder-2.1.2/lib/builder/xmlbase.rb:134:in `call'
  /home/rubys/.rvm/gems/ruby/1.8.7/gems/builder-2.1.2/lib/builder/xmlbase.rb:134:in `_nested_structures'
  /home/rubys/.rvm/gems/ruby/1.8.7/gems/builder-2.1.2/lib/builder/xmlbase.rb:58:in `method_missing'
  /home/rubys/git/gorp/lib/gorp.rb:662
  /home/rubys/.rvm/gems/ruby/1.8.7/gems/builder-2.1.2/lib/builder/xmlbase.rb:134:in `call'
  /home/rubys/.rvm/gems/ruby/1.8.7/gems/builder-2.1.2/lib/builder/xmlbase.rb:134:in `_nested_structures'
  /home/rubys/.rvm/gems/ruby/1.8.7/gems/builder-2.1.2/lib/builder/xmlbase.rb:58:in `method_missing'
  /home/rubys/git/gorp/lib/gorp.rb:626
  makedepot.rb:2465
    
class CartsController < ApplicationController
  layout 'store'
  # ...
  # GET /carts
  # GET /carts.xml
  def index
    @carts = Cart.all
 
    respond_to do |format|
      format.html # index.html.erb
      format.xml  { render :xml => @carts }
    end
  end
 
  # GET /carts/1
  # GET /carts/1.xml
  def show
    @cart = Cart.find(params[:id])
 
    respond_to do |format|
      format.html # show.html.erb
      format.xml  { render :xml => @cart }
    end
  rescue ActiveRecord::RecordNotFound
    logger.error("Attempt to access invalid cart #{params[:id]}")
    redirect_to({:controller => :store}, :notice => 'Invalid cart')
  end
 
  # GET /carts/new
  # GET /carts/new.xml
  def new
    @cart = Cart.new
 
    respond_to do |format|
      format.html # new.html.erb
      format.xml  { render :xml => @cart }
    end
  end
 
  # GET /carts/1/edit
  def edit
    @cart = Cart.find(params[:id])
  end
 
  # POST /carts
  # POST /carts.xml
  def create
    @cart = Cart.new(params[:cart])
 
    respond_to do |format|
      if @cart.save
        format.html { redirect_to(@cart, :notice => 'Cart was successfully created.') }
        format.xml  { render :xml => @cart, :status => :created, :location => @cart }
      else
        format.html { render :action => "new" }
        format.xml  { render :xml => @cart.errors, :status => :unprocessable_entity }
      end
    end
  end
 
  # PUT /carts/1
  # PUT /carts/1.xml
  def update
    @cart = Cart.find(params[:id])
 
    respond_to do |format|
      if @cart.update_attributes(params[:cart])
        format.html { redirect_to(@cart, :notice => 'Cart was successfully updated.') }
        format.xml  { head :ok }
      else
        format.html { render :action => "edit" }
        format.xml  { render :xml => @cart.errors, :status => :unprocessable_entity }
      end
    end
  end
 
  # DELETE /carts/1
  # DELETE /carts/1.xml
  def destroy
    @cart = Cart.find(params[:id])
    @cart.destroy
    session[:cart_id] = nil
 
    respond_to do |format|
      format.html { redirect_to({:controller => :store}, :notice => 'Your cart is currently empty') }
      format.xml  { head :ok }
    end
  end
end
pub depot_n

10.5 Iteration E5: Degrading If Javascript Is Disabled

get /carts/2
Your Cart
Pragmatic Project Automation $59.90
Pragmatic Version Control $57.00
Total $116.90
Home
Questions
News
Contact
Your Cart
Pragmatic Project Automation $59.90
Pragmatic Version Control $57.00
Total $116.90
post /carts/2
You are being redirected.
get http://127.0.0.1:3000/store
Home
Questions
News
Contact
Your cart is currently empty

Your Pragmatic Catalog

Auto

Pragmatic Project Automation

Pragmatic Project Automation shows you how to improve the consistency and repeatability of your project's procedures using automation to reduce risk and errors.

Simply put, we're going to put this thing called a computer to work for you doing the mundane (but important) project stuff. That means you'll have more time and energy to do the really exciting---and difficult---stuff, like writing quality code.

$29.95
Utc

Pragmatic Unit Testing (C#)

Pragmatic programmers use feedback to drive their development and personal processes. The most valuable feedback you can get while coding comes from unit testing.

Without good tests in place, coding can become a frustrating game of "whack-a-mole." That's the carnival game where the player strikes at a mechanical mole; it retreats and another mole pops up on the opposite side of the field. The moles pop up and down so fast that you end up flailing your mallet helplessly as the moles continue to pop up where you least expect them.

$27.75
Svn

Pragmatic Version Control

This book is a recipe-based approach to using Subversion that will get you up and running quickly---and correctly. All projects need version control: it's a foundational piece of any project's infrastructure. Yet half of all project teams in the U.S. don't use any version control at all. Many others don't use it well, and end up experiencing time-consuming problems.

$28.50
get /store
Home
Questions
News
Contact

Your Pragmatic Catalog

Auto

Pragmatic Project Automation

Pragmatic Project Automation shows you how to improve the consistency and repeatability of your project's procedures using automation to reduce risk and errors.

Simply put, we're going to put this thing called a computer to work for you doing the mundane (but important) project stuff. That means you'll have more time and energy to do the really exciting---and difficult---stuff, like writing quality code.

$29.95
Utc

Pragmatic Unit Testing (C#)

Pragmatic programmers use feedback to drive their development and personal processes. The most valuable feedback you can get while coding comes from unit testing.

Without good tests in place, coding can become a frustrating game of "whack-a-mole." That's the carnival game where the player strikes at a mechanical mole; it retreats and another mole pops up on the opposite side of the field. The moles pop up and down so fast that you end up flailing your mallet helplessly as the moles continue to pop up where you least expect them.

$27.75
Svn

Pragmatic Version Control

This book is a recipe-based approach to using Subversion that will get you up and running quickly---and correctly. All projects need version control: it's a foundational piece of any project's infrastructure. Yet half of all project teams in the U.S. don't use any version control at all. Many others don't use it well, and end up experiencing time-consuming problems.

$28.50
post /line_items?id=1
You are being redirected.
get http://127.0.0.1:3000/store
Your Cart
Pragmatic Version Control $28.50
Total $28.50
Home
Questions
News
Contact

Your Pragmatic Catalog

Auto

Pragmatic Project Automation

Pragmatic Project Automation shows you how to improve the consistency and repeatability of your project's procedures using automation to reduce risk and errors.

Simply put, we're going to put this thing called a computer to work for you doing the mundane (but important) project stuff. That means you'll have more time and energy to do the really exciting---and difficult---stuff, like writing quality code.

$29.95
Utc

Pragmatic Unit Testing (C#)

Pragmatic programmers use feedback to drive their development and personal processes. The most valuable feedback you can get while coding comes from unit testing.

Without good tests in place, coding can become a frustrating game of "whack-a-mole." That's the carnival game where the player strikes at a mechanical mole; it retreats and another mole pops up on the opposite side of the field. The moles pop up and down so fast that you end up flailing your mallet helplessly as the moles continue to pop up where you least expect them.

$27.75
Svn

Pragmatic Version Control

This book is a recipe-based approach to using Subversion that will get you up and running quickly---and correctly. All projects need version control: it's a foundational piece of any project's infrastructure. Yet half of all project teams in the U.S. don't use any version control at all. Many others don't use it well, and end up experiencing time-consuming problems.

$28.50
pub depot_o

Environment

Sun, 20 Dec 2009 08:12:06 GMT
/home/rubys/.rvm/ruby-1.8.7-p174/bin/ruby -v
ruby 1.8.7 (2009-06-12 patchlevel 174) [i686-linux]
gem -v
1.3.5
gem list
abstract (1.0.0)
builder (2.1.2)
erubis (2.6.5)
gemcutter (0.2.1, 0.1.8, 0.1.7, 0.1.6)
gorp (0.13.0, 0.11.0, 0.10.1, 0.10.0, 0.9.0, 0.8.2, 0.7.2)
htmlentities (4.2.0)
json_pure (1.2.0, 1.1.9)
net-scp (1.0.2)
net-ssh (2.0.17, 2.0.16, 2.0.15)
rack (1.0.1)
rack-mount (0.3.2, 0.3.0, 0.2.3, 0.2.0)
rack-test (0.5.3, 0.5.2, 0.5.1)
rake (0.8.7)
rdoc (2.4.3)
sqlite3-ruby (1.2.5)
tzinfo (0.3.15)
will_paginate (2.3.11)
echo $RUBYLIB | sed "s/:/\n/g"
/home/rubys/git/gorp/lib
/home/rubys/git/arel/lib
/home/rubys/git/rack/lib
ruby /home/rubys/git/rails/railties/bin/rails -v
Rails 3.0.pre
git log -1
commit 2419fae092ec207185f9ed69c2aa1ba1cd53fffe    
Author: Joshua Peek <josh@joshpeek.com>
Date:   Thu Dec 17 22:10:06 2009 -0600

    
    Pending tests for AD Response

Todos

None!