Tag: Video

« Previously Next Page »

How to Post Your Flights to Twitter via OAuth

I just posted a writeup of the new Twitter posting feature on the Jetrecord blog, complete with a short screencast hosted on Vimeo. Hopefully you’ll get a good idea of how the OAuth handshake works from an end user perspective.

The Building of Jetrecord: Episode 4: Cucumbers and Webrats!

The message here is that with automated tests, we don’t have to worry about code being fragile. We can try out some stuff we think might work, and test it in seconds. We can be more courageous with our coding, and not have to code with cotton gloves on. — Test Driven Development

In the last episode I started my Rails code, committed it to a Git repository, and released my first deployment with Capistrano. In this episode I’m going to start my first feature by writing some tests and then implementing the code.

Cue film.

Download (right-click and save) QuickTime MOV (72 Mb) | QuickTime iPod MP4 (19 Mb) | QuickTime SMIL with captions (72 Mb) [Note: you must choose to open the SMIL file with QuickTime]

Adding RSpec, Cucumber, and Webrat to our Rails App

With the RSpec-Rails, Cucumber, and Webrat gems installed, let’s start a new branch in our Git repository and set up our app for testing:

$> git branch cucumber
$> git checkout cucumber
$> script/generate cucumber
$> script/generate rspec

Now edit RAILS_ROOT/features/support/env.rb. This file was created when we ran script/generate cucumber. We need to change a few lines. Here is what my env.rb file looks like after editing.

# Sets up the Rails environment for Cucumber
ENV["RAILS_ENV"] = "test"
require File.expand_path(File.dirname(__FILE__) + '/../../config/environment')
require 'cucumber/rails/world'
require 'cucumber/formatters/unicode' # Comment out this line if you don't want Cucumber Unicode support
Cucumber::Rails.use_transactional_fixtures
 
# Comment out the next two lines if you're not using RSpec's matchers (should / should_not) in your steps.
require 'webrat/rails'
require 'cucumber/rails/rspec'

We can add autotesting capability by installing the ZenTest gem. Autotest re-runs our tests after every code change, greatly speeding up our testing process. After getting the gem installed, we need to set some global variables so autotest knows to run our RSpec and Cucumber tests. Open up your .bash_profile file (usually located in your user’s home directory on your system). Add the following lines to the bottom of the file.

export RSPEC=true
export AUTOFEATURE=true

Additionally, if you’re on a Mac, I think it’s worth setting up Growl notifications for autotest. It’s not a requirement for testing or even using autotest but it’s a nice enhancement. Search Google for “autotest+growl” and you’ll find a number sites that will show you how to set it up.

Note: Growl notifications currently don’t work with Cucumber but they do work with RSpec and we will get into using RSpec eventually.

Let’s write a feature test to check our output from the last episode. If you remember, all we did was set up our home page to say “Hi.” I’m going to write a test to make sure it says that.

$> script/generate feature Page
      exists  features/step_definitions
      create  features/manage_pages.feature
      create  features/step_definitions/page_steps.rb

Open up the RAILS_ROOT/features/manage_pages.feature file. I’m writing my test like so:

Feature: First Test
  In order to prove that I can write a quick feature
  Harry
  wants to write a simple test
 
  Scenario: Friendly home page
    Given I am on the home page
    Then I should see "Hi."

Now I save that and run the test with rake:

$> rake features
(in /Users/harry/Sites/jetrecord)
Feature: First Test  # features/manage_pages.feature
  In order to prove that I can write a quick feature
  Harry
  wants to write a simple test
  Scenario: Friendly home page  # features/manage_pages.feature:6
    Given I am on the home page # features/manage_pages.feature:7
    Then I should see "Hi."     # features/step_definitions/webrat_steps.rb:83
 
1 scenario
1 step skipped
1 step pending (1 with no step definition)
 
You can use these snippets to implement pending steps which have no step definition:
 
Given /^I am on the home page$/ do
end

So the test ran but didn’t pass because I don’t have a step defined that tells Cucumber how to get to the home page. Thankfully, it gives me a snippet of code at the end of the output there. I open up RAILS_ROOT/features/step_definitions/page_steps.rb and add that in, filling it in with the code it needs to fulfill that step.

Given /^I am on the home page$/ do
  visit '/'
end

Quite simply, I’m telling Cucumber to visit the URL that looks like ‘/’, which is the home page. I run the test again.

