Rails from the Inside Out

Rails from the Inside Out

Table of Contents

Development Log

rails -v
Rails 2.3.2
ruby -v
ruby 1.8.7 (2008-08-11 patchlevel 72) [x86_64-linux]
gem -v
1.3.3

1.1 XML to Raw SQLite3

Our store resells products, so lets start with a list of products provided by our supplier. We are ultimately going to want to do things with these products, so lets load them into a database.

Start with some XML, listing a number of products.

edit testdata.xml
<?xml version="1.0" encoding="UTF-8"?>
<products type="array">
  <product>
    <created-at type="datetime">2009-07-05T16:26:31Z</created-at>
    <description>&lt;p&gt;
       &lt;em&gt;Pragmatic Project Automation&lt;/em&gt; shows you how to improve the 
       consistency and repeatability of your project's procedures using 
       automation to reduce risk and errors.
      &lt;/p&gt;
      &lt;p&gt;
        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.
      &lt;/p&gt;</description>
    <id type="integer">2</id>
    <image-url>/images/auto.jpg</image-url>
    <price type="decimal">24.95</price>
    <title>Pragmatic Project Automation</title>
    <updated-at type="datetime">2009-07-05T16:32:09Z</updated-at>
  </product>
  <product>
    <created-at type="datetime">2009-07-05T16:26:31Z</created-at>
    <description>&lt;p&gt;
         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.
      &lt;/p&gt;</description>
    <id type="integer">3</id>
    <image-url>/images/svn.jpg</image-url>
    <price type="decimal">28.5</price>
    <title>Pragmatic Version Control</title>
    <updated-at type="datetime">2009-07-05T16:26:31Z</updated-at>
  </product>
  <product>
    <created-at type="datetime">2009-07-05T16:26:31Z</created-at>
    <description>&lt;p&gt;
        Pragmatic programmers use feedback to drive their development and 
        personal processes. The most valuable feedback you can get while 
        coding comes from unit testing.
      &lt;/p&gt;
      &lt;p&gt;
        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.
      &lt;/p&gt;</description>
    <id type="integer">4</id>
    <image-url>/images/utc.jpg</image-url>
    <price type="decimal">27.75</price>
    <title>Pragmatic Unit Testing (C#)</title>
    <updated-at type="datetime">2009-07-05T16:26:31Z</updated-at>
  </product>
</products>

Test that loading the XML produces the right data in the database.

edit test_products.rb
require 'test/unit'
 
class Product_Load_Test < Test::Unit::TestCase
  def setup
    @db = `sqlite3 -line products.db 'select * from products'`
  end
 
  def test_automate
    assert_match /title = Pragmatic Project Automation/, @db
    assert_match /description = <p>\s+<em>Pragmatic Project Automation/, @db
    assert_match %r|image_url = /images/auto.jpg|, @db
    assert_match /price = 24.95/, @db
  end
 
  def test_version
    assert_match /title = Pragmatic Version Control/, @db
    assert_match /description = <p>\s+This book is a recipe-based approach/, @db
    assert_match %r|image_url = /images/svn.jpg|, @db
    assert_match /price = 28.5/, @db
  end
 
  def test_unit
    assert_match /title = Pragmatic Unit Testing/, @db
    assert_match /description = <p>\s+Pragmatic programmers use feedback/, @db
    assert_match %r|image_url = /images/utc.jpg|, @db
    assert_match /price = 27.75/, @db
  end
end

Run that test, watch it fail.

ruby test_products.rb
Loaded suite test_products
Started
SQL error: no such table: products
SQL error: no such table: products
SQL error: no such table: products
FFF
Finished in 0.052071 seconds.
 
  1) Failure:
test_automate(Product_Load_Test) [test_products.rb:9]:
<""> expected to be =~
</title = Pragmatic Project Automation/>.
 
  2) Failure:
test_unit(Product_Load_Test) [test_products.rb:23]:
<""> expected to be =~
</title = Pragmatic Unit Testing/>.
 
  3) Failure:
test_version(Product_Load_Test) [test_products.rb:16]:
<""> expected to be =~
</title = Pragmatic Version Control/>.
 
3 tests, 3 assertions, 3 failures, 0 errors

Now write the code that loads the database from XML.

edit load_products.rb
require 'rexml/document'
require 'sqlite3'
 
input  = File.new('testdata.xml')
output = SQLite3::Database.new('products.db')
 
output.execute_batch <<SQL
  CREATE TABLE products (
    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
    title varchar(255), 
    description text, 
    image_url varchar(255),
    price decimal(8,2) DEFAULT 0
  );
SQL
 
REXML::Document.new(input).each_element('//product') do |product|
  output.execute 'INSERT INTO products(title,description,image_url,price)' +
    ' VALUES (?, ?, ?, ?)',
    product.elements['title'].text,
    product.elements['description'].text,
    product.elements['image-url'].text,
    product.elements['price'].text
end
 
input.close
output.close

Verify that the this code does what it is intended to do.

ruby load_products.rb
ruby test_products.rb
Loaded suite test_products
Started
...
Finished in 0.010379 seconds.
 
3 tests, 12 assertions, 0 failures, 0 errors

Try it a second time -- see a problem.

ruby load_products.rb
/usr/lib/ruby/1.8/sqlite3/errors.rb:62:in `check': table products already exists (SQLite3::SQLException)
	from /usr/lib/ruby/1.8/sqlite3/statement.rb:39:in `initialize'
	from /usr/lib/ruby/1.8/sqlite3/database.rb:154:in `new'
	from /usr/lib/ruby/1.8/sqlite3/database.rb:154:in `prepare'
	from /usr/lib/ruby/1.8/sqlite3/database.rb:225:in `execute_batch'
	from load_products.rb:7

Before proceeding, set up git.

cat /home/rubys/.gitconfig
[user]
name = Sam Ruby
email = rubys@intertwingly.net
 
[alias]
st = status

Verify the configuration.

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

Initialize a repository for the code.

git init
Initialized empty Git repository in /home/rubys/git/awdwr/work/depot/.git/

Add everything in the current directory.

git add .

Commit the changes.

git commit -m "load via raw SQLite3"
Created initial commit a679b97: load via raw SQLite3
 4 files changed, 114 insertions(+), 0 deletions(-)
 create mode 100644 load_products.rb
 create mode 100644 products.db
 create mode 100644 test_products.rb
 create mode 100644 testdata.xml

1.2 Update Using Raw SQLite3

At this point, we could simply just add a DROP TABLE IF EXISTS to the SQL, but that's a wee bit drastic. Over time, we are going to want to add columns (e.g., quantity_on_hand), so lets match products in the database against the contents of the XML by their original ("base") id, and update existing rows if they are already present, adding new rows when they are not.

Conditionally CREATE table, match based on id, and UPDATE when found.

edit load_products.rb
require 'rexml/document'
require 'sqlite3'
 
input  = File.new('testdata.xml')
output = SQLite3::Database.new('products.db')
 
unless output.execute('select name from sqlite_master').include? ["products"]
  output.execute_batch <<-SQL
    CREATE TABLE products (
      id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
      base_id INTEGER,
      title varchar(255), 
      description text, 
      image_url varchar(255),
      price decimal(8,2) DEFAULT 0
    );
  SQL
end
 
REXML::Document.new(input).each_element('//product') do |product|
  title       = product.elements['title'].text
  description = product.elements['description'].text
  image_url   = product.elements['image-url'].text
  price       = product.elements['price'].text
 
  base_id     = product.elements['id'].text
 
  id = output.execute('SELECT id FROM products WHERE base_id=?', base_id)
  if id.empty?
    output.execute 'INSERT INTO products' +
      '(base_id, title, description, image_url, price)' +
      ' VALUES (?, ?, ?, ?, ?)', base_id, title, description, image_url, price
  else
    output.execute 'UPDATE products ' +
      'SET base_id=?, title=?, description=?, image_url=?, price=? WHERE id=?',
      base_id, title, description, image_url, price, id.first
  end
end
 
input.close
output.close

Run the same test as before.

ruby load_products.rb
/usr/lib/ruby/1.8/sqlite3/errors.rb:62:in `check': no such column: base_id (SQLite3::SQLException)
	from /usr/lib/ruby/1.8/sqlite3/statement.rb:39:in `initialize'
	from /usr/lib/ruby/1.8/sqlite3/database.rb:154:in `new'
	from /usr/lib/ruby/1.8/sqlite3/database.rb:154:in `prepare'
	from /usr/lib/ruby/1.8/sqlite3/database.rb:181:in `execute'
	from load_products.rb:28
	from /usr/lib/ruby/1.8/rexml/element.rb:892:in `each'
	from /usr/lib/ruby/1.8/rexml/xpath.rb:53:in `each'
	from /usr/lib/ruby/1.8/rexml/element.rb:892:in `each'
	from /usr/lib/ruby/1.8/rexml/element.rb:393:in `each_element'
	from load_products.rb:20

Note a problem. For now, simply delete the database and try again.

rm products.db
ruby load_products.rb
ruby test_products.rb
Loaded suite test_products
Started
...
Finished in 0.018474 seconds.
 
3 tests, 12 assertions, 0 failures, 0 errors

Try again.

ruby load_products.rb
ruby test_products.rb
Loaded suite test_products
Started
...
Finished in 0.01182 seconds.
 
3 tests, 12 assertions, 0 failures, 0 errors

See what files have changed.

git status
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#
#	modified:   load_products.rb
#	modified:   products.db
#
no changes added to commit (use "git add" and/or "git commit -a")

See what the changes were.

git diff
diff --git a/load_products.rb b/load_products.rb
index 83e8720..c9568b6 100644
--- a/load_products.rb
+++ b/load_products.rb
@@ -4,23 +4,37 @@ require 'sqlite3'
 input  = File.new('testdata.xml')
 output = SQLite3::Database.new('products.db')
 
-output.execute_batch <<SQL
-  CREATE TABLE products (
-    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
-    title varchar(255), 
-    description text, 
-    image_url varchar(255),
-    price decimal(8,2) DEFAULT 0
-  );
-SQL
+unless output.execute('select name from sqlite_master').include? ["products"]
+  output.execute_batch <<-SQL
+    CREATE TABLE products (
+      id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+      base_id INTEGER,
+      title varchar(255), 
+      description text, 
+      image_url varchar(255),
+      price decimal(8,2) DEFAULT 0
+    );
+  SQL
+end
 
 REXML::Document.new(input).each_element('//product') do |product|
-  output.execute 'INSERT INTO products(title,description,image_url,price)' +
-    ' VALUES (?, ?, ?, ?)',
-    product.elements['title'].text,
-    product.elements['description'].text,
-    product.elements['image-url'].text,
-    product.elements['price'].text
+  title       = product.elements['title'].text
+  description = product.elements['description'].text
+  image_url   = product.elements['image-url'].text
+  price       = product.elements['price'].text
+
+  base_id     = product.elements['id'].text
+
+  id = output.execute('SELECT id FROM products WHERE base_id=?', base_id)
+  if id.empty?
+    output.execute 'INSERT INTO products' +
+      '(base_id, title, description, image_url, price)' +
+      ' VALUES (?, ?, ?, ?, ?)', base_id, title, description, image_url, price
+  else
+    output.execute 'UPDATE products ' +
+      'SET base_id=?, title=?, description=?, image_url=?, price=? WHERE id=?',
+      base_id, title, description, image_url, price, id.first
+  end
 end
 
 input.close
diff --git a/products.db b/products.db
index 1694c44..7f494fa 100644
Binary files a/products.db and b/products.db differ

Commit all of the changes.

git commit -a -m "update via raw SQLite3"
Created commit 95019bc: update via raw SQLite3
 2 files changed, 41 insertions(+), 27 deletions(-)
 rewrite load_products.rb (68%)

1.3 Update Using ActiveRecord

Our code is SQLite3 specific (for deployment, we might prefer MySQL or Oracle or DB2...), and is starting to get crufty. Let's see if ActiveRecord can simplify things.

establish_connection, Schema.define, find_by_base_id, save!

edit load_products.rb
require 'rexml/document'
require 'rubygems'
require 'active_record'
 
input  = File.new('testdata.xml')
 
ActiveRecord::Base.establish_connection(
  :adapter => 'sqlite3',
  :database => 'products.db')
 
class Product < ActiveRecord::Base
  unless table_exists?
    ActiveRecord::Schema.define do
      create_table :products do |t|
        t.integer :base_id
        t.string  :title
        t.text    :description
        t.string  :image_url
        t.decimal :price, :precision=>8, :scale=>2, :default=>0.0
      end
    end
  end
end
 
REXML::Document.new(input).each_element('//product') do |xproduct|
  base_id  = xproduct.elements['id'].text
 
  product = Product.find_by_base_id(base_id) || Product.new 
 
  product.base_id     = base_id
  product.title       = xproduct.elements['title'].text
  product.description = xproduct.elements['description'].text
  product.image_url   = xproduct.elements['image-url'].text
  product.price       = xproduct.elements['price'].text
 
  product.save!
end
 
input.close
ActiveRecord::Base.remove_connection

Run the same test.

ruby load_products.rb
ruby test_products.rb
Loaded suite test_products
Started
...
Finished in 0.018658 seconds.
 
3 tests, 12 assertions, 0 failures, 0 errors

Commit changes.

git status
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#
#	modified:   load_products.rb
#
no changes added to commit (use "git add" and/or "git commit -a")
git commit -a -m "update using ActiveRecord"
Created commit 6f0ddd9: update using ActiveRecord
 1 files changed, 40 insertions(+), 41 deletions(-)
 rewrite load_products.rb (92%)

View the log of changes made so far.

git log
commit 6f0ddd919be9f5587ab0db06115334aba332ada1
Author: Sam Ruby <rubys@intertwingly.net>
Date:   Sat Jul 11 12:38:37 2009 -0400
 
    update using ActiveRecord
 
commit 95019bc45fb58154de46f82717c8a6a6ad3a1b96
Author: Sam Ruby <rubys@intertwingly.net>
Date:   Sat Jul 11 12:38:36 2009 -0400
 
    update via raw SQLite3
 
commit a679b97d9ccf5e0f34514dbf212b8f32bcd6cf3a
Author: Sam Ruby <rubys@intertwingly.net>
Date:   Sat Jul 11 12:38:36 2009 -0400
 
    load via raw SQLite3

2.1 Rack

Now, lets get that data to display in the brower, using the simplest thing that could possibly work, namely Rack.

Tests: response OK, 3 products, and verify one title.

edit test_product_server.rb
require 'product_server'
require 'test/unit'
require 'rack/test'
 
class ProductServerTest < Test::Unit::TestCase
  include Rack::Test::Methods
 
  def app
    ProductServer.new
  end
 
  def test_product_list
    response = get('/products')
    assert response.ok?
    assert 3, response.body.scan('<h2>').count
    assert_match /<h2>Pragmatic Project Automation<\/h2>/, response.body
  end
end

Code: Establish connection, use Builder, and send response.

edit product_server.rb
require 'rubygems'
require 'rack'
require 'builder'
require 'active_record'
 
ActiveRecord::Base.establish_connection(
  :adapter => 'sqlite3',
  :database => 'products.db')
 
class Product < ActiveRecord::Base
end
 
class ProductServer
  def call(env)
    x = Builder::XmlMarkup.new :indent=>2
 
    x.html do
      x.head do
        x.title 'Pragmatic Bookshelf'
      end
      x.body do
        x.h1 'Pragmatic Bookshelf'
 
        Product.all.each do |product|
          x.h2 product.title
          x << "      #{product.description}\n"
          x.p product.price
        end
      end
    end
 
    response = Rack::Response.new
    response['Content-Type'] = 'text/html'
    response.write x.target!
    response.finish
  end
end

Test the server logic.

ruby test_product_server.rb
Loaded suite test_product_server
Started
.
Finished in 0.046949 seconds.
 
1 tests, 3 assertions, 0 failures, 0 errors

Minimal rack configuration.

edit config.ru
require 'product_server'
 
use Rack::ShowExceptions
 
map '/products' do
  run ProductServer.new
end

Start the server.

See the output produced.

get /products

Pragmatic Bookshelf

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.

24.95

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

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

See what file we changed.

git status
# On branch master
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#	config.ru
#	product_server.rb
#	test_product_server.rb
nothing added to commit but untracked files present (use "git add" to track)

Add in the new files.

git add *server.rb config.ru

Commit the changes.

git commit -m "rack server"
Created commit aa60f15: rack server
 3 files changed, 62 insertions(+), 0 deletions(-)
 create mode 100644 config.ru
 create mode 100644 product_server.rb
 create mode 100644 test_product_server.rb

Make the test data viewable

mkdir public
git mv testdata.xml public

Update the rack configuration.

edit config.ru
require 'product_server'
 
use Rack::ShowExceptions
 
map '/products' do
  run ProductServer.new
end
 
map '/' do
  run Rack::File.new('public')
end

Restart the server.

Get the test data.

