Asian Dawn?
I read about them in Time magazine.
I read about them in Time magazine.
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]
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.
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.
Cucumber: Behaviour Driven Development with elegance and joy
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.
Wolfman Jack: Who’s this on The Wolfman telephone?
Operator: Hello, I have a collect call.
Wolman Jack: Pardon me, your name is Collette?
Operator: Collect call for…
Wolfman Jack: Your name is Collette Call?
Operator: Sir, this is the operator.
Wolfman Jack: Are you French, Operator?
Operator: Collect call for Wolfman Jack.
Wolfman Jack: I love you, Operator.
This is how I responded to a friend looking for website advice. My friend was at the very beginning stages of getting a website made and wanted to know how to begin. I think my advice is applicable to a lot of websites so I’d like to share it with you.
My friend needed a site to promote a musician and potentially sell music/merchandise. The following is my response, almost verbatim. Names have been changed where appropriate.
The first thing I would do is write. Write the message(s) you want to tell people about Fred. The type of web site you are describing is part billboard, part brochure, and part catalog. You need to sell Fred before you can sell his music or tickets to his shows.
If it were me, I would approach it in this order:
Okay, so I’ve written all of that. Why? Because web design is just another type of design. You can’t design without something to design around. You can’t write a song without knowing the subject, knowing your audience, and knowing everything you want to communicate to your audience.
(That’s not exactly true, of course. You can meander your way through writing music, jotting down any stream-of-consciousness lyrics that come to mind, but you’ve got to be really good to pull it off. Most people aren’t good, which is why popular music today sounds so shitty. There’s almost no consideration for craft. The same goes for most other creative endeavors.)
The other reason I’m writing first is because sometimes, in the middle of writing the message, I discover that the message is something entirely different than what I thought it would be when I started writing it. Like writing a book.
Web designers take your message and craft it into a digital experience that communicates directly to your specific audience. The best designers find a way to craft a site that feels like home to your audience, as if it was built especially for them, which translates into sales, positive reviews, and word of mouth advertising.
I consider the act of creating the content (in your case, the music) and the message about that content–and doing it first–to be absolutely essential to a successful web site design. If you start with design, you may get something nice but, at best, it will look and feel like every other web site you’ve ever seen about a musician or a band. It ends up looking like the endless rows of tomato sauce in the grocery store.
However, if you start by creating the best content and the best marketing message, you can design a site to fit. It’s a bespoke suit versus something off the rack from The Men’s Wearhouse. I’m not saying there’s anything wrong with The Men’s Wearhouse, but a bespoke suit communicates something about the wearer that The Men’s Wearhouse cannot.
Okay, I’m rambling.
What about getting it done? I think you have lots of options available to you. You can go from free to $10,000+ depending on your needs. If you just want something up now, I would visit any of the popular blogging sites like Wordpress.com, Typepad.com, or Blogger.com. You can be up and running within a day with something very simple with which you can communicate news to his audience. Many of these types of sites also have no fee, which means you can discard it when you outgrow it.
A step above that would be contacting one of Fred’s friends. Does he know anyone with techie skills who’d be willing to set up and/or design something and then keep the software up to date? Once again you can use free website/blogging software and just customize it for your needs. The only hard part is finding someone who knows how to set it all up and is willing to continue maintaining it.
A step above that is hiring a contractor who can either manage the project for you or do most of the work. Most individual contractors are good at one thing: either graphic design or web design1, but usually not both. The ones who are great at both cost more, obviously.
A step above that is hiring a web design firm to do it all for you. This is the bespoke suit level. Costs vary but you can expect to pay anywhere between $50 and $150+ per hour. Sometimes firms will give you a bid on completing the whole job for a set price.
With a simple web site, a good firm can handle the design and creation of the site in about a week or two, depending on their work load. More complexity leads to more time. E-commerce sites (those that sell merchandise) are more complex and expensive than brochure/billboard sites.
If you’re going to go with a design firm, I would also recommend finding a copywriter, assuming the design firm does not have someone in-house. A copywriter specializes in crafting your message into gold. And since the message and the content are the most important things, it’s a service worth paying for at that level of web site. The copywriter will usually work with the design firm since the message should go hand in hand with the visual design.
Unless you have an interest in learning how web sites work and how they’re built, I would highly recommend handing over every part of the design, construction, and maintenance to someone who does. Whether that’s a friend, a contractor, or a firm depends on your needs and your budget.
Again, without any content it’s really hard to know what your needs are. It’s like trying to find the right book publisher before you’ve written your book. Write the book first, then publish it.
I hope that’s helpful.
Cheers, Harry
1. I’m lumping interface design, user experience, HTML, CSS, JavaScript, and programming into the generic label of web design rather than splitting them into their typical, jargon-filled job titles.
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]
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.
$> is the command line prompt. Anything coming after it is a command that I’m typing into my terminal.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 jetrecordI 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.

Great! We’re off and running.
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.
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 && 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 && cd /var/www/apps/jetrecord/releases/20090108224035 && git checkout -q -b deploy dbd8430254cd5bd2860c2b73349c149b1038512f && (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 &&\\\n mkdir -p /var/www/apps/jetrecord/releases/20090108224035/public &&\\\n mkdir -p /var/www/apps/jetrecord/releases/20090108224035/tmp &&\\\n ln -s /var/www/apps/jetrecord/shared/log /var/www/apps/jetrecord/releases/20090108224035/log &&\\\n ln -s /var/www/apps/jetrecord/shared/system /var/www/apps/jetrecord/releases/20090108224035/public/system &&\\\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 && 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.

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.
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.
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.
Git: the fast version control system

Pragmatic Version Control Using Git by Travis Swicegood

Deploying Rails Applications by Ezra Zygmuntowicz, Bruce Tate, and Clinton Begin
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.