$> rake features
(in /Users/harry/Sites/jetrecord)
Feature: First Test  # features/manage_pages.feature
  In order to prove that I can write a quick feature
  Harry
  wants to write a simple test
  Scenario: Friendly home page  # features/manage_pages.feature:6
    Given I am on the home page # features/step_definitions/page_steps.rb:1
      No route matches "/" with {:method=>:get} (ActionController::RoutingError)
      [stacktrace info]
 
1 scenario
1 step failed
1 step skipped
rake aborted!

Okay, we’re getting somewhere but we didn’t move very far. Cucumber barfed because it was looking for a route defined by the Rails application. Cucumber runs in the context of the Rails application, not of the whole site architecture. It doesn’t care that I have an index file in the public directory because it’s not actually crawling my web site like a browser would.

This is not necessarily a bad thing. If I wanted I could install Selenium, hook into it via Cucumber and re-run the tests. This would catch my index.html page because Selenium acts like a web browser. Eventually, I can see myself doing that because I’m going to need to test some Ajax interactions. But right now it’s not necessary.

In order to run my application I need to remove the index file anyway, so the real solution is to define my home page in Rails and then test again. To do that I need to add a route to my RAILS_ROOT/config/routes.rb file. Then I need to define the controller and a view for the home page.

# config/routes.rb
ActionController::Routing::Routes.draw do |map|
  map.root :controller => 'pages', :action => 'home'
end
# app/controllers/pages_controller.rb
class PagesController < ApplicationController
  def home
  end
end
# app/views/pages/home.html.erb
Hi.
# app/views/layouts/application.html.erb
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
        "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
  <head>
    <meta http-equiv="Content-type" content="text/html; charset=utf-8" />
    <title>Hello</title>
  </head>
  <body>
    <%= yield %>
  </body>
</html>

Alright. One last thing. We need to rename or remove the public/index.html file. I’m just going to remove it. Then we try the test again.

$> git rm public/index.html
$> rake features
(in /Users/harry/Sites/jetrecord)
Feature: First Test  # features/manage_pages.feature
  In order to prove that I can write a quick feature
  Harry
  wants to write a simple test
  Scenario: Friendly home page  # features/manage_pages.feature:6
    Given I am on the home page # features/step_definitions/page_steps.rb:1
    Then I should see "Hi."     # features/step_definitions/webrat_steps.rb:83
 
1 scenario
2 steps passed

Bingo! Now let’s try running autotest.

$> autotest
loading autotest/cucumber_rails_rspec
/opt/ruby-enterprise-1.8.6-20080810/bin/ruby /opt/ruby-enterprise-1.8.6-20080810/lib/ruby/gems/1.8/gems/cucumber-0.1.15/bin/cucumber features --format progress --format autotest --color --out /var/folders/f1/f1HkdqHjFe43ByUhxU-FO++++TI/-Tmp-/autotest-cucumber.5265.0 
..

Great! It’s working. Two passing steps (the two dots at the end) and autotest is still running, waiting for me to do something stupid. I’m going to introduce an error into home.html.erb.

# app/views/pages/home.html.erb
Hello.

And then back to the terminal.

/opt/ruby-enterprise-1.8.6-20080810/bin/ruby /opt/ruby-enterprise-1.8.6-20080810/lib/ruby/gems/1.8/gems/cucumber-0.1.15/bin/cucumber features --format progress --format autotest --color --out /var/folders/f1/f1HkdqHjFe43ByUhxU-FO++++TI/-Tmp-/autotest-cucumber.5265.1 
.F
 
Failed:
 
1)
expected: /Hi./m,
     got: "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n        \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html>\n  <head>\n    <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\" />\n    <title>Hello</title>\n    \n  </head>\n  <body>\n    Hello.\n  </body>\n</html>" (using =~)
Diff:
@@ -1,2 +1,2 @@
-/Hi./m
+"<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n        \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html>\n  <head>\n    <meta http-equiv=\"Content-type\" content=\"text/html; charset=utf-8\" />\n    <title>Hello</title>\n    \n  </head>\n  <body>\n    Hello.\n  </body>\n</html>"
 
[stacktrace info]
 
./features/step_definitions/webrat_steps.rb:84:in `Then /^I should see "(.*)"$/'
features/manage_pages.feature:8:in `Then I should see "Hi."'

And that’s how it works. We write a test, it fails, we write some code to pass the test, it passes.

I have to apologize. This post has already become too long. I like the direction it took but I don’t think moving on to writing an actual Jetrecord feature would be wise right now. So, really, next time I’m going to write tests for the first feature. I’ll even include some RSpec in there.

Coming Up in the Next Episode

No more rhymes now, I mean it. I’m going to write some tests for the first feature. Until next time, cheers and happy flying.

Material You May Find Useful Related to This Episode

Cucumber: Behaviour Driven Development with elegance and joy

Creative Commons License The Building of Jetrecord by Harry Love is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License. When code, text, or media in this series is not created by me and is not in the public domain I will provide links to their sources from which you can find their respective licenses and terms of use.

The Building of Jetrecord: Episode 3: Git, Capistrano, and A Test Release

What Mayer thinks will be essential for continued innovation is for Google to keep its sense of fearlessness. “I like to launch [products] early and often. That has become my mantra,” she says. She mentions Apple Computer and Madonna. “Nobody remembers the Sex Book or the Newton. Consumers remember your average over time. That philosophy frees you from fear.” — Managing Google’s Idea Factory

In the last episode I briefly sketched out what I want to build. In this episode I’m going to start the project by creating a Git repository and deploying a test release with Capistrano.

Cue film.

Download (right-click and save) QuickTime MOV (74 Mb) | QuickTime iPod MP4 (9 Mb) | QuickTime SMIL with captions (74 Mb) [Note: you must choose to open the SMIL file with QuickTime]

A Few Quick Changes

First, I changed my mind about setting up the complicated user management system from the beginning. I realized that I would be violating one of the tenets of Agile/XP: YAGNI. It reminds us to keep focused on the current feature and save details of any related features for the stories that describe them. Flights don’t need to know about the implementation details of users. Therefore, I’m only going to worry about flights for the time being. When called for I will stub out a User model, authentication, et cetera.

Second, I forgot to add a couple gems that will help us write stories and integration tests: Webrat and FactoryGirl. Webrat is a library for writing tests that crawl your site in memory, visiting links, testing inputs, submitting forms, and so on. FactoryGirl is a library that replaces test fixtures with factories. You can install both with RubyGems. We’re not going to need them until the next episode; I just wanted to mention them now.

Some Conventions

Starting the Rails App

Depending on your personality this is either the best part of the project or the worst: the blank canvas. You installed Rails and all of the prerequisites I listed in episode 2. All of our hopes and dreams for a perfect app begin with this command:

$> rails jetrecord

I have set up my development environment to use Apache + Passenger to serve the site and have configured a virtualhost on my local machine to use “ld.com” for the site. (It doesn’t matter if ld.com actually exists or not. As far as my machine is concerned, it points to my local Rails app.) If you’re using Mongrel, Webrick, Thin, or something else as your development server you can run script/server from the RAILS_ROOT.

Since I’m using Apache and I’ve set it to launch at boot time I don’t need to use script/server. I can just browse to ld.com. When I do, I see this.

Rails app home screen for Jetrecord

Great! We’re off and running.

Setting Up Our Repository with Git

Git is installed on my machine. To make a repository, I cd to RAILS_ROOT and do this:

$> git init
Initialized empty Git repository in /Users/harry/Sites/jetrecord/.git/
 
$> git status
# On branch master
#
# Initial commit
#
# Untracked files:
#   (use "git add ..." to include in what will be committed)
#
#	README
#	Rakefile
#	app/
#	config/
#	doc/
#	log/
#	public/
#	script/
#	test/
nothing added to commit but untracked files present (use "git add" to track)

If I run git add right now, Git will recursively add everything in RAILS_ROOT to the repository. But I don’t want that. There are a few items I don’t want to track. To have Git automatically ignore those items, I will create a .gitignore file. Each line tells Git what to ignore.

$> nano -w .gitignore                                                                 
 
config/database.yml
*~
*.cache
*.log
*.pid
tmp/**/*
.DS\_Store
db/cstore/**
doc/*.dot
doc/api
doc/app
doc/plugins
coverage/*
db/*.sqlite3
*.tmproj
Capfile

I save that file. Now I add everything to the repository and make the initial commit.

$> git add .
$> git commit -m "First commit"
 
Created initial commit 4d55351: First commit
 46 files changed, 8525 insertions(+), 0 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 README
 create mode 100644 Rakefile
 create mode 100644 app/controllers/application.rb
 create mode 100644 app/helpers/application_helper.rb
 create mode 100644 config/boot.rb
 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/inflections.rb
 create mode 100644 config/initializers/mime_types.rb
 create mode 100644 config/initializers/new_rails_defaults.rb
 create mode 100644 config/locales/en.yml
 create mode 100644 config/routes.rb
 create mode 100644 doc/README_FOR_APP
 create mode 100644 public/404.html
 create mode 100644 public/422.html
 create mode 100644 public/500.html
 create mode 100755 public/dispatch.cgi
 create mode 100755 public/dispatch.fcgi
 create mode 100755 public/dispatch.rb
 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 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/performance/request
 create mode 100755 script/plugin
 create mode 100755 script/process/inspector
 create mode 100755 script/process/reaper
 create mode 100755 script/process/spawner
 create mode 100755 script/runner
 create mode 100755 script/server
 create mode 100644 test/performance/browsing_test.rb
 create mode 100644 test/test_helper.rb

The application is now being tracked by Git. One last thing before we move on to Capistrano. I’m going to change the default index page to show a simple “Hello, World.” I’m using TextMate, which is the reason for the mate command.

$> mate public/index.html

All I did was replace the dynamic stuff on the home page with “Hi.” You’ll see it in a minute. Alright. That’s all I’m going to do for the first release. Now I need to move the repository to a remote location so I can pull from it when I release new code.

$> ssh REMOTE
REMOTE $> mkdir etc/git/jetrecord.git
REMOTE $> cd etc/git/jetrecord.git
REMOTE $> git init --bare
Initialized empty Git repository in /home/user/etc/git/jetrecord.git/
REMOTE $> exit
Connection closed
 
$> git remote add origin ssh://user@REMOTE/home/user/etc/git/jetrecord.git
$> git push origin master
Counting objects: 68, done.
Compressing objects: 100% (61/61), done.
Writing objects: 100% (68/68), 80.67 KiB, done.
Total 68 (delta 17), reused 0 (delta 0)
To ssh://user@REMOTE/home/user/etc/git/jetrecord.git
 * [new branch]      master -> master

Good. Now I have a remote Git repository. Any changes that I push will go to this remote location that will also be accessible to Capistrano. And so we move on to Capistrano.

Capistrano

I have the Capistrano gem installed. I just need to initialize it for my app and then fix up my deployment recipe.

$> capify .
[add] writing './Capfile'
[add] writing './config/deploy.rb'
[done] capified!
 
$> mate config/deploy.rb

Here’s my deploy.rb

set :use_sudo, false
set :application, "jetrecord"
set :repository,  "ssh://user@REMOTE/home/user/etc/git/jetrecord.git"
set :deploy_to, "/var/www/apps/#{application}"
set :user, 'user'
set :runner, user
set :scm, :git
set :domain, 'REMOTE'
role :app, domain
role :web, domain
role :db,  domain, :primary => true
set :server_name, "landingdeparting.com"
set :server_alias, "*.landingdeparting.com"
depend :remote, :command, :gem
 
# Allow ssh to use ssh keys
set :ssh_options, { :forward_agent => true }
 
deploy.task :restart do
  # Restart Passenger
  run "touch #{current_path}/tmp/restart.txt"
end
 
deploy.task :symlinks do
  run "ln -nfs #{shared_path}/config/database.yml #{release_path}/config/database.yml"
end
 
after :deploy, 'deploy:cleanup'
after 'deploy:update_code', 'deploy:symlinks'

Cool. Commit the changes.

$> git add .
$> git commit -a -m "First stab at Capistrano deployment recipe"
Created commit e80f9dc: First stab at Capistrano deployment recipe
 1 files changed, 28 insertions(+), 0 deletions(-)
 create mode 100644 config/deploy.rb
 
$> git push
Counting objects: 6, done.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 775 bytes, done.
Total 4 (delta 2), reused 0 (delta 0)
To ssh://user@REMOTE/home/user/etc/git/jetrecord.git
   ba68814..e80f9dc  master -> master

Okay, one more thing on the remote end. In my deploy recipe I added a symlink from the database.yml file in the shared directory to the RAILS_ROOT/config directory. I’m doing this because I don’t want to track database.yml in my Git repository and it’s cheaper to create a symlink than to copy an actual file from one release to the next. But I still need to create the file manually. I’ll do that now.

$> ssh REMOTE
REMOTE $> mkdir /var/www/apps/jetrecord
REMOTE $> mkdir /var/www/apps/jetrecord/shared
REMOTE $> mkdir /var/www/apps/jetrecord/shared/config
REMOTE $> nano -w /var/apps/jetrecord/shared/config/database.yml
 
production:
  adapter: postgresql
  database: jetrecord_production
  user: dbuser
  password: pass
  pool: 5
  timeout: 5000

Save and exit. Don’t forget to set up the actual database on the remote server with the correct credentials!

With that done, all that’s left to do is make sure my domain name is set up and ready to be seen by the world. This will depend on a great number of things, such as where your site is hosted, what operating system it’s on, what DNS services you’re using, and so on.

I’m not a sysadmin and I’m not going to pretend to be one. The focus of this series is the application so I’ll move on assuming that we’ve got our domain set up and we’ve tested that we can reach the site with a text index page of some kind.

Now we’ll run the setup command, check our deployment recipe, and then do a deploy. For the deploy I’m going to run cap:deploy instead of cap:deploy:cold because I don’t have any database migrations to run yet.

$> cap deploy:setup
  * executing 'deploy:setup'
  * executing "mkdir -p /var/www/apps/jetrecord /var/www/apps/jetrecord/releases /var/www/apps/jetrecord/shared /var/www/apps/jetrecord/shared/system /var/www/apps/jetrecord/shared/log /var/www/apps/jetrecord/shared/pids &#038;&  chmod g+w /var/www/apps/jetrecord /var/www/apps/jetrecord/releases /var/www/apps/jetrecord/shared /var/www/apps/jetrecord/shared/system /var/www/apps/jetrecord/shared/log /var/www/apps/jetrecord/shared/pids"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
 
$> cap deploy:check
  * executing 'deploy:check'
  * executing "test -d /var/www/apps/jetrecord/releases"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
  * executing "test -w /var/www/apps/jetrecord"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
  * executing "test -w /var/www/apps/jetrecord/releases"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
  * executing "which git"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
  * executing "which gem"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
You appear to have all necessary dependencies installed
 
$> cap deploy
  * executing 'deploy'
  * executing 'deploy:update'
 ** transaction: start
  * executing 'deploy:update_code'
    executing locally: "git ls-remote ssh://user@REMOTE.joyent.us/home/user/etc/git/jetrecord.git HEAD"
  * executing "git clone -q ssh://user@REMOTE/home/user/etc/git/jetrecord.git /var/www/apps/jetrecord/releases/20090108224035 &#038;& cd /var/www/apps/jetrecord/releases/20090108224035 &#038;& git checkout -q -b deploy dbd8430254cd5bd2860c2b73349c149b1038512f &#038;& (echo dbd8430254cd5bd2860c2b73349c149b1038512f > /var/www/apps/jetrecord/releases/20090108224035/REVISION)"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
  * executing 'deploy:finalize_update'
  * executing "chmod -R g+w /var/www/apps/jetrecord/releases/20090108224035"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
  * executing "rm -rf /var/www/apps/jetrecord/releases/20090108224035/log /var/www/apps/jetrecord/releases/20090108224035/public/system /var/www/apps/jetrecord/releases/20090108224035/tmp/pids &#038;&\\\n      mkdir -p /var/www/apps/jetrecord/releases/20090108224035/public &#038;&\\\n      mkdir -p /var/www/apps/jetrecord/releases/20090108224035/tmp &#038;&\\\n      ln -s /var/www/apps/jetrecord/shared/log /var/www/apps/jetrecord/releases/20090108224035/log &#038;&\\\n      ln -s /var/www/apps/jetrecord/shared/system /var/www/apps/jetrecord/releases/20090108224035/public/system &#038;&\\\n      ln -s /var/www/apps/jetrecord/shared/pids /var/www/apps/jetrecord/releases/20090108224035/tmp/pids"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
  * executing "find /var/www/apps/jetrecord/releases/20090108224035/public/images /var/www/apps/jetrecord/releases/20090108224035/public/stylesheets /var/www/apps/jetrecord/releases/20090108224035/public/javascripts -exec touch -t 200901082240.37 {} ';'; true"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
    triggering after callbacks for 'deploy:update_code'
  * executing 'deploy:symlinks'
  * executing "ln -nfs /var/www/apps/jetrecord/shared/config/database.yml /var/www/apps/jetrecord/releases/20090108224035/config/database.yml"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
  * executing 'deploy:symlink'
  * executing "rm -f /var/www/apps/jetrecord/current &#038;& ln -s /var/www/apps/jetrecord/releases/20090108224035 /var/www/apps/jetrecord/current"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
 ** transaction: commit
  * executing 'deploy:restart'
  * executing "touch /var/www/apps/jetrecord/current/tmp/restart.txt"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
    triggering after callbacks for 'deploy'
  * executing 'deploy:cleanup'
  * executing "ls -xt /var/www/apps/jetrecord/releases"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished
 ** keeping 5 of 6 deployed releases
  * executing "rm -rf /var/www/apps/jetrecord/releases/20090108222452"
    servers: ["REMOTE"]
    [REMOTE] executing command
    command finished

Okay, done. Now we check to see if landingdeparting.com shows my Rails app.

LD says 'hi'

So that’s it. We have our Rails project started. We have our source code in a Git repository. We can deploy the app to our server with Capistrano. And we can view our handiwork live on the web. Now it’s time to build the real application. We’ll take it bird by bird.

Epilogue

From now on when I type something into the terminal I’m only going to show the output of the command if I think it’s relevant for the topic of the episode. In this episode, for instance, I set up Git and Capistrano and I thought it would be good to see how these programs respond. In future episodes I’m not going to show output from Git or Capistrano unless it’s relevant.

I’m going to follow the same general rule for other commands. If it’s relevant to the topic at hand or if a special case needs to be addressed, I’ll show it. Otherwise, I’ll only show the commands I type in. You can also assume that I am making regular commits to git and pushing those changes live with Capistrano. I don’t intend to show you every commit message and the result of every deployment here in the text unless there’s a special case. Some of these things may show up in the videos, but they get a little tedious here. Does that make sense? Each episode will build on previous episodes with regards to the minutiae that I share.

Coming Up in the Next Episode

I’m going to start building the first feature by writing some tests and then writing the code that implements the feature. Until next time, cheers and happy flying.

Material You May Find Useful Related to This Episode

Git: the fast version control system

Capistrano

Phusion Passenger

Pragmatic Version Control Using Git by Travis Swicegood

Deploying Rails Applications by Ezra Zygmuntowicz, Bruce Tate, and Clinton Begin

Creative Commons License The Building of Jetrecord by Harry Love is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License. When code, text, or media in this series is not created by me and is not in the public domain I will provide links to their sources from which you can find their respective licenses and terms of use.

The Building of Jetrecord: Episode 2: Tell Me a Story

Sketches are quick, dirty, and cheap and that’s exactly how you want to start out. Draw stuff. Scrawl stuff. Boxes, circles, lines. Get your ideas out of your head and onto paper. The goal at this point should be to convert concepts into rough interface designs. This step is all about experimentation. There are no wrong answers. — From Idea to Implementation — Getting Real

In the last episode I talked about high level goals for the project and I created a framework of rules for myself in order to help me make decisions. In this episode I’m going to follow one of the rules I created for myself:

11. Each feature must begin with a story discussing the feature and its impact, followed by sketches of APIs and GUIs, followed by test coverage, followed by implementation.

Cue film.

Download (right-click and save) QuickTime MOV (39 Mb) | QuickTime iPod MP4 (8 Mb) | QuickTime SMIL with captions (39 Mb) [Note: you must choose to open the SMIL file with QuickTime]

Am I Building the Right Thing?

One of the questions we ended with last time was, “How do I know that what I’m building is what people really want?” The answer is: ask them.

I’m going to follow a principle from Extreme Programming that says you should have a customer on site with you as you build. If you can’t get a real customer, one of you has to play the role of Customer. That means I’m going to play the customer for my own application.

Is this legal? Yes and no. Under ideal circumstances the customer should be someone who is not a developer because features are a business decision, not a programming decision. Really, it should be the client paying for the engagement or, in the case of retail software, someone from the business side of your organization who is familiar with the domain and the needs of your customers. By playing both roles there’s a possibility that competing demands will cloud the judgment either of the Developer role or the Customer role.

At the same time, I’m a customer of Jetrecord. At least, I plan to be. So in a sense I’m building software for myself, so perhaps the two conflicts cancel each other out.

In any case I don’t have a choice.

Why Agile (or something like it)?

In my limited experience as a developer I’ve come to discover that programming is simply a conversation you have with your code. Just like writing an article, it may begin on one topic and end up on something completely different. Thinking about programming this way fits my personality and the way I learn new things. I love things that grow and take on a life of their own. I also like to look at the big picture several times during the course of a project. If I keep my head down for too long it’s easy to get lost in the details and pretty soon I’ve veered off course.

Certainly every circumstance is different and you have to adjust for the needs of the project. Agile, Extreme Programming, and the like don’t work for everything. When the choice is up to me, though, and the type of engagement is a good fit, I like the communication, freedom, and feeling of progress Agile affords.

And, of course, let’s remember that I’m not following Agile methodologies to the letter. I’m using methods that will help me get the project done reasonably and responsibly. I believe this follows the spirit of Agile, if nothing else.

Assembling B-25 bombers at North American Aviation

The Basic Features

The customer’s job is to define the features of the application. Here they are in broad terms:

  1. Log flights
  2. Visualize flights and hours
  3. Export flights

I’m not going to worry about advanced and/or paying features yet. We’ll save those for later. If I don’t get these basic features right, there’s no point moving forward. These are the features that I, the Customer, believe bring the most value to the application at this point in the project.

Creating Stories

An Agile feature story is a “promise for conversation” according to Alistair Cockburn. That means, it’s important to get it down on paper so that we, the customer and developer, can talk about it at length later on. I’ll make sketches and write tests based on the stories I create. As for detail, I’m going to shoot for making each story completable in the span of one to two weeks, including tests.

Logbook Feature Stories

Discussion

That’s enough to start. None of these have enough detail to tell me everything I need to know about the feature but they help me start conversations with the customer. They also tell me that there are some fundamental features that need to be developed in order support the ones listed. I would look at this list and begin discussing the following:

Also, we’ve talked about flights, but flights belong to users. That means we need a system in place for managing users and their needs.

New B-25 bombers lined up for final inspection

User Feature Stories

Discussion

It looks like we need a fairly complicated user management system in addition to being able to log flights. Ideally, with a larger team, we could work on both at the same time. However, since it’s only me, and since I’m also the customer, I’ve decided that the user management system is the first thing I’ll do after setting up my development and production environments.

Sketches

Again, these are sketches. Not blueprints or wireframes. Not schematics or Photoshop comps. They have just enough detail to let me know what I’m building so I can begin creating HTML mockups, acceptance tests, and unit tests. Ideally, as the developer, I’m drawing these while seated next to the customer so we can make changes quickly and throw things away if need be.

Flight Forms

Flight forms sketch

User Forms

User forms sketch

User API

User API sketch

Inventory

At this point I should be able to take the sketches and start building the application using whatever language I choose. I had a great time using the Ruby on Rails framework to build version 1 and I’ve decided to use it again for version 2. It’s well-suited to handle an application of this type and the Ruby language is, for lack of a better word, fun.

These are the things I need to get going (note: gems are installed with RubyGems):

Optional: Ruby on Rails comes with a simple web server that we can use for development purposes. However, I’m going to set up and practice using the server that I will use in production, even though the OS will be different:

I’m not going to cover installation. I assume you can follow the instructions given on their respective web sites and that you know how to use Google. Assuming you’re going to follow along as I write code, if you’ve never worked with Rails before I recommend installing everything and working through any of the simple tutorials you can find on the web before continuing on.

Also, please note, from here on out I’m not going to talk about the topic of programming per se. If you’ve never programmed before, the rest of the series might be interesting to watch but otherwise might go over your head. Let me say, though, that there are hundreds of web-based tutorials that can teach you how to program. There are even some that use Ruby.

Coming Up in the Next Episode

I’m going to set up my Rails project and my Git source code repository. Then I’m going to make sure I can do an initial release. Until next time, cheers and happy flying.

Material You May Find Useful Related to This Episode

Extreme Programming Installed book cover Extreme Programming Installed by Ron Jeffries

Designing Interfaces book cover Designing Interfaces by Jenifer Tidwell

Universal Principles of Design book cover Universal Principles of Design by William Lidwell

Moleskine notebook Moleskine Storyboard Notebook Pocket

Creative Commons License
The Building of Jetrecord by Harry Love is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License. When code, text, or media in this series is not created by me and is not in the public domain I will provide links to their sources from which you can find their respective licenses and terms of use.

The Building of Jetrecord: Episode 1: The Tabula Rasa of Doom

We were out at our family cabin in Bolinas, and he was at the kitchen table close to tears, surrounded by binder paper and pencils and unopened books on birds, immobilized by the hugeness of the task ahead. Then my father sat down beside him, put his arm around my brother’s shoulder, and said, “Bird by bird, buddy. Just take it bird by bird.” –Anne Lamott

I don’t think any of us ever realizes the hugeness of the task until we sit down with our materials and attempt to make sense of it all, even though the whole making-order-out-of-chaos thing is highly overrated in my opinion. Nevertheless, that’s what I’m going to do here in this series. I’m going to take a seemingly simple task–building an online logbook for pilots–expose the underbelly, cut it apart, and then put it all together into a fantastic, cohesive product that everyone will love, including me and you and every pilot you know.

Why? Because I believe we are created to do two things: experience pain and build things. Sometimes these are one and the same. I hate to admit right from the start that this is a task to which I feel compelled, by which I feel repulsed, and about which I feel nonplussed but apparently we’re in a new age of transparency so I’m starting with me, as Michael Jackson would say. I both love and hate computers and projects. And it always happens that about the time I’ve finished a web project I’ve forgotten all about this crummy beginning stage and am strangely excited to do it all over again.

Cue film.

Download (right-click and save) QuickTime MOV (49 Mb) | QuickTime iPod MP4 (7 Mb) | QuickTime SMIL with captions (49 Mb) [Note: you must choose to open the SMIL file with QuickTime]

It Begins Like This

  1. I’m building version 2 of an online logbook for pilots called Jetrecord. This is my attempt to record and share the process.
  2. This is not an exercise (please place your hands inside the yellow circles); it’s the process of building a genuine application that is already live on the web. What you see here is what will be released for version 2. I’m hoping that by sharing something real you’ll be inspired to create your own projects and you’ll see what it’s like for one developer to wade through this stuff.
  3. As far as sharing goes you are free to take the code, the text of this series, and the videos and reuse, repurpose, and remix them under the terms of the Creative Commons Attribution-Share Alike 3.0 United States License. More details are at the end of this post.
  4. My other hope is that we can learn from each other. I am not an expert, as will become more apparent as the series goes on. Please contribute your knowledge in the comments. Can something be improved? Do you have a suggestion or a critique? I want to hear it. Please keep it constructive, though. Shuriken are not allowed.
  5. If you’d like to contact me by email concerning the series, use harry@harrylove.org.

The Scope of the Series

The process of creating a web application from start to finish involves several subjects and my plan is to explore breadth rather than depth. That is, I’m going to talk about whatever will help me complete the project and I’ll mostly concentrate on the places where subjects overlap. If I don’t explain things in enough detail, I apologize. The truth is I may not know enough about a subject to explore it in depth. I’ll try to point you to the places I’m getting my information. Don’t hesitate to speak up if you’d like me to say more about a particular topic. If I have something more to say, something worth creating a new episode for, I may just do it. Otherwise, of course, I’ll point you to my sources.

Let’s Start with Questions, Goals, and Rules

I’m going to start this project the same way I would approach an essay, article, or presentation, by asking questions and then stating goals and rules. You don’t have to do it this way. This is how I do it because it works for me. I’m going to say that just once. It applies to everything I’m going to do from here on out but I don’t want to bore you to death by repeating myself every episode (there’s an important programming principle in there somewhere).

Nose wheel and landing gear assembly

Questions

Goals

Succinctly and broadly, what is the desired end state? For example, the goal of basketball is to have more points than your opponent at the end of the allotted time for the game.

P-51 Mustang

Mantra

Pilots Aren’t Robots Yet!

Rules

I’m writing some rules for myself that will serve as a guide for the decisions I make. They are written with this project in mind only.

  1. Rules should help us make decisions; this is the only criteria for adding a rule.
  2. Modify or discard any rules that are not helpful.
  3. Use the best design principles we know how to use and spend time researching concepts we are unfamiliar with.
  4. Make forward progress; no product is perfect.
  5. Follow the spirit of Agile methods for software development; remember rule #2 above
  6. Keep documentation in the source code except where sensitive information may compromise security or business secrets.
  7. Documentation is for developers and is an interface into the system; design it well.
  8. Everything should be added to the source control repository unless there is a good reason not to.
  9. Code that can be extracted into a shared library (gem) or plugin should be.
  10. Favor gems, plugins, other libraries, and framework code to writing our own unless there is a good reason not to.
  11. Each feature must begin with a story discussing the feature and its impact, followed by sketches of APIs and GUIs, followed by test coverage, followed by implementation.
  12. Metrics should be built into the project early and often.
  13. Questions drive the need for answers which drive the need to record data, not the other way around.
  14. Build for data access first and GUI display second.
  15. Build for universal access.
  16. Build for progressive enhancement.
  17. Browsers must prove their worth to be included in our support; mainstream use is not proof in and of itself.
  18. See also Matthew Moore’s Ruby on Rails Code Quality Checklist; remember rule #2 above

Coming Up in the Next Episode

Next I’m going to write a brief outline of the basic application. I’m also going to sketch the application on paper and make a list of parts I need to get started. I hope you’ll join me. You can subscribe to the RSS feed in order to be notified when the next episode is up. I have also created a table of contents page for this series. Until next time, cheers and happy flying.

Material You May Find Useful Related to This Episode

Art of the Start book cover The Art of the Start by Guy Kawasaki

Bird by Bird book cover Bird by Bird by Anne Lamott

The Spirit of St. Louis book cover The Spirit of St. Louis by Charles Lindbergh

Building Scalable Web Sites book cover Building Scalable Web Sites by Cal Henderson

Getting Real by 37signals

List of software development philosophies on Wikipedia

Creative Commons License
The Building of Jetrecord by Harry Love is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License. When code, text, or media in this series is not created by me and is not in the public domain I will provide links to their sources from which you can find their respective licenses and terms of use.

« Previously Next Page »