get /testdata.xml
<?xml version="1.0" encoding="UTF-8"?>
<products type="array">
  <product>
    <created-at type="datetime">2009-07-05T16:26:31Z</created-at>
    <description>&lt;p&gt;
       &lt;em&gt;Pragmatic Project Automation&lt;/em&gt; shows you how to improve the 
       consistency and repeatability of your project's procedures using 
       automation to reduce risk and errors.
      &lt;/p&gt;
      &lt;p&gt;
        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.
      &lt;/p&gt;</description>
    <id type="integer">2</id>
    <image-url>/images/auto.jpg</image-url>
    <price type="decimal">24.95</price>
    <title>Pragmatic Project Automation</title>
    <updated-at type="datetime">2009-07-05T16:32:09Z</updated-at>
  </product>
  <product>
    <created-at type="datetime">2009-07-05T16:26:31Z</created-at>
    <description>&lt;p&gt;
         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.
      &lt;/p&gt;</description>
    <id type="integer">3</id>
    <image-url>/images/svn.jpg</image-url>
    <price type="decimal">28.5</price>
    <title>Pragmatic Version Control</title>
    <updated-at type="datetime">2009-07-05T16:26:31Z</updated-at>
  </product>
  <product>
    <created-at type="datetime">2009-07-05T16:26:31Z</created-at>
    <description>&lt;p&gt;
        Pragmatic programmers use feedback to drive their development and 
        personal processes. The most valuable feedback you can get while 
        coding comes from unit testing.
      &lt;/p&gt;
      &lt;p&gt;
        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.
      &lt;/p&gt;</description>
    <id type="integer">4</id>
    <image-url>/images/utc.jpg</image-url>
    <price type="decimal">27.75</price>
    <title>Pragmatic Unit Testing (C#)</title>
    <updated-at type="datetime">2009-07-05T16:26:31Z</updated-at>
  </product>
</products>

Update the loader script with the new location.

edit load_products.rb
require 'rexml/document'
require 'rubygems'
require 'active_record'
 
input  = File.new('public/testdata.xml')
 
ActiveRecord::Base.establish_connection(
  :adapter => 'sqlite3',
  :database => 'products.db')
 
class Product < ActiveRecord::Base
  unless table_exists?
    ActiveRecord::Schema.define do
      create_table :products do |t|
        t.integer :base_id
        t.string  :title
        t.text    :description
        t.string  :image_url
        t.decimal :price, :precision=>8, :scale=>2, :default=>0.0
      end
    end
  end
end
 
REXML::Document.new(input).each_element('//product') do |xproduct|
  base_id  = xproduct.elements['id'].text
 
  product = Product.find_by_base_id(base_id) || Product.new 
 
  product.base_id     = base_id
  product.title       = xproduct.elements['title'].text
  product.description = xproduct.elements['description'].text
  product.image_url   = xproduct.elements['image-url'].text
  product.price       = xproduct.elements['price'].text
 
  product.save!
end
 
input.close
ActiveRecord::Base.remove_connection

Verify the changes.

rm products.db
ruby load_products.rb
-- create_table(:products)
   -> 0.0278s
ruby test_products.rb
Loaded suite test_products
Started
...
Finished in 0.010468 seconds.
 
3 tests, 12 assertions, 0 failures, 0 errors

Commit the results.

git commit -a -m "serve testdata"
Created commit 8aba582: serve testdata
 4 files changed, 5 insertions(+), 1 deletions(-)
 rename testdata.xml => public/testdata.xml (100%)

2.2 Capistrano

We've got the program working on our machine, let's deploy it to our server machine which is running Passenger (a.k.a. mod_rails a.k.a. mod_rack) on Apache's http. This takes a bit of planning the first time, but then Capistrano takes all of the guesswork and potential for errors out of the equation when it really matters. Note: this step can be safely skipped on first reading.

Create our Capistrano configuration

capify .
[skip] `./Capfile' already exists
[add] writing `./Capfile'
[add] writing `./config/deploy.rb'
[done] capified!

Tailor it extensively

edit config/deploy.rb
# be sure to change these
set :user, 'sa3ruby'
set :domain, 'depot.intertwingly.net'
set :application, 'depot'
 
# file paths
set :repository,  "#{user}@#{domain}:git/#{application}.git" 
set :deploy_to, "/home/#{user}/#{domain}" 
 
# distribute your applications across servers (the instructions below put them
# all on the same server, defined above as 'domain', adjust as necessary)
role :app, domain
role :web, domain
role :db, domain, :primary => true
 
# you might need to set this if you aren't seeing password prompts
default_run_options[:pty] = true
 
# As Capistrano executes in a non-interactive mode and therefore doesn't cause
# any of your shell profile scripts to be run, the following might be needed
# if (for example) you have locally installed gems or applications.  Note:
# this needs to contain the full values for the variables set, not simply
# the deltas.
default_environment['PATH']='$HOME/.gems/bin:/usr/local/bin:/usr/bin:/bin'
default_environment['GEM_PATH']='$HOME/.gems:/usr/lib/ruby/gems/1.8'
 
# miscellaneous options
set :deploy_via, :remote_cache
set :scm, 'git'
set :branch, 'master'
set :scm_verbose, true
set :use_sudo, false
 
# additional tasks
namespace :deploy do
  desc 'Restarting Passenger'
  task :restart, :roles => :app do
    run "mkdir -p #{current_path}/tmp" 
    run "touch #{current_path}/tmp/restart.txt" 
  end
end
 
# insert tasks into the deployment sequence
after "deploy:symlink", "deploy:restart"

Commit to the repository.

git status
# On branch master
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#	Capfile
#	config/
nothing added to commit but untracked files present (use "git add" to track)
git add config Capfile
git commit -m "capify"
Created commit e9cb0f5: capify
 2 files changed, 47 insertions(+), 0 deletions(-)
 create mode 100644 Capfile
 create mode 100644 config/deploy.rb

Push the repository to the server.

git remote add origin ssh://sa3ruby@depot.intertwingly.net/~/git/depot.git
git push origin master
Counting objects: 29, done.
Compressing objects:   3% (1/27)   
Compressing objects:   7% (2/27)   
Compressing objects:  11% (3/27)   
Compressing objects:  14% (4/27)   
Compressing objects:  18% (5/27)   
Compressing objects:  22% (6/27)   
Compressing objects:  25% (7/27)   
Compressing objects:  29% (8/27)   
Compressing objects:  33% (9/27)   
Compressing objects:  37% (10/27)   
Compressing objects:  40% (11/27)   
Compressing objects:  44% (12/27)   
Compressing objects:  48% (13/27)   
Compressing objects:  51% (14/27)   
Compressing objects:  55% (15/27)   
Compressing objects:  59% (16/27)   
Compressing objects:  62% (17/27)   
Compressing objects:  66% (18/27)   
Compressing objects:  70% (19/27)   
Compressing objects:  74% (20/27)   
Compressing objects:  77% (21/27)   
Compressing objects:  81% (22/27)   
Compressing objects:  85% (23/27)   
Compressing objects:  88% (24/27)   
Compressing objects:  92% (25/27)   
Compressing objects:  96% (26/27)   
Compressing objects: 100% (27/27)   
Compressing objects: 100% (27/27), done.
Writing objects:   3% (1/29)   
Writing objects:   6% (2/29)   
Writing objects:  10% (3/29)   
Writing objects:  13% (4/29)   
Writing objects:  17% (5/29)   
Writing objects:  20% (6/29)   
Writing objects:  24% (7/29)   
Writing objects:  27% (8/29)   
Writing objects:  31% (9/29)   
Writing objects:  34% (10/29)   
Writing objects:  37% (11/29)   
Writing objects:  41% (12/29)   
Writing objects:  44% (13/29)   
Writing objects:  48% (14/29)   
Writing objects:  51% (15/29)   
Writing objects:  55% (16/29)   
Writing objects:  58% (17/29)   
Writing objects:  62% (18/29)   
Writing objects:  65% (19/29)   
Writing objects:  68% (20/29)   
Writing objects:  72% (21/29)   
Writing objects:  75% (22/29)   
Writing objects:  79% (23/29)   
Writing objects:  82% (24/29)   
Writing objects:  86% (25/29)   
Writing objects:  89% (26/29)   
Writing objects:  93% (27/29)   
Writing objects:  96% (28/29)   
Writing objects: 100% (29/29)   
Writing objects: 100% (29/29), 7.77 KiB, done.
Total 29 (delta 8), reused 0 (delta 0)
To ssh://sa3ruby@depot.intertwingly.net/~/git/depot.git
 * [new branch]      master -> master

Allow Capistrano to set up the server.

cap deploy:setup
  * executing `deploy:setup'
  * executing "mkdir -p /home/sa3ruby/depot.intertwingly.net /home/sa3ruby/depot.intertwingly.net/releases /home/sa3ruby/depot.intertwingly.net/shared /home/sa3ruby/depot.intertwingly.net/shared/system /home/sa3ruby/depot.intertwingly.net/shared/log /home/sa3ruby/depot.intertwingly.net/shared/pids &&  chmod g+w /home/sa3ruby/depot.intertwingly.net /home/sa3ruby/depot.intertwingly.net/releases /home/sa3ruby/depot.intertwingly.net/shared /home/sa3ruby/depot.intertwingly.net/shared/system /home/sa3ruby/depot.intertwingly.net/shared/log /home/sa3ruby/depot.intertwingly.net/shared/pids"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished

Check that the server is ready for deployment.

cap deploy:check
  * executing `deploy:check'
  * executing "test -d /home/sa3ruby/depot.intertwingly.net/releases"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
  * executing "test -w /home/sa3ruby/depot.intertwingly.net"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
  * executing "test -w /home/sa3ruby/depot.intertwingly.net/releases"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
  * executing "which git"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
  * executing "test -w /home/sa3ruby/depot.intertwingly.net/shared"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
You appear to have all necessary dependencies installed

Do the deployment.

cap deploy
  * executing `deploy'
  * executing `deploy:update'
 ** transaction: start
  * executing `deploy:update_code'
    updating the cached checkout on all servers
    executing locally: "git ls-remote sa3ruby@depot.intertwingly.net:git/depot.git master"
  * executing "if [ -d /home/sa3ruby/depot.intertwingly.net/shared/cached-copy ]; then cd /home/sa3ruby/depot.intertwingly.net/shared/cached-copy && git fetch  origin && git reset  --hard e9cb0f5100b5fcb7ceda7514044d0eab49117648 && git clean  -d -x -f; else git clone  sa3ruby@depot.intertwingly.net:git/depot.git /home/sa3ruby/depot.intertwingly.net/shared/cached-copy && cd /home/sa3ruby/depot.intertwingly.net/shared/cached-copy && git checkout  -b deploy e9cb0f5100b5fcb7ceda7514044d0eab49117648; fi"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
 ** [depot.intertwingly.net :: out] Initialized empty Git repository in /home/sa3ruby/depot.intertwingly.net/shared/cached-copy/.git/
 ** [depot.intertwingly.net :: out] remote: Counting objects: 29, done.*[K
 ** remote: Compressing objects:   3% (1/27)   *[K
remote: Compressing objects:   7% (2/27)   *[K
remote: Compressing objects:  11% (*[K3remote: /27)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  14% (4/27)   *[K
remote: Compressing objects:  18% (5/27)   *[K
remote: Compressing objects:  22% (6/27)   *[K
remote: Compressing*[K remote: objects:  25% (7/27)   *[K
remote: Compressing objects:  29% (8/27)   *[K
remote: Compressing objects:  33% (9/27)   *[K
remote: Compressing objects:  37% (10/2*[K7remote: )   *[K
remote: Compressing objects:  40% (11/27)   *[K
remote: Compressing objects:  44% (12/27)   *[K
remote: Compressing objects:  48% (13/27)   *[K
remote: Compressing*[K remote: objects:  51% (14/27)   *[K
remote: Compressing objects:  55% (15/27)   *[K
remote: Compressing objects:  59% (16/27)   *[K
remote: Compressing objects:  62% (1*[K7remote: /27)   *[K
remote: Compressing objects:  66% (18/27)   *[K
remote: Compressing objects:  70% (19/27)   *[K
remote: Compressing objects:  74% (20/27)   *[K
remote: Compress*[Ki
 ** [depot.intertwingly.net :: out] remote: ng objects:  77% (21/27)   *[K
remote: Compressing objects:  81% (22/27)   *[K
remote: Compressing objects:  85% (23/27)   *[K
remote: Compressing objects:  88%*[K remote: (24/27)   *[K
remote: Compressing objects:  92% (25/27)   *[K
remote: Compressing objects:  96% (26/27)   *[K
remote: Compressing objects: 100% (27/27)   *[K
remote: Compr*[Keremote: ssing objects: 100% (27/27), done.*[K
 ** remote: Total 29 (delta 8), reused 0 (delta 0)*[K
 ** [depot.intertwingly.net :: out] Receiving objects:   3% (1/29)
 ** [depot.intertwingly.net :: out] Receiving objects:   6% (2/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  10% (3/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  13% (4/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  17% (5/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  20% (6/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  24% (7/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  27% (8/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  31% (9/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  34% (10/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  37% (11/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  41% (12/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  44% (13/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  48% (14/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  51% (15/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  55% (16/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  58% (17/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  62% (18/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  65% (19/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  68% (20/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  72% (21/29)   
Receiving objects:  75% (22/29)   
Receiving objects:  79% (23/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  82% (24/29)   
Receiving objects:  86% (25/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  89% (26/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  93% (27/29)
 ** [depot.intertwingly.net :: out] Receiving objects:  96% (28/29)   
Receiving objects: 100% (29/29)
 ** [depot.intertwingly.net :: out] Receiving objects: 100% (29/29), 7.63 KiB, done.
 ** [depot.intertwingly.net :: out] Resolving deltas:  12% (1/8)
 ** [depot.intertwingly.net :: out] Resolving deltas:  25% (2/8)
 ** [depot.intertwingly.net :: out] Resolving deltas:  37% (3/8)
 ** [depot.intertwingly.net :: out] Resolving deltas:  62% (5/8)
 ** [depot.intertwingly.net :: out] Resolving deltas:  87% (7/8)
 ** [depot.intertwingly.net :: out] Resolving deltas: 100% (8/8)
 ** [depot.intertwingly.net :: out] Resolving deltas: 100% (8/8), done.
 ** [depot.intertwingly.net :: out] Switched to a new branch "deploy"
    command finished
    copying the cached version to /home/sa3ruby/depot.intertwingly.net/releases/20090711163908
  * executing "cp -RPp /home/sa3ruby/depot.intertwingly.net/shared/cached-copy /home/sa3ruby/depot.intertwingly.net/releases/20090711163908 && (echo e9cb0f5100b5fcb7ceda7514044d0eab49117648 > /home/sa3ruby/depot.intertwingly.net/releases/20090711163908/REVISION)"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
  * executing `deploy:finalize_update'
  * executing "chmod -R g+w /home/sa3ruby/depot.intertwingly.net/releases/20090711163908"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
  * executing `deploy:symlink'
  * executing "rm -f /home/sa3ruby/depot.intertwingly.net/current && ln -s /home/sa3ruby/depot.intertwingly.net/releases/20090711163908 /home/sa3ruby/depot.intertwingly.net/current"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
    triggering after callbacks for `deploy:symlink'
  * executing `deploy:restart'
  * executing "mkdir -p /home/sa3ruby/depot.intertwingly.net/current/tmp"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
  * executing "touch /home/sa3ruby/depot.intertwingly.net/current/tmp/restart.txt"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
 ** transaction: commit

See the results.

get http://depot.intertwingly.net/products

Pragmatic Bookshelf

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.

24.95

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

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

get http://depot.intertwingly.net/testdata.xml
<?xml version="1.0" encoding="UTF-8"?>
<products type="array">
  <product>
    <created-at type="datetime">2009-07-05T16:26:31Z</created-at>
    <description>&lt;p&gt;
       &lt;em&gt;Pragmatic Project Automation&lt;/em&gt; shows you how to improve the 
       consistency and repeatability of your project's procedures using 
       automation to reduce risk and errors.
      &lt;/p&gt;
      &lt;p&gt;
        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.
      &lt;/p&gt;</description>
    <id type="integer">2</id>
    <image-url>/images/auto.jpg</image-url>
    <price type="decimal">24.95</price>
    <title>Pragmatic Project Automation</title>
    <updated-at type="datetime">2009-07-05T16:32:09Z</updated-at>
  </product>
  <product>
    <created-at type="datetime">2009-07-05T16:26:31Z</created-at>
    <description>&lt;p&gt;
         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.
      &lt;/p&gt;</description>
    <id type="integer">3</id>
    <image-url>/images/svn.jpg</image-url>
    <price type="decimal">28.5</price>
    <title>Pragmatic Version Control</title>
    <updated-at type="datetime">2009-07-05T16:26:31Z</updated-at>
  </product>
  <product>
    <created-at type="datetime">2009-07-05T16:26:31Z</created-at>
    <description>&lt;p&gt;
        Pragmatic programmers use feedback to drive their development and 
        personal processes. The most valuable feedback you can get while 
        coding comes from unit testing.
      &lt;/p&gt;
      &lt;p&gt;
        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.
      &lt;/p&gt;</description>
    <id type="integer">4</id>
    <image-url>/images/utc.jpg</image-url>
    <price type="decimal">27.75</price>
    <title>Pragmatic Unit Testing (C#)</title>
    <updated-at type="datetime">2009-07-05T16:26:31Z</updated-at>
  </product>
</products>

2.3 Whenever

At this point, we are displaying a what amounts to be static data. Presumably the supplier will be making changes, so let's set things up so that everything is updated every morning, before we wake up.

Load from the web (yes, this is our server, work with me for now)

edit load_products.rb
require 'net/http'
require 'rexml/document'
require 'rubygems'
require 'active_record'
 
input  = Net::HTTP.get(URI.parse(ARGV.first))
 
ActiveRecord::Base.establish_connection(
  :adapter => 'sqlite3',
  :database => 'products.db')
 
class Product < ActiveRecord::Base
  unless table_exists?
    ActiveRecord::Schema.define do
      create_table :products do |t|
        t.integer :base_id
        t.string  :title
        t.text    :description
        t.string  :image_url
        t.decimal :price, :precision=>8, :scale=>2, :default=>0.0
      end
    end
  end
end
 
REXML::Document.new(input).each_element('//product') do |xproduct|
  base_id  = xproduct.elements['id'].text
 
  product = Product.find_by_base_id(base_id) || Product.new 
 
  product.base_id     = base_id
  product.title       = xproduct.elements['title'].text
  product.description = xproduct.elements['description'].text
  product.image_url   = xproduct.elements['image-url'].text
  product.price       = xproduct.elements['price'].text
 
  product.save!
end
 
ActiveRecord::Base.remove_connection

Verify that the change works

rm products.db
ruby load_products.rb http://depot.intertwingly.net/testdata.xml
-- create_table(:products)
   -> 0.0155s
ruby test_products.rb
Loaded suite test_products
Started
...
Finished in 0.04859 seconds.
 
3 tests, 12 assertions, 0 failures, 0 errors

Set up whenever

wheneverize .
[add] writing `./config/schedule.rb'
[done] wheneverized!

Add a command to run load_products daily at 4:15 am

edit config/schedule.rb
# Use this file to easily define all of your cron jobs.
#
# It's helpful, but not entirely necessary to understand cron before proceeding.
# http://en.wikipedia.org/wiki/Cron
 
# Example:
#
# set :cron_log, "/path/to/my/cron_log.log"
#
# every 2.hours do
#   command "/usr/bin/some_great_command"
#   runner "MyModel.some_method"
#   rake "some:great:rake:task"
# end
#
# every 4.days do
#   runner "AnotherModel.prune_old_records"
# end
 
# Learn more: http://github.com/javan/whenever
 
root = File.dirname(File.expand_path(__FILE__))
 
every 1.day, :at => '4:15 am' do
  command "cd #{root}; ruby load_products.rb http://depot.intertwingly.net/testdata.xml"
end

Visually inspect what the crontab entry looks like.

whenever
15 4 * * * cd /home/rubys/git/awdwr/work/depot; ruby load_products.rb http://depot.intertwingly.net/testdata.xml
 
edit config/deploy.rb
# be sure to change these
set :user, 'sa3ruby'
set :domain, 'depot.intertwingly.net'
set :application, 'depot'
 
# file paths
set :repository,  "#{user}@#{domain}:git/#{application}.git" 
set :deploy_to, "/home/#{user}/#{domain}" 
 
# distribute your applications across servers (the instructions below put them
# all on the same server, defined above as 'domain', adjust as necessary)
role :app, domain
role :web, domain
role :db, domain, :primary => true
 
# you might need to set this if you aren't seeing password prompts
default_run_options[:pty] = true
 
# As Capistrano executes in a non-interactive mode and therefore doesn't cause
# any of your shell profile scripts to be run, the following might be needed
# if (for example) you have locally installed gems or applications.  Note:
# this needs to contain the full values for the variables set, not simply
# the deltas.
default_environment['PATH']='$HOME/.gems/bin:/usr/local/bin:/usr/bin:/bin'
default_environment['GEM_PATH']='$HOME/.gems:/usr/lib/ruby/gems/1.8'
 
# miscellaneous options
set :deploy_via, :remote_cache
set :scm, 'git'
set :branch, 'master'
set :scm_verbose, true
set :use_sudo, false
 
# additional tasks
namespace :deploy do
  desc 'Restarting Passenger'
  task :restart, :roles => :app do
    run "mkdir -p #{current_path}/tmp" 
    run "touch #{current_path}/tmp/restart.txt" 
  end
 
  desc 'Update the crontab file'
    task :update_crontab, :roles => :db do
    run "cd #{current_path} && whenever --update-crontab #{application}"
  end
end
 
# insert tasks into the deployment sequence
after "deploy:symlink", "deploy:restart"
after "deploy:restart", "deploy:update_crontab"

Commit the changes.

git st
# On branch master
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#
#	modified:   config/deploy.rb
#	modified:   load_products.rb
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#	config/schedule.rb
no changes added to commit (use "git add" and/or "git commit -a")
git add config/schedule.rb
git commit -a -m "whenever"
Created commit 4eaa140: whenever
 3 files changed, 34 insertions(+), 2 deletions(-)
 create mode 100644 config/schedule.rb

Push and deploy!

git push
Counting objects: 10, done.
Compressing objects:  16% (1/6)   
Compressing objects:  33% (2/6)   
Compressing objects:  50% (3/6)   
Compressing objects:  66% (4/6)   
Compressing objects:  83% (5/6)   
Compressing objects: 100% (6/6)   
Compressing objects: 100% (6/6), done.
Writing objects:  16% (1/6)   
Writing objects:  33% (2/6)   
Writing objects:  50% (3/6)   
Writing objects:  66% (4/6)   
Writing objects:  83% (5/6)   
Writing objects: 100% (6/6)   
Writing objects: 100% (6/6), 1.06 KiB, done.
Total 6 (delta 3), reused 0 (delta 0)
To ssh://sa3ruby@depot.intertwingly.net/~/git/depot.git
   e9cb0f5..4eaa140  master -> master
cap deploy
  * executing `deploy'
  * executing `deploy:update'
 ** transaction: start
  * executing `deploy:update_code'
    updating the cached checkout on all servers
    executing locally: "git ls-remote sa3ruby@depot.intertwingly.net:git/depot.git master"
  * executing "if [ -d /home/sa3ruby/depot.intertwingly.net/shared/cached-copy ]; then cd /home/sa3ruby/depot.intertwingly.net/shared/cached-copy && git fetch  origin && git reset  --hard 4eaa140c4d4587119f0d10ec0705a72db8c02793 && git clean  -d -x -f; else git clone  sa3ruby@depot.intertwingly.net:git/depot.git /home/sa3ruby/depot.intertwingly.net/shared/cached-copy && cd /home/sa3ruby/depot.intertwingly.net/shared/cached-copy && git checkout  -b deploy 4eaa140c4d4587119f0d10ec0705a72db8c02793; fi"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
 ** [depot.intertwingly.net :: out] remote: Counting objects: 10, done.*[K
 ** remote: Compressing objects:  16% (1/6)   *[K
remote: Compressing objects:  33% (2/6)   *[K
remote: Compressing objects:  50% (3/6)   *[K
remote: Compressing objects:  *[K6
 ** [depot.intertwingly.net :: out] remote: 6% (4/6)   *[K
remote: Compressing objects:  83% (5/6)   *[K
remote: Compressing objects: 100% (6/6)   *[K
remote: Compressing objects: 100% (6/6), done.*[K
 ** remote: Total *[K6remote:  (delta 3), reused 0 (delta 0)*[K
 ** [depot.intertwingly.net :: out] Unpacking objects:  16% (1/6)
 ** [depot.intertwingly.net :: out] Unpacking objects:  33% (2/6)
 ** [depot.intertwingly.net :: out] Unpacking objects:  50% (3/6)
 ** [depot.intertwingly.net :: out] Unpacking objects:  66% (4/6)
 ** [depot.intertwingly.net :: out] Unpacking objects:  83% (5/6)
 ** [depot.intertwingly.net :: out] Unpacking objects: 100% (6/6)   
Unpacking objects: 100% (6/6), done.
 ** [depot.intertwingly.net :: out] From sa3ruby@depot.intertwingly.net:git/depot
 ** e9cb0f5..4eaa140  master     -> origin/master
 ** [depot.intertwingly.net :: out] HEAD is now at 4eaa140 whenever
    command finished
    copying the cached version to /home/sa3ruby/depot.intertwingly.net/releases/20090711163919
  * executing "cp -RPp /home/sa3ruby/depot.intertwingly.net/shared/cached-copy /home/sa3ruby/depot.intertwingly.net/releases/20090711163919 && (echo 4eaa140c4d4587119f0d10ec0705a72db8c02793 > /home/sa3ruby/depot.intertwingly.net/releases/20090711163919/REVISION)"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
  * executing `deploy:finalize_update'
  * executing "chmod -R g+w /home/sa3ruby/depot.intertwingly.net/releases/20090711163919"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
  * executing `deploy:symlink'
  * executing "rm -f /home/sa3ruby/depot.intertwingly.net/current && ln -s /home/sa3ruby/depot.intertwingly.net/releases/20090711163919 /home/sa3ruby/depot.intertwingly.net/current"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
    triggering after callbacks for `deploy:symlink'
  * executing `deploy:restart'
  * executing "mkdir -p /home/sa3ruby/depot.intertwingly.net/current/tmp"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
  * executing "touch /home/sa3ruby/depot.intertwingly.net/current/tmp/restart.txt"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
    triggering after callbacks for `deploy:restart'
  * executing `deploy:update_crontab'
  * executing "cd /home/sa3ruby/depot.intertwingly.net/current && whenever --update-crontab depot"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
 ** [out :: depot.intertwingly.net] [write] crontab file updated
    command finished
 ** transaction: commit

3.1 Convert to Rails

Taking a step back, we have done something real. It doesn't do much, but it didn't really take much code either. But the problems are starting to accumulate: our application directory is getting cluttered, changes to schemas is a problem, we have code duplicated that establishes the connection, and we haven't even begun thinking about updates. Additionally, we have the database in the git repository and while that has proven to be convenient so far, that won't be such a hot idea once we deploy. And synchronizing gems versions between the machines is a pain... Fred Brooks once recommended that we "plan to throw one away; you will, anyhow." As you will see, we are not exactly going to be throwing anything away, but we will be in a very real sense starting over.

First, let Rails do its thing...

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

Throw away the Rack bootstrap, it served us well.

git rm config.ru
rm 'config.ru'

Define the product anew.

ruby script/generate scaffold product base_id:integer title:string description:text image_url:string price:decimal
      exists  app/models/
      exists  app/controllers/
      exists  app/helpers/
      create  app/views/products
      exists  app/views/layouts/
      exists  test/functional/
      exists  test/unit/
      create  test/unit/helpers/
      exists  public/stylesheets/
      create  app/views/products/index.html.erb
      create  app/views/products/show.html.erb
      create  app/views/products/new.html.erb
      create  app/views/products/edit.html.erb
      create  app/views/layouts/products.html.erb
      create  public/stylesheets/scaffold.css
      create  app/controllers/products_controller.rb
      create  test/functional/products_controller_test.rb
      create  app/helpers/products_helper.rb
      create  test/unit/helpers/products_helper_test.rb
       route  map.resources :products
  dependency  model
      exists    app/models/
      exists    test/unit/
      exists    test/fixtures/
      create    app/models/product.rb
      create    test/unit/product_test.rb
      create    test/fixtures/products.yml
      create    db/migrate
      create    db/migrate/20090711163925_create_products.rb

Tailor the definition to taste

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

Out with the old db.

git rm products.db
rm 'products.db'

In with the new.

rake db:migrate
mv 20090711163925_create_products.rb 20080601000001_create_products.rb
(in /home/rubys/git/awdwr/work/depot)
==  CreateProducts: migrating =================================================
-- create_table(:products)
   -> 0.0112s
==  CreateProducts: migrated (0.0113s) ========================================
 

Write unit tests (this time using ActiveRecord!)

edit test/unit/product_test.rb
require 'test_helper'
 
class ProductTest < ActiveSupport::TestCase
  def setup
    Product.import('public/testdata.xml')
  end
 
  test "Pragmatic Project Automation" do
    product = Product.find_by_base_id(2)
    assert_equal 'Pragmatic Project Automation', product.title
    assert_match /^<p>\s+<em>Pragmatic Project Automation/, product.description
    assert_equal '/images/auto.jpg', product.image_url
    assert_equal 24.95, product.price
  end
 
  test "Pragmatic Version Control" do
    product = Product.find_by_base_id(3)
    assert_equal 'Pragmatic Version Control', product.title
    assert_match /^<p>\s+This book is a recipe-based approach/, product.description
    assert_equal '/images/svn.jpg', product.image_url
    assert_equal 28.5, product.price
  end
 
  test "Pragmatic Unit Testing" do
    product = Product.find_by_base_id(4)
    assert_equal 'Pragmatic Unit Testing (C#)', product.title
    assert_match /<p>\s+Pragmatic programmers use feedback/, product.description
    assert_equal '/images/utc.jpg', product.image_url
    assert_equal 27.75, product.price
  end
end

Run the tests and watch them fail.

rake test:units
/usr/bin/ruby1.8 -I"lib:test" "/usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader.rb" "test/unit/helpers/products_helper_test.rb" "test/unit/product_test.rb" 
(in /home/rubys/git/awdwr/work/depot)
Loaded suite /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
EEE
Finished in 0.090224 seconds.
 
  1) Error:
test_Pragmatic_Project_Automation(ProductTest):
NoMethodError: undefined method `import' for #<Class:0x7fbaf3e43030>
    /test/unit/product_test.rb:5:in `setup'
 
  2) Error:
test_Pragmatic_Unit_Testing(ProductTest):
NoMethodError: undefined method `import' for #<Class:0x7fbaf3e43030>
    /test/unit/product_test.rb:5:in `setup'
 
  3) Error:
test_Pragmatic_Version_Control(ProductTest):
NoMethodError: undefined method `import' for #<Class:0x7fbaf3e43030>
    /test/unit/product_test.rb:5:in `setup'
 
3 tests, 0 assertions, 0 failures, 3 errors
rake aborted!
Command failed with status (1): [/usr/bin/ruby1.8 -I"lib:test" "/usr/lib/ru...]

    
(See full trace by running task with --trace)

Put the load logic in the model (url or file: getting fancy!)

edit app/models/product.rb
class Product < ActiveRecord::Base
 
  def self.import(source)
    if source =~ /^http:/
      input = Net::HTTP.get(URI.parse(source))
    else
      input = File.open(source) {|file| file.read}
    end
 
    REXML::Document.new(input).each_element('//product') do |xproduct|
      base_id  = xproduct.elements['id'].text
 
      product = self.find_by_base_id(base_id) || self.new 
 
      product.base_id     = base_id
      product.title       = xproduct.elements['title'].text
      product.description = xproduct.elements['description'].text
      product.image_url   = xproduct.elements['image-url'].text
      product.price       = xproduct.elements['price'].text
 
      product.save!
    end
  end
 
end

Run the tests and watch them pass.

rake test:units
/usr/bin/ruby1.8 -I"lib:test" "/usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader.rb" "test/unit/helpers/products_helper_test.rb" "test/unit/product_test.rb" 
(in /home/rubys/git/awdwr/work/depot)
Loaded suite /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
...
Finished in 0.197805 seconds.
 
3 tests, 12 assertions, 0 failures, 0 errors

Function tests are already provided and they pass!

rake test:functionals
/usr/bin/ruby1.8 -I"lib:test" "/usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader.rb" "test/functional/products_controller_test.rb" 
(in /home/rubys/git/awdwr/work/depot)
Loaded suite /usr/lib/ruby/gems/1.8/gems/rake-0.8.7/lib/rake/rake_test_loader
Started
.......
Finished in 0.147647 seconds.
 
7 tests, 10 assertions, 0 failures, 0 errors

remove old tests and server

git rm test_*.rb product_server.rb
rm 'product_server.rb'
rm 'test_product_server.rb'
rm 'test_products.rb'

Restart the server.

Load testdata.

ruby script/runner 'Product.import("public/testdata.xml")'

Explore.

get /products

Listing products

Base Title Description Image url Price
2 Pragmatic Project Automation <p> <em>Pragmatic Project Automation</em> shows you how to improve the consistency and repeatability of your project's procedures using automation to reduce risk and errors. </p> <p> 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. </p> /images/auto.jpg 24.95 Show Edit Destroy
3 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. don't use any version control at all. Many others don't use it well, and end up experiencing time-consuming problems. </p> /images/svn.jpg 28.5 Show Edit Destroy
4 Pragmatic Unit Testing (C#) <p> Pragmatic programmers use feedback to drive their development and personal processes. The most valuable feedback you can get while coding comes from unit testing. </p> <p> 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. </p> /images/utc.jpg 27.75 Show Edit Destroy

New product
get /products/1

Base: 2

Title: Pragmatic Project Automation

Description: <p> <em>Pragmatic Project Automation</em> shows you how to improve the consistency and repeatability of your project's procedures using automation to reduce risk and errors. </p> <p> 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. </p>

Image url: /images/auto.jpg

Price: 24.95

Edit | Back
get /products/1/edit

Editing product






Show | Back
get /products/new

New product






Back

3.2 Deploy Rails

Capistrano is understands Rails, but there are a few things you need to be aware of.

Update whenever to use runner.

edit config/schedule.rb
# Use this file to easily define all of your cron jobs.
#
# It's helpful, but not entirely necessary to understand cron before proceeding.
# http://en.wikipedia.org/wiki/Cron
 
# Example:
#
# set :cron_log, "/path/to/my/cron_log.log"
#
# every 2.hours do
#   command "/usr/bin/some_great_command"
#   runner "MyModel.some_method"
#   rake "some:great:rake:task"
# end
#
# every 4.days do
#   runner "AnotherModel.prune_old_records"
# end
 
# Learn more: http://github.com/javan/whenever
 
every 1.day, :at => '4:15 am' do
  runner "Product.import('http://depot.intertwingly.net/testdata.xml')"
end

Peek at the results.

whenever
15 4 * * * /home/rubys/git/awdwr/work/depot/script/runner -e production "Product.import('http://depot.intertwingly.net/testdata.xml')"
 

Add GEM_HOME to environment.rb, migration tasks, and do cleanup.

edit config/deploy.rb
# be sure to change these
set :user, 'sa3ruby'
set :domain, 'depot.intertwingly.net'
set :application, 'depot'
 
# file paths
set :repository,  "#{user}@#{domain}:git/#{application}.git" 
set :deploy_to, "/home/#{user}/#{domain}" 
set :gemhome, "/home/#{user}/.gems"
 
# distribute your applications across servers (the instructions below put them
# all on the same server, defined above as 'domain', adjust as necessary)
role :app, domain
role :web, domain
role :db, domain, :primary => true
 
# you might need to set this if you aren't seeing password prompts
default_run_options[:pty] = true
 
# As Capistrano executes in a non-interactive mode and therefore doesn't cause
# any of your shell profile scripts to be run, the following might be needed
# if (for example) you have locally installed gems or applications.  Note:
# this needs to contain the full values for the variables set, not simply
# the deltas.
default_environment['PATH']='$HOME/.gems/bin:/usr/local/bin:/usr/bin:/bin'
default_environment['GEM_PATH']='$HOME/.gems:/usr/lib/ruby/gems/1.8'
 
# miscellaneous options
set :deploy_via, :remote_cache
set :scm, 'git'
set :branch, 'master'
set :scm_verbose, true
set :use_sudo, false
 
# additional tasks
namespace :deploy do
  desc 'Restarting Passenger'
  task :restart, :roles => :app do
    run "mkdir -p #{current_path}/tmp" 
    run "touch #{current_path}/tmp/restart.txt" 
  end
 
  desc 'Update the crontab file'
    task :update_crontab, :roles => :db do
    run "cd #{current_path} && whenever --update-crontab #{application}"
  end
 
  desc 'set db path outside of application directory'
  task :set_db_path do
    run "sed -i 's|db/production|#{deploy_to}/depot|' " +
        "#{release_path}/config/database.yml"
  end
 
  desc 'set GEM_HOME in the environment'
  task :set_gem_home do
    run "sed -i '1iENV[%(GEM_HOME)]=%(#{gemhome})\\n' " +
        "#{release_path}/config/environment.rb"
  end
end
 
# insert tasks into the deployment sequence
after "deploy:update_code", "deploy:set_db_path"
after "deploy:update_code", "deploy:set_gem_home"
after "deploy:symlink", "deploy:restart"
after "deploy:restart", "deploy:cleanup"
after "deploy:restart", "deploy:update_crontab"
 
# Rails migration tasks
load 'ext/rails-database-migrations.rb'

Tell git what file NOT to retain

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

Commit. Push. Deploy.

git st
# On branch master
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#	deleted:    config.ru
#	deleted:    product_server.rb
#	deleted:    products.db
#	deleted:    test_product_server.rb
#	deleted:    test_products.rb
#
# Changed but not updated:
#   (use "git add <file>..." to update what will be committed)
#
#	modified:   config/deploy.rb
#	modified:   config/schedule.rb
#
# Untracked files:
#   (use "git add <file>..." to include in what will be committed)
#
#	.gitignore
#	README
#	Rakefile
#	app/
#	config/boot.rb
#	config/database.yml
#	config/environment.rb
#	config/environments/
#	config/initializers/
#	config/locales/
#	config/routes.rb
#	db/
#	doc/
#	public/404.html
#	public/422.html
#	public/500.html
#	public/favicon.ico
#	public/images/
#	public/index.html
#	public/javascripts/
#	public/robots.txt
#	public/stylesheets/
#	script/
#	test/
git add .
git commit -m "convert to Rails!"
Created commit 8dd1afc: convert to Rails!
 64 files changed, 8915 insertions(+), 97 deletions(-)
 create mode 100644 .gitignore
 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/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
 delete mode 100644 config.ru
 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/20080601000001_create_products.rb
 create mode 100644 db/schema.rb
 create mode 100644 doc/README_FOR_APP
 delete mode 100644 product_server.rb
 delete mode 100644 products.db
 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
 delete mode 100644 test_product_server.rb
 delete mode 100644 test_products.rb
git push
Counting objects: 93, done.
Compressing objects:   1% (1/77)   
Compressing objects:   2% (2/77)   
Compressing objects:   3% (3/77)   
Compressing objects:   5% (4/77)   
Compressing objects:   6% (5/77)   
Compressing objects:   7% (6/77)   
Compressing objects:   9% (7/77)   
Compressing objects:  10% (8/77)   
Compressing objects:  11% (9/77)   
Compressing objects:  12% (10/77)   
Compressing objects:  14% (11/77)   
Compressing objects:  15% (12/77)   
Compressing objects:  16% (13/77)   
Compressing objects:  18% (14/77)   
Compressing objects:  19% (15/77)   
Compressing objects:  20% (16/77)   
Compressing objects:  22% (17/77)   
Compressing objects:  23% (18/77)   
Compressing objects:  24% (19/77)   
Compressing objects:  25% (20/77)   
Compressing objects:  27% (21/77)   
Compressing objects:  28% (22/77)   
Compressing objects:  29% (23/77)   
Compressing objects:  31% (24/77)   
Compressing objects:  32% (25/77)   
Compressing objects:  33% (26/77)   
Compressing objects:  35% (27/77)   
Compressing objects:  36% (28/77)   
Compressing objects:  37% (29/77)   
Compressing objects:  38% (30/77)   
Compressing objects:  40% (31/77)   
Compressing objects:  41% (32/77)   
Compressing objects:  42% (33/77)   
Compressing objects:  44% (34/77)   
Compressing objects:  45% (35/77)   
Compressing objects:  46% (36/77)   
Compressing objects:  48% (37/77)   
Compressing objects:  49% (38/77)   
Compressing objects:  50% (39/77)   
Compressing objects:  51% (40/77)   
Compressing objects:  53% (41/77)   
Compressing objects:  54% (42/77)   
Compressing objects:  55% (43/77)   
Compressing objects:  57% (44/77)   
Compressing objects:  58% (45/77)   
Compressing objects:  59% (46/77)   
Compressing objects:  61% (47/77)   
Compressing objects:  62% (48/77)   
Compressing objects:  63% (49/77)   
Compressing objects:  64% (50/77)   
Compressing objects:  66% (51/77)   
Compressing objects:  67% (52/77)   
Compressing objects:  68% (53/77)   
Compressing objects:  70% (54/77)   
Compressing objects:  71% (55/77)   
Compressing objects:  72% (56/77)   
Compressing objects:  74% (57/77)   
Compressing objects:  75% (58/77)   
Compressing objects:  76% (59/77)   
Compressing objects:  77% (60/77)   
Compressing objects:  79% (61/77)   
Compressing objects:  80% (62/77)   
Compressing objects:  81% (63/77)   
Compressing objects:  83% (64/77)   
Compressing objects:  84% (65/77)   
Compressing objects:  85% (66/77)   
Compressing objects:  87% (67/77)   
Compressing objects:  88% (68/77)   
Compressing objects:  89% (69/77)   
Compressing objects:  90% (70/77)   
Compressing objects:  92% (71/77)   
Compressing objects:  93% (72/77)   
Compressing objects:  94% (73/77)   
Compressing objects:  96% (74/77)   
Compressing objects:  97% (75/77)   
Compressing objects:  98% (76/77)   
Compressing objects: 100% (77/77)   
Compressing objects: 100% (77/77), done.
Writing objects:   1% (1/88)   
Writing objects:   2% (2/88)   
Writing objects:   3% (3/88)   
Writing objects:   4% (4/88)   
Writing objects:   5% (5/88)   
Writing objects:   6% (6/88)   
Writing objects:   7% (7/88)   
Writing objects:   9% (8/88)   
Writing objects:  10% (9/88)   
Writing objects:  11% (10/88)   
Writing objects:  12% (11/88)   
Writing objects:  13% (12/88)   
Writing objects:  14% (13/88)   
Writing objects:  15% (14/88)   
Writing objects:  17% (15/88)   
Writing objects:  18% (16/88)   
Writing objects:  19% (17/88)   
Writing objects:  20% (18/88)   
Writing objects:  22% (20/88)   
Writing objects:  23% (21/88)   
Writing objects:  25% (22/88)   
Writing objects:  26% (23/88)   
Writing objects:  27% (24/88)   
Writing objects:  28% (25/88)   
Writing objects:  29% (26/88)   
Writing objects:  30% (27/88)   
Writing objects:  31% (28/88)   
Writing objects:  32% (29/88)   
Writing objects:  34% (30/88)   
Writing objects:  35% (31/88)   
Writing objects:  36% (32/88)   
Writing objects:  37% (33/88)   
Writing objects:  38% (34/88)   
Writing objects:  39% (35/88)   
Writing objects:  40% (36/88)   
Writing objects:  42% (37/88)   
Writing objects:  43% (38/88)   
Writing objects:  44% (39/88)   
Writing objects:  45% (40/88)   
Writing objects:  46% (41/88)   
Writing objects:  47% (42/88)   
Writing objects:  48% (43/88)   
Writing objects:  50% (44/88)   
Writing objects:  51% (45/88)   
Writing objects:  52% (46/88)   
Writing objects:  53% (47/88)   
Writing objects:  54% (48/88)   
Writing objects:  55% (49/88)   
Writing objects:  56% (50/88)   
Writing objects:  57% (51/88)   
Writing objects:  59% (52/88)   
Writing objects:  60% (53/88)   
Writing objects:  61% (54/88)   
Writing objects:  62% (55/88)   
Writing objects:  63% (56/88)   
Writing objects:  64% (57/88)   
Writing objects:  65% (58/88)   
Writing objects:  67% (59/88)   
Writing objects:  68% (60/88)   
Writing objects:  69% (61/88)   
Writing objects:  70% (62/88)   
Writing objects:  71% (63/88)   
Writing objects:  72% (64/88)   
Writing objects:  73% (65/88)   
Writing objects:  75% (66/88)   
Writing objects:  77% (68/88)   
Writing objects:  78% (69/88)   
Writing objects:  79% (70/88)   
Writing objects:  80% (71/88)   
Writing objects:  81% (72/88)   
Writing objects:  82% (73/88)   
Writing objects:  84% (74/88)   
Writing objects:  85% (75/88)   
Writing objects:  86% (76/88)   
Writing objects:  87% (77/88)   
Writing objects:  88% (78/88)   
Writing objects:  89% (79/88)   
Writing objects:  90% (80/88)   
Writing objects:  92% (81/88)   
Writing objects:  93% (82/88)   
Writing objects:  94% (83/88)   
Writing objects:  95% (84/88)   
Writing objects:  96% (85/88)   
Writing objects:  97% (86/88)   
Writing objects:  98% (87/88)   
Writing objects: 100% (88/88)   
Writing objects: 100% (88/88), 84.60 KiB, done.
Total 88 (delta 13), reused 0 (delta 0)
To ssh://sa3ruby@depot.intertwingly.net/~/git/depot.git
   4eaa140..8dd1afc  master -> master
cap deploy:migrations
  * executing `deploy:migrations'
  * executing `deploy:update_code'
    updating the cached checkout on all servers
    executing locally: "git ls-remote sa3ruby@depot.intertwingly.net:git/depot.git master"
  * executing "if [ -d /home/sa3ruby/depot.intertwingly.net/shared/cached-copy ]; then cd /home/sa3ruby/depot.intertwingly.net/shared/cached-copy && git fetch  origin && git reset  --hard 8dd1afcc2636408adcbb662c18528b2347e43d57 && git clean  -d -x -f; else git clone  sa3ruby@depot.intertwingly.net:git/depot.git /home/sa3ruby/depot.intertwingly.net/shared/cached-copy && cd /home/sa3ruby/depot.intertwingly.net/shared/cached-copy && git checkout  -b deploy 8dd1afcc2636408adcbb662c18528b2347e43d57; fi"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
 ** [depot.intertwingly.net :: out] remote: Counting objects: 93, done.*[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:   1% (1/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:   2% (2/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:   3% (*[K3
 ** [depot.intertwingly.net :: out] remote: /77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:   5% (4/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:   6% (5/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:   7% (6/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing*[K
 ** [depot.intertwingly.net :: out] remote: objects:   9% (7/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  10% (8/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  11% (9/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  12% (10/7*[K7
 ** [depot.intertwingly.net :: out] remote: )   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  14% (11/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  15% (12/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  16% (13/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  18% (14/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  19% (15/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  20% (16/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  22% (17/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  23% (18/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing obje*[Kc
 ** [depot.intertwingly.net :: out] remote: ts:  24% (19/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  25% (20/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  27% (21/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  28% (22/77)*[K
 ** [depot.intertwingly.net :: out] remote:   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  29% (23/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  31% (24/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  32% (25/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing o*[Kb
 ** [depot.intertwingly.net :: out] remote: jects:  33% (26/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  35% (27/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  36% (28/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  37% (29/*[K7
 ** [depot.intertwingly.net :: out] Unpacking objects:   1% (1/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:   2% (2/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:   3% (3/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:   4% (4/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:   5% (5/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:   6% (6/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:   7% (7/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:   9% (8/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  10% (9/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  11% (10/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  12% (11/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  13% (12/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  14% (13/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  15% (14/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  17% (15/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  18% (16/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  19% (17/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  20% (18/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  21% (19/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  22% (20/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  23% (21/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  25% (22/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  26% (23/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  27% (24/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  28% (25/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  29% (26/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  30% (27/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  31% (28/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  32% (29/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  34% (30/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  35% (31/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  36% (32/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  37% (33/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  38% (34/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  39% (35/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  40% (36/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  42% (37/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  43% (38/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  44% (39/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  45% (40/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  46% (41/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  47% (42/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  48% (43/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  50% (44/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  51% (45/88)
 ** [depot.intertwingly.net :: out] remote: 7)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  38% (30/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  40% (31/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  41% (32/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressin*[Kg
 ** [depot.intertwingly.net :: out] remote:  objects:  42% (33/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  44% (34/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  45% (35/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  46% (*[K3
 ** [depot.intertwingly.net :: out] remote: 6/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  48% (37/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  49% (38/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  50% (39/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compres*[Ks
 ** [depot.intertwingly.net :: out] remote: ing objects:  51% (40/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  53% (41/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  54% (42/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  55*[K%
 ** [depot.intertwingly.net :: out] remote:  (43/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  57% (44/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  58% (45/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  59% (46/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Comp*[Kr
 ** [depot.intertwingly.net :: out] remote: essing objects:  61% (47/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  62% (48/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  63% (49/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects: *[K
 ** [depot.intertwingly.net :: out] remote: 64% (50/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  66% (51/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  67% (52/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  68% (53/77)   *[K
 ** [depot.intertwingly.net :: out] remote: C*[Ko
 ** [depot.intertwingly.net :: out] remote: mpressing objects:  70% (54/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  71% (55/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  72% (56/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing object*[Ks
 ** [depot.intertwingly.net :: out] remote: :  74% (57/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  75% (58/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  76% (59/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  77% (60/77)  *[K
 ** [depot.intertwingly.net :: out] remote:
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  79% (61/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  80% (62/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  81% (63/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing obj*[Ke
 ** [depot.intertwingly.net :: out] remote: cts:  83% (64/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  84% (65/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  85% (66/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  87% (67/77*[K)
 ** [depot.intertwingly.net :: out] Unpacking objects:  52% (46/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  53% (47/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  54% (48/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  55% (49/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  56% (50/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  57% (51/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  59% (52/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  60% (53/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  61% (54/88)
 ** [depot.intertwingly.net :: out] remote:    *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  88% (68/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  89% (69/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  90% (70/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing *[Ko
 ** [depot.intertwingly.net :: out] remote: bjects:  92% (71/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  93% (72/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  94% (73/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  96% (74*[K/
 ** [depot.intertwingly.net :: out] remote: 77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  97% (75/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressing objects:  98% (76/77)   *[K
 ** [depot.intertwingly.net :: out] Unpacking objects:  62% (55/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  63% (56/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  64% (57/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  65% (58/88)
 ** [depot.intertwingly.net :: out] remote: Compressing objects: 100% (77/77)   *[K
 ** [depot.intertwingly.net :: out] remote: Compressi*[Kn
 ** [depot.intertwingly.net :: out] Unpacking objects:  67% (59/88)
 ** [depot.intertwingly.net :: out] remote: g objects: 100% (77/77), done.*[K
 ** [depot.intertwingly.net :: out] Unpacking objects:  68% (60/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  69% (61/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  70% (62/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  71% (63/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  72% (64/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  73% (65/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  75% (66/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  76% (67/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  77% (68/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  78% (69/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  79% (70/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  80% (71/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  81% (72/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  82% (73/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  84% (74/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  85% (75/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  86% (76/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  87% (77/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  88% (78/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  89% (79/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  90% (80/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  92% (81/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  93% (82/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  94% (83/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  95% (84/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  96% (85/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  97% (86/88)
 ** [depot.intertwingly.net :: out] Unpacking objects:  98% (87/88)
 ** [depot.intertwingly.net :: out] Unpacking objects: 100% (88/88)
 ** [depot.intertwingly.net :: out] Unpacking objects: 100% (88/88), done.
 ** [depot.intertwingly.net :: out] remote: Total 88 (delta 13), reused 0 (delta 0)*[K
 ** [depot.intertwingly.net :: out] From sa3ruby@depot.intertwingly.net:git/depot
 ** 4eaa140..8dd1afc  master     -> origin/master
 ** [depot.intertwingly.net :: out] HEAD is now at 8dd1afc convert to Rails!
    command finished
    copying the cached version to /home/sa3ruby/depot.intertwingly.net/releases/20090711163952
  * executing "cp -RPp /home/sa3ruby/depot.intertwingly.net/shared/cached-copy /home/sa3ruby/depot.intertwingly.net/releases/20090711163952 && (echo 8dd1afcc2636408adcbb662c18528b2347e43d57 > /home/sa3ruby/depot.intertwingly.net/releases/20090711163952/REVISION)"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
  * executing `deploy:finalize_update'
  * executing "chmod -R g+w /home/sa3ruby/depot.intertwingly.net/releases/20090711163952"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
    triggering after callbacks for `deploy:update_code'
  * executing `deploy:set_db_path'
  * executing "sed -i 's|db/production|/home/sa3ruby/depot.intertwingly.net/depot|' /home/sa3ruby/depot.intertwingly.net/releases/20090711163952/config/database.yml"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
  * executing `deploy:set_gem_home'
  * executing "sed -i '1iENV[%(GEM_HOME)]=%(/home/sa3ruby/.gems)\\n' /home/sa3ruby/depot.intertwingly.net/releases/20090711163952/config/environment.rb"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
  * executing `deploy:migrate'
  * executing "ls -xt /home/sa3ruby/depot.intertwingly.net/releases"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
  * executing "cd /home/sa3ruby/depot.intertwingly.net/releases/20090711163952; rake RAILS_ENV=production  db:migrate"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
 ** [out :: depot.intertwingly.net] (in /home/sa3ruby/depot.intertwingly.net/releases/20090711163952)
 ** [out :: depot.intertwingly.net] ==  CreateProducts: migrating =================================================
 ** [out :: depot.intertwingly.net] -- create_table(:products)
 ** [out :: depot.intertwingly.net] -> 0.0884s
 ** [out :: depot.intertwingly.net] ==  CreateProducts: migrated (0.0886s) ========================================
 ** [out :: depot.intertwingly.net] 
    command finished
  * executing `deploy:migrate'
  * executing "cd /home/sa3ruby/depot.intertwingly.net/releases/20090711163952; rake RAILS_ENV=production  db:migrate"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
 ** [out :: depot.intertwingly.net] (in /home/sa3ruby/depot.intertwingly.net/releases/20090711163952)
    command finished
  * executing `deploy:symlink'
  * executing "rm -f /home/sa3ruby/depot.intertwingly.net/current && ln -s /home/sa3ruby/depot.intertwingly.net/releases/20090711163952 /home/sa3ruby/depot.intertwingly.net/current"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
    triggering after callbacks for `deploy:symlink'
  * executing `deploy:restart'
  * executing "mkdir -p /home/sa3ruby/depot.intertwingly.net/current/tmp"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
  * executing "touch /home/sa3ruby/depot.intertwingly.net/current/tmp/restart.txt"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
    triggering after callbacks for `deploy:restart'
  * executing `deploy:cleanup'
*** no old releases to clean up
  * executing `deploy:update_crontab'
  * executing "cd /home/sa3ruby/depot.intertwingly.net/current && whenever --update-crontab depot"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
 ** [out :: depot.intertwingly.net] [write] crontab file updated
    command finished
  * executing `deploy:restart'
  * executing "mkdir -p /home/sa3ruby/depot.intertwingly.net/current/tmp"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
  * executing "touch /home/sa3ruby/depot.intertwingly.net/current/tmp/restart.txt"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
    command finished
    triggering after callbacks for `deploy:restart'
  * executing `deploy:cleanup'
*** no old releases to clean up
  * executing `deploy:update_crontab'
  * executing "cd /home/sa3ruby/depot.intertwingly.net/current && whenever --update-crontab depot"
    servers: ["depot.intertwingly.net"]
    [depot.intertwingly.net] executing command
 ** [out :: depot.intertwingly.net] [write] crontab file updated
    command finished

See this live.

get http://depot.intertwingly.net/products

Listing products

Base Title Description Image url Price

New product