Missing host to link to! Please provide :host parameter

If you’ve followed my version of the authlogic account activation tutorial or the original version by Matt Hooks you might have run into this error:

Missing host to link to! Please provide :host parameter or set default_url_options[:host] when sending emails

When authlogic sends e-mails with the account activation link, it uses a url_for helper to build that link. Because the “Notifier” mailer is an instance of ActionMailer::Base and not ActionController::Base it doesn’t know what the host parameter of the URL should be, so you have to tell it explicitly.

Put the following into your environments/development.rb and environments/test.rb:

1
2
# This assumes you're running your local development server on port 3000 via script/server
config.action_mailer.default_url_options = { :host => "127.0.0.1:3000" }

Put this into your environments/production.rb:

1
2
# Replace example.org with your actual domain name
config.action_mailer.default_url_options = { :host => "example.org" }

Does the Chronic Time Parsing Library Break with DST Changes?

I’ve been troubleshooting a problem on an existing application for the last week or so that deals with the parsing of dates using the Chronic time parsing library. Today the problem magically solved itself, without me doing anything. These types of self solving problems are usually more frustrating than problems you can’t solve at all, so I took a little extra time to experiment with the particular date format I was using and found what might be a problem with the Chronic library when it gets to the “fall back” DST change in the fall.

Do You Observe DST?

It looks like if your local environment is set to a timezone which observes DST, like Eastern Standard Time for instance, you get a chunk of dates around the switch in the fall where Chronic returns nil instead of the correct date. If you’re on a machine where the timezone is set to an area which does not observe DST, like Arizona, you won’t be able to replicate this problem.

Replicating the problem

Install the latest version of Chronic.

$> sudo gem install chronic

Fire up irb and give the following a shot.

1
2
3
4
require 'chronic'
(Date.parse("2009-01-01")..Date.parse("2009-12-31")).each do |d|
  puts Chronic.parse("next tuesday 6am", :now => d)
end

With ruby 1.8.7 (2008-08-11 patchlevel 72) [i486-linux] on Ubuntu and my timezone set to EST/New York. I see a bunch of Tuesdays and then a blank section around the end of October / beginning of November.

...snip...
Tue Oct 20 06:00:00 -0400 2009
Tue Oct 20 06:00:00 -0400 2009
Tue Oct 27 06:00:00 -0400 2009
Tue Oct 27 06:00:00 -0400 2009
Tue Oct 27 06:00:00 -0400 2009
Tue Oct 27 06:00:00 -0400 2009
Tue Oct 27 06:00:00 -0400 2009
Tue Oct 27 06:00:00 -0400 2009
Tue Oct 27 06:00:00 -0400 2009
nil
nil
nil
nil
nil
nil
Tue Nov 03 06:00:00 -0500 2009
Tue Nov 10 06:00:00 -0500 2009
Tue Nov 10 06:00:00 -0500 2009
Tue Nov 10 06:00:00 -0500 2009
Tue Nov 10 06:00:00 -0500 2009
Tue Nov 10 06:00:00 -0500 2009
Tue Nov 10 06:00:00 -0500 2009
Tue Nov 10 06:00:00 -0500 2009
Tue Nov 17 06:00:00 -0500 2009
Tue Nov 17 06:00:00 -0500 2009
Tue Nov 17 06:00:00 -0500 2009
...snip...

If you change your timezone to something like MST/Arizona you’ll see this

...snip...
Tue Oct 20 06:00:00 -0400 2009
Tue Oct 20 06:00:00 -0400 2009
Tue Oct 27 06:00:00 -0400 2009
Tue Oct 27 06:00:00 -0400 2009
Tue Oct 27 06:00:00 -0400 2009
Tue Oct 27 06:00:00 -0400 2009
Tue Oct 27 06:00:00 -0400 2009
Tue Oct 27 06:00:00 -0400 2009
Tue Oct 27 06:00:00 -0400 2009
Tue Nov 03 06:00:00 -0500 2009
Tue Nov 03 06:00:00 -0500 2009
Tue Nov 03 06:00:00 -0500 2009
Tue Nov 03 06:00:00 -0500 2009
Tue Nov 03 06:00:00 -0500 2009
Tue Nov 03 06:00:00 -0500 2009
Tue Nov 03 06:00:00 -0500 2009
Tue Nov 10 06:00:00 -0500 2009
Tue Nov 10 06:00:00 -0500 2009
Tue Nov 10 06:00:00 -0500 2009
Tue Nov 10 06:00:00 -0500 2009
Tue Nov 10 06:00:00 -0500 2009
Tue Nov 10 06:00:00 -0500 2009
Tue Nov 10 06:00:00 -0500 2009
Tue Nov 17 06:00:00 -0500 2009
Tue Nov 17 06:00:00 -0500 2009
Tue Nov 17 06:00:00 -0500 2009
...snip...

What’s the Fix?

None as far as I know. I’m sure one could dig into the internals of the library and figure out how to deal with this problem, but I’m not up for that right now.

Make Fun of Your Client To Prevent Defects

One thing I’ve always heard about learning a new language is that you can’t consider yourself fully fluent until you can tell a joke. Telling a joke requires the understanding of homonyms and how words flow together. “So a guy walks into a bar…” sounds different than “A man enters beverage store…”.

When dealing with clients, it’s easy to joke about your client, but don’t miss an opportunity to prevent future missteps once you know more about them.

How often do you get off the phone with a client and immediately have a funny remark about some critique or issue they have mentioned. Typically this manifests itself when the client brings up an issue, that while important to them, is considered trivial by the developer.

Can you believe this guy!? I built out this whiz-bang feature and he’s just complaining about the font being too small!

The key part of this interaction is that you’ve now got a little insight into what makes this particular client tick. When you demo the next few features and he is unimpressed by the functionality, but comments on the spacing of form elements you should skip the joke and make a mental note for the future.

Once you can tell a joke about your client, before the interaction, and you have an “I told ya so!” moment with yourself or your pair afterwards, you know you’ve made it to the next level of understanding that client. Now, the next time you deploy a feature, discuss requirements or ask a question you can preempt the inevitable by accounting for those quirks and personal preferences of your client.

Instead of making a joke, make them happy, then you can both smile. ;)

Authlogic Account Activation Tutorial

I found a great tutorial explaining how to setup user activation with authlogic, but it was a little hard to read so I’ve dumped it all into one easier to read file.

Here’s the original: http://github.com/matthooks/authlogic-activation-tutorial/tree/master
Here’s my fork on github: http://github.com/clayton/authlogic-activation-tutorial/tree/master

Below is the formatted guide that is on github.

Remember, all credit goes to Matt Hooks and his original Authlogic Account Activation Tutorial

Introduction

This is an easier to read version of Matt Hooks’ Authlogic Activation Tutorial. The tutorial is divided into a number of steps and walks through the process of implementing user activation functionality into your pre-existing Rails app using Authlogic. If you are just starting out with Authlogic, be sure to checkout the Authlogic Example Tutorial.

Step 1

Let’s begin by adding an ‘active’ field with a default of false to the user model.

script/generate migration AddActiveToUsers active:boolean

1
2
3
4
5
6
7
8
9
10
  # new migration XXX_add_active_to_users.rb
  class AddActiveToUsers < ActiveRecord::Migration
    def self.up
      add_column :users, :active, :boolean, :default => false, :null => false
    end
 
    def self.down
      remove_column :users, :active
    end
  end

Step 2

Don’t forget to run the migraiton.

rake db:migrate

Authlogic automatically executes the following methods, if present, upon user action: active?, approved?, and confirmed?. Let’s create an “active?” method so we can hook into this magical goodness. And we should make sure that we protect the active attribute from mass-assignments by calling attr_accessible.

1
2
3
4
5
6
  # added to user.rb
  attr_accessible :login, :email, :password, :password_confirmation, :openid_identifier
 
  def active?
    active
  end

Step 3

Now try to log in. You should receive the error, “Your account is not active.” So far so good. Let’s make a controller to handle our activations:

script/generate controller activations new create

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
  # new file app/controllers/activations_controller.rb
  class ActivationsController < ApplicationController
    before_filter :require_no_user, :only => [:new, :create]
 
    def new
      @user = User.find_using_perishable_token(params[:activation_code], 1.week) || (raise Exception)
      raise Exception if @user.active?
    end
 
    def create
      @user = User.find(params[:id])
 
      raise Exception if @user.active?
 
      if @user.activate!
        @user.deliver_activation_confirmation!
        redirect_to account_url
      else
        render :action => :new
      end
    end
 
  end

Step 4

I raise exceptions in these actions to make sure that someone who is already active cannot re-activate their account and to deal with an invalid perishable token. I’ll leave it up to you how you want to handle these errors — you should probably provide some sort of “My Token is Expired!” action that will reset the token and resend the activation email if the user does not get around to activating right away.

Going down the list, let’s define the missing actions. First:

1
2
3
4
5
  # added to user.rb
  def activate!
    self.active = true
    save
  end

Step 5

Next, let’s make sure our user gets an e-mail with his activation code when he signs up. How are we getting our activation code? The same way we get our password reset code — through our perishable token:

Update: If you are experiencing the “Missing host to link to! Please provide :host parameter ” error see this post Missing host to link to! Please provide :host parameter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
  # added to app/models/user.rb
  def deliver_activation_instructions!
    reset_perishable_token!
    Notifier.deliver_activation_instructions(self)
  end
 
  def deliver_activation_confirmation!
    reset_perishable_token!
    Notifier.deliver_activation_confirmation(self)
  end
 
  # added to app/models/notifier.rb
  def activation_instructions(user)
    subject       "Activation Instructions"
    from          "Binary Logic Notifier <noreply@binarylogic.com>"
    recipients    user.email
    sent_on       Time.now
    body          :account_activation_url => register_url(user.perishable_token)
  end
 
  def activation_confirmation(user)
    subject       "Activation Complete"
    from          "Binary Logic Notifier <noreply@binarylogic.com>"
    recipients    user.email
    sent_on       Time.now
    body          :root_url => root_url
  end
 
  # added to config/routes.rb
  map.register '/register/:activation_code', :controller => 'activations', :action => 'new'
  map.activate '/activate/:id', :controller => 'activations', :action => 'create'
 
  <!-- new file app/views/notifier/activation_instructions.erb --> 
  Thank you for creating an account! Click the url below to activate your account!
 
  <%= @account_activation_url %>
 
  If the above URL does not work try copying and pasting it into your browser. If you continue to have problem, please feel free to contact us.
 
  <!-- new file app/views/notifier/activation_confirmation.erb -->
  Your account has been activated.
 
  <%= @root_url %>
 
  If the above URL does not work try copying and pasting it into your browser. If you continue to have problem, please feel free to contact us.

Step 6

Now let’s modify the user create action:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  # modified app/controllers/users_controller.rb
  def create
    @user = User.new(params[:user])
 
    # Saving without session maintenance to skip
    # auto-login which can't happen here because
    # the User has not yet been activated
    if @user.save_without_session_maintenance
      @user.deliver_activation_instructions!
      flash[:notice] = "Your account has been created. Please check your e-mail for your account activation instructions!"
      redirect_to root_url
    else
      render :action => :new
    end
  end

Step 7

As the comment says, we don’t need the Authlogic auto-login to take place so we save without maintaining the session. Now let’s define the ‘register’ view.

1
2
3
4
5
6
7
8
  <!-- new file app/views/activations/new.html.erb -->
 
  <h1>Activate your account</h1>
 
  <% form_for @user, :url => activate_path(@user.id), :html => { :method => :post } do |f| %>
  	<%= f.error_messages %>
  	<%= f.submit "Activate" %>
  <% end %>

Step 8

Let’s see if things are working…

… (processing) …

Looks like our user got activated!

But there’s a slight problem. Since we didn’t update the user’s password, we didn’t get a magical Authlogic auto-login! How rude.

At this point it’s perfectly fine to let the user log themselves in. And you can certainly simplify the activation down to one action so the user doesn’t have to click another button. But, I like Authlogic’s session maintenance. I also like short signup forms. So let’s kill two birds with one stone.

Let’s set up the user creation form to only ask for a user’s login/email. Then, let’s ask the user to set their password/openid upon activation, which will log them in automatically.

First, let’s change our acts_as_authentic call to only check for password length on update if the user has no credentials set.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
  # modified user.rb
  # For authlogic 2.0+
  acts_as_authentic do |c|
    c.validates_length_of_password_field_options = {:on => :update, :minimum => 4, :if => :has_no_credentials?}
    c.validates_length_of_password_confirmation_field_options = {:on => :update, :minimum => 4, :if => :has_no_credentials?}
  end
 
  # Pre-authlogic 2.0
  # acts_as_authentic :login_field_validation_options => { :if => :openid_identifier_blank? },
  #                   :password_field_validation_options => { :if => :openid_identifier_blank? },
  #                   :password_field_validates_length_of_options => { :on => :update, :if => :has_no_credentials? }
 
  # ...
  # we need to make sure that either a password or openid gets set
  # when the user activates his account
  def has_no_credentials?
    self.crypted_password.blank? && self.openid_identifier.blank?
  end
 
  # ...
  # now let's define a couple of methods in the user model. The first
  # will take care of setting any data that you want to happen at signup
  # (aka before activation)
  def signup!(params)
    self.login = params[:user][:login]
    self.email = params[:user][:email]
    save_without_session_maintenance
  end
 
  # the second will take care of setting any data that you want to happen
  # at activation. at the very least this will be setting active to true
  # and setting a pass, openid, or both.
  def activate!(params)
    self.active = true
    self.password = params[:user][:password]
    self.password_confirmation = params[:user][:password_confirmation]
    self.openid_identifier = params[:user][:openid_identifier]
    save
  end
 
  # modified activations_controller.rb
  def create
    @user = User.find(params[:id])
 
    raise Exception if @user.active?
 
    if @user.activate!(params)
      @user.deliver_activation_confirmation!
      flash[:notice] = "Your account has been activated."
      redirect_to account_url
    else
      render :action => :new
    end
  end
 
  # modified users_controller.rb
  def create
    @user = User.new
 
    if @user.signup!(params)
      @user.deliver_activation_instructions!
      flash[:notice] = "Your account has been created. Please check your e-mail for your account activation instructions!"
      redirect_to root_url
    else
      render :action => :new
    end
  end

Step 10

Now we need to update our views to reflect the new signup process.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  <!-- modified app/views/activations/new.html.erb -->
  <h1>Activate your account</h1>
 
  <% form_for @user, :url => activate_path(@user.id), :html => { :method => :post } do |form| %>
  	<%= form.error_messages %>
  	<%= render :partial => "form", :locals => { :form => form }%>
  	<%= form.submit "Activate" %>
  <% end %>
 
  <!-- new file app/views/activations/_form.html.erb -->
  <%= form.label :password, "Set your password" %><br />
  <%= form.password_field :password %><br />
  <br />
  <%= form.label :password_confirmation %><br />
  <%= form.password_field :password_confirmation %><br />
  <br />
  <%= form.label :openid_identifier, "Or use OpenID instead of your email / password" %><br />
  <%= form.text_field :openid_identifier %><br />

The End

And that’s it! Let me know if you have any suggestions for improvement.

Using Ruby’s true in Cucumber Multiline Tables

Last week my pair and I ran into a problem with a failing Cucumber scenario. We were using one of the more awesome features of Cucumber, multiline tables, when we got the unexpected failure. We realized that what we were doing was probably a common pattern and would certain trip other’s up.

Scenario and Step

If you’re not familiar with multiline tables in cucumber, take a look at the wiki entry. We were specifying a list of attributes on one of our models that we wanted to have access to in our step. First, here is an example scenario outline.

1
2
3
4
5
6
7
Scenario: Finding a specific dog
  Given an existing microchipped dog named "George"
  When I search for a dog with the following attributes:
    | name   | microchipped |
    | George | true         |
  Then I find "1" dog
  And that dog should be named "George"

Our step was taking the multiline table attributes and using them in an ActiveRecord find, like this.

1
2
3
4
5
/^I search for a dog with the following attributes:$/ do |table|
  table.hashes.each do |attributes|
    @dog = Dog.find(:first, :conditions => attributes)
  end
end

In our example we are looking for two attributes a string called “name” and a boolean called “microchipped”. However, this find will always fail, even if your ‘Given an existing microchipped dog named “George”‘ step explicitly sets up the correct object in the beginning of the test.

The Fix

We found that if we changed our scenario to look like this, everything worked fine.

1
2
3
4
5
6
7
Scenario: Finding a specific dog
  Given an existing microchipped dog named "George"
  When I search for a dog with the following attributes:
    | name   | microchipped |
    | George | 1            |
  Then I find "1" dog
  And that dog should be named "George"

See the change? We changed “true” for microchipped to “1″. Typically when you’re working with rails you can pass true in the conditions of an ActiveRecord find and Rails will convert that to the correct SQL string for you. However, because cucumber passes each argument in the multiline table rows as strings, Rails never has the chance to covert that true for you.

In the first, failing, example you end up with something like this for your SQL:

SELECT * FROM dogs WHERE name = "George" AND microchipped = "true" LIMIT 1;

In the second, passing, example you end up with something like this:

SELECT * FROM dogs WHERE name = "George" AND microchipped = "1" LIMIT 1;

Test Driven Development Talk with ASUSoDA

Tonight I had the privilege of giving a presentation on Test Driven Development to the ASUSoDA group at ASU. I focused on the higher level aspects of testing and the specific benefits and pitfalls of TDD. After my planned presentation I was also able to demo my Intern Management App which was fun given the lighthearted nature of the examples.

Here are the slides from my presentation: 852k PDF

How To: Setup RSpec, Cucumber, Webrat, RCov and Autotest on Leopard

RSpec, Cucumber, Webrat, RCov and Autotest are a powerful combination of tools for testing your Rails app. Unfortunately getting them to all work nicely together can be a bit of challenge. I recently configured a development environment from scratch on OS X 10.5 Leopard and kept track of all of the little details.

Prerequisites

I’m assuming you’ve got the following installed:

  • ruby
  • ruby gems 1.3.1
  • Apple development tools
  • git
  • rails >= 2.3.2
  • You’ve added github to your gem sources (gem sources -a http://gems.github.com)

RSpec & RSpec-Rails

First let’s grab the rspec1 and rspec-rails2 gems.

sudo gem install rspec
sudo gem install rspec-rails

Cucumber

Next we’ll install the cucumber3 gem

sudo gem install cucumber

Webrat

Webrat4 is used by cucumber to simulate a browser for your integration tests. Webrat will also install nokogiri5.

sudo gem install webrat

RCov

I thought RCov6 would get installed with RSpec, but it wasn’t for me. You might not need to do this, but just to make sure…

sudo gem install rcov

Autotest

Autotest7 comes from ZenTest8 and allows you to have a kick ass workflow where you are constantly running relevant tests and less-constantly automatically running your entire test suite.

sudo gem install ZenTest

Optionally, Thoughtbot’s Factory Girl

Factory girl9 is a really helpful fixture replacement (and more) gem to use in conjunction with cucumber, checkout their much better explanation

sudo gem install thoughtbot-factory_girl --source http://gems.github.com

Optionally, Carlos Brando’s Autotest Notification

While autotest normally runs in a terminal window, it can be setup to hook into applications like growl or snarl. The Autotest Notification9 gem helps make this setup a lot easier.

You will need growl installed and configured for this step the installation instructions on this gems github page are very easy to follow.

sudo gem install carlosbrando-autotest-notification --source=http://gems.github.com

Next you need to turn autotest notifications “on”

an-install

A Sample Rails App

Let’s create a sample rails app for the rest of this guide.

rails sample-app

Configuring Environment Variables

Autotest relies on some environment variables to run all of your features and specs correctly. If autotest “hangs” after you try to run it, or it just never seems to be watching your specs or features, this will most likely solve your problem.

Open the test.rb environment definition file in sample-app/config/environments/test.rb and add the following.

1
2
ENV['AUTOFEATURE'] = "true"
ENV['RSPEC'] = "true"

These lines will test autotest to run, and look for changes to, your specs (rather than test unit tests) and your cucumber features.

Update

If you don’t want to add these environment variables to every rails project you’ve got on your machine, you can also choose to set them as environment variables in your .bash_profile or .bashrc (or whatever shell you’re using) files.

export AUTOFEATURE=true
export RSPEC=true

Unpacking Gems

Next let’s freeze (unpack) some gems that we’ll be using in our app. I’ve run into problems trying to use the system gems with cucumber, rspec and webrat, especially when I have multiple versions of any of them installed. Unpacking them into my rails app solves this problem for me.

mkdir sample-app/vendor/gems
cd sample-app/vendor/gems
gem unpack rails
gem unpack rspec
gem unpack rspec-rails
gem unpack cucumber

Because webrat (and nokogiri) are native gems, that is, they are built locally on your machine based on its architecture, we won’t unpack those.

config.gem support
The current accepted practice, when using rails 2.3, and as suggested by the rspec guy(s) is to use rails’ config.gem functionality.

Open sample-app/config/environments/test.rb and add the following lines:

config.gem "rspec", :lib => false, :version => ">= 1.2.0" 
config.gem "rspec-rails", :lib => false, :version => ">= 1.2.0" 
config.gem "cucumber", :lib => false, :version => ">= 0.2.3"
config.gem "thoughtbot-factory_girl", :lib    => "factory_girl", :source => "http://gems.github.com"
config.gem "webrat", :lib => false, :version => ">= 0.4.3"
config.gem "nokogiri", :lib => false, :version => ">= 1.2.3"

Your version numbers may be different, but these are all current at the time of writing.

Boot Strapping RSpec and Cucumber

Before you can get very far with rspec or cucumber you need to run the bootstrapping scripts to give yourself the default files and directories.

# From inside your rails app sample-app/
script/generate rspec
script/generate cucumber

Factories
Depending on where you’re going to use your factories the most, you might want to save your file in either spec/ or features/. I chose the latter. Only complete this step if you plan to use the FactoryGirl gem.

touch sample-app/features/factories.rb

Getting Accurate RCov Data

By default RCov is setup to only use your specs when calculating code coverage. If you’re using Cucumber and RSpec, you’ll obviously want to include both types of tests to calculate your project’s true code coverage.

I picked up this rcov rake task from my co-worker Jay McGavren it does all of the heavy lifting for you, we’ll just need to make a couple of changes.

Drop this file into sample-app/lib/tasks/rcov.rake and use it by calling rake rcov:all from your terminal.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
require 'cucumber/rake/task' #I have to add this
require 'spec/rake/spectask'
 
namespace :rcov do
  Cucumber::Rake::Task.new(:cucumber) do |t|    
    t.rcov = true
    t.rcov_opts = %w{--rails --exclude osx/objc,gems/,spec/,features/ --aggregate coverage.data}
    t.rcov_opts << %[-o "coverage"]
  end
 
  Spec::Rake::SpecTask.new(:rspec) do |t|
    t.spec_opts = ['--options', ""#{RAILS_ROOT}/spec/spec.opts""]
    t.spec_files = FileList['spec/**/*_spec.rb']
    t.rcov = true
    t.rcov_opts = lambda do
      IO.readlines("#{RAILS_ROOT}/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
    end
  end
 
  desc "Run both specs and features to generate aggregated coverage"
  task :all do |t|
    rm "coverage.data" if File.exist?("coverage.data")
    Rake::Task["rcov:cucumber"].invoke
    Rake::Task["rcov:rspec"].invoke
  end
end

The important part here is on line 7, we want rcov to exclude our features directory. We obviously don’t need or want rcov telling us that our feature files are not “covered”. To solve this problem we’ve simply excluded the features directory from rcov’s processing.

We also need to slightly modify sample-app/spec/rcov.opts to get the full rspec + cucumber coverage data.

Your rcov.opts should look like this:

--exclude "spec/*,gems/*,features/*" 
--rails
--aggregate "coverage.data"

We again want to ignore our cucumber features and we also want to tell rcov to aggregate data in a file called coverage.data. This is used in the above rake task.

Write Some Specs and Features!

Act like you know what you’re doing and write some models, controllers whatever. Add some specs and features too.

Autotest Workflow

Open a terminal and make your way to your sample rails app and fire up autotest. You might see something like the following, depending on how many specs and features you’ve got.

$> autotest
loading autotest/cucumber_rails_rspec
opts 
...
 
Finished in 0.06276 seconds
 
3 examples, 0 failures
================================================================================
 
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby /Library/Ruby/Gems/1.8/gems/cucumber-0.2.3/bin/cucumber --format progress --format rerun --out /var/folders/Aq/Aqp06i3dFnqse+tQgQA+1++++TI/-Tmp-/autotest-cucumber.75956.0 features
.................
 
4 scenarios
17 passed steps
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby /Library/Ruby/Gems/1.8/gems/rspec-1.2.2/bin/spec --autospec spec/models/intern_spec.rb -O spec/spec.opts 
...
 
Finished in 0.062995 seconds
 
3 examples, 0 failures
================================================================================
 
/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby /Library/Ruby/Gems/1.8/gems/cucumber-0.2.3/bin/cucumber --format progress --format rerun --out /var/folders/Aq/Aqp06i3dFnqse+tQgQA+1++++TI/-Tmp-/autotest-cucumber.75956.1 features
.................
 
4 scenarios
17 passed steps

The REALLY important stuff

  1. make sure you’ve got “ENV['AUTOFEATURE'] = true” in your test.rb otherwise autotest won’t run your features automatically
  2. make sure you’ve got “ENV['RSPEC'] = true” in your bash profile or else autotest won’t run your specs automatically
  3. make sure you’ve got “–aggregate = ‘coverage.data’” in your spec/rcov.opts file if you’re going to use the above rake task and hope to get combined rcov coverage data between rspec and cucumber
  4. make sure you’re excluding the features directory from rcov where required or else you’ll end up with misleading rcov data.

Gem Versions

Here’s a list of the current gems and their versions that I used in preparing this guide.

*** LOCAL GEMS ***
 
actionmailer (2.3.2, 1.3.6, 1.3.3)
actionpack (2.3.2, 1.13.6, 1.13.3)
actionwebservice (1.2.6, 1.2.3)
activerecord (2.3.2, 1.15.6, 1.15.3)
activeresource (2.3.2)
activesupport (2.3.2, 1.4.4, 1.4.2)
acts_as_ferret (0.4.1)
addressable (2.0.2)
builder (2.1.2)
capistrano (2.0.0)
carlosbrando-autotest-notification (1.9.1)
cgi_multipart_eof_fix (2.5.0, 2.2)
cucumber (0.2.3)
daemons (1.0.9, 1.0.7)
data_objects (0.9.11)
diff-lcs (1.1.2)
dnssd (0.6.0)
extlib (0.9.11)
fastthread (1.0.1, 1.0)
fcgi (0.8.7)
ferret (0.11.4)
gem_plugin (0.2.3, 0.2.2)
highline (1.2.9)
hpricot (0.6)
libxml-ruby (0.3.8.4)
mongrel (1.1.4, 1.0.1)
mysql (2.7)
needle (1.3.0)
net-sftp (1.1.0)
net-ssh (1.1.2)
nokogiri (1.2.3)
polyglot (0.2.5)
rack (0.9.1)
rails (2.3.2, 1.2.6, 1.2.3)
rake (0.8.4, 0.7.3)
rcov (0.8.1.2.0)
RedCloth (3.0.4)
rspec (1.2.2)
rspec-rails (1.2.2)
ruby-openid (1.1.4)
ruby-yadis (0.3.4)
rubynode (0.1.3)
sources (0.0.1)
sqlite3-ruby (1.2.1)
term-ansicolor (1.0.3)
termios (0.9.4)
textmate (0.9.2)
thor (0.9.9)
thoughtbot-factory_girl (1.2.0)
treetop (1.2.5)
webrat (0.4.3)
ZenTest (4.0.0)

El Fin

Hopefully this guide was useful or had that one little step that you needed to get everything working. I’m sure this will all be out of date in the coming weeks, but I’ll try to keep it as up-to-date as possible. If you see any errors, or can better explain some of the missing pieces, please post a comment. Thanks!

1 http://github.com/dchelimsky/rspec/tree/master

2 http://github.com/dchelimsky/rspec-rails/tree/master

3 http://github.com/aslakhellesoy/cucumber/tree/master

4 http://wiki.github.com/brynary/webrat

5 http://github.com/tenderlove/nokogiri/tree/master

6 http://rubyforge.org/projects/rcov/

7 http://www.zenspider.com/ZSS/Products/ZenTest/#rsn

8 http://www.zenspider.com/ZSS/Products/ZenTest/

9 http://github.com/thoughtbot/factory_girl/tree/master

10 http://github.com/carlosbrando/autotest-notification/tree/master

Updates
2009-12-08 – Removed “sudo” when describing how to unpack gems (h/t xdotcommer)

The FAIL Monster Loves Excuses

Do you remember watching the FAIL Monster on Sesame Street? Never heard of the FAIL Monster? Weird, I’m pretty sure he was Cookie Monster’s cousin or something. He would pop-up and sing a little song about your failures and then at the end he would go crazy and NOM NOM NOM all of your excuses. The really strange thing is that when I grew up, I still saw the FAIL Monster, except he was all over, eating up everyone’s excuses, not just mine. When was the last time the FAIL Monster paid you a visit?

The FAIL Monster Is Your Worst Best Friend

When you first meet him you’re trying to figure out what this asshole is doing hanging out right after you really screwed up. You realize that he’s no good, but as time goes on, you start enjoying his company. He loves showing up because he knows there will be excuses aplenty.

Here are some situations when you might see him:

  • You’ve messed up and can’t take responsibility
  • The project is off track and it’s not at all your fault
  • If only < outside agent > would have completed < task > on time
  • You start using Fat Bastard’s circular logic to explain away your problems
  • “I don’t have < resource > to do < what is right >
  • You don’t write tests

Put the FAIL Monster on a Diet

Here is a quick guide to help you trim down your personal FAIL Monster:

  1. Quit making so many goddamned excuses!

Stop making excuses for your lack of understanding, your irresponsibility, your lack of prospects and your shit attitude. Take the time to push yourself, learn a new skill, read a book, meet people, take a leadership role, achieve greatness and succeed.

You can make excuses for everything, the only thing they’re good for is feeding your own FAILURE.

Why I Fell in Love With Agile

I’ve been working at Integrum for nearly 5 months now. When I started, agile was a concept that I had read about, but never experienced in practice. I took bits and pieces of advice from books like Extreme Programming Explained and Practices of an Agile Developer but I had never really lived day-in day-out as a developer in an agile workplace. However, now that I’ve experienced the project boards, burn down charts, velocities and story workshops, I cannot imagine going back to the slam-your-head-against-the-wall waterfall approach from which I came.

What I Love

Project Boards – Nearly real-time visual feedback on the status of your project. Everyone on the team can assess your project at a glance and you can avoid long status meetings with middle management. Greatly increases accountability.

Stand-up Meetings – Quick, informative useful meetings that don’t waste people’s time. Social group pressure keeps people who like the sound of their own voice from talking for 30 minutes.

Daily Client Interaction – No need for ego-maniac project managers. Developers discuss issues with clients each day so that they can deliver the best software with the most value as quickly as possible. Challenges or roadblocks can be brought up almost immediately helping to prevent wasted time on either side of the equation.

User Stories – “Replacements for a conversation” as I always hear around the office. Replaces the need to confusing spec documents and misguided feature requests. These provide a way to ensure that neither the client’s money or developer’s time are being wasted.

Acceptance Criteria – It’s not easy getting quality acceptance criteria from your clients, but man is it worth it. Helps to make sure that what’s “done is done” and there are no disputes towards the end of the project about what is or is not completed.

Short Iterations – Allows the developer to focus on a set of the most important tasks for a short period of time. The client receives frequent demonstrations of the completed work and changes in course can be made very quickly. What was important 4 months ago during the original planning session might not be important this week.

Waterfall Development: DO NOT WANT!

Looking back on my last job where waterfall development was the norm, I can’t imagine how I got anything done. When I really think about it, I wonder how much software I wrote that was “right” the first time around.

Just as a quick list, here are some of the things I’ve grown to hate:

  • Project manager dominated client interaction
  • Long useless meetings
  • Finger pointing and blaming and lack of accountability
  • Waiting a few weeks to find out that your implementation is incorrect
  • No way to tell what was actually “done”
  • Wasteful spec documents

This Won’t Work For My Business

There are probably a lot of development shops that are humming along using long standing processes based on a waterfall model. A lot of people probably think that they don’t need to change because “if it ain’t broke, don’t fix it”. If you’re not doing agile your shit is broke!

A lot of managers probably think that this system won’t work with their employees. I can see this being the case if any of the following apply:

  • You don’t trust your employees
  • Your employees are morons (or you think they are)
  • You think you need to operate like the status quo
  • You can’t give up your control on the process
  • Your clients suck

Agile Works at Integrum

Derek Neighbors has taken the time to produce a couple of videos outlining how agile is put into action at Integrum:

Whatever the reason, agile works, like actually works, at Integrum. Is it the culture? The people? The atmosphere? The expectations? The clients? I can’t quite put my finger on it, but I can guarantee it’s not coincidental.

RSpec Shared Example before(:each) Gotcha

Shared example groups are a great feature of Rspec that help you simplify your tests and keep your code DRY. You setup shared example groups almost exactly like you would a regular set of specs, but these similarities can be slightly misleading.

Below we have an example model, spec and shared example group. Our Dog model has its own set of functionality, but as a mammal it should still have some aspects of being a mammal. We’ve got some specs in a shared example group that we use for testing all of our mammal models to make sure things don’t get too out of whack in the universe.

Our Example Model

1
2
3
4
5
6
7
8
9
10
11
12
13
class Dog
 
  attr_accessor :name, :mammal
 
  def initialize
    self.mammal = true
  end
 
  def greet
    "Hi, I'm #{self.name}, woof woof!"
  end
 
end

Our Example Spec

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
describe Dog do
 
  before(:each) do
    @animal = Dog.new
    @animal.name = "Bruno"
  end
 
  it_should_behave_like "a mammal"
 
  describe "Greet" do
    it "should respond with its name and a greeting" do
      @animal.greet.should == "Hi, I'm Bruno, woof woof!"
    end
  end
 
end

Our Shared Spec

1
2
3
4
5
describe "a mammal", :shared => true do
  it "should really be a mammal" do
    @animal.mammal.should be_true
  end
end

A Typical before(:each)

Typically, when you’ve got a describe block, you might use before(:each) to setup some scenario that is used for each spec in that describe block, pretty normal RSpec stuff. We’re using it above in our example spec to create a new Dog object and set that dog’s name.

Using before(:each) in a shared spec

What if you wanted to use a before(:each) in your shared spec? Expanding on our example above we can do something like this.

1
2
3
4
5
6
7
8
describe "a mammal", :shared => true do
  before(:each) do
    @animal.stub!(:has_body_hair?).and_return(true)
  end
  it "should really be a mammal" do
    @animal.mammal.should be_true
  end
end

Based on typical RSpec behavior, one would think that the stubbing of the has_by_hair? method on the instance of an animal, would only apply to the specs inside of the describe block of the shared example group. However, by specifying in the Dog spec that a dog “should behave like” a mammal, and thus using the shared spec, that stub will apply to all subsequent “it should” blocks in your model spec.

What if, for example, we had the following in our Dog spec.

  describe "Mutate into Lizard Dog" do
   # dog.mutate will remove body hair and make the dog cold blooded
    it "should mutate into a new species" do
      @animal.mutate
      @animal.has_body_hair?.should be_false
    end
  end

If we include this in our Dog spec, below the inclusion of the shared example spec, our test will fail. We’ve already stubbed out the has_body_hair? method as part of our shared example group, when we call it down here in this completely separate describe block, RSpec is just using the stub we setup previously.

It might be a design problem if…

Now while I’m considering this a gotcha, it may be that this is expected behavior, I couldn’t find anything specifically when researching this “bug” originally. It is also possible that stubbing behavior in shared example groups is frowned upon, and I’m just “doing it wrong”.

Ultimately, I tried using patterns that made sense to me and seemed to be in line with how RSpec works in general. A stubbed method inside the before(:each) of a describe block is usually only applicable to the specs and nested describes contained within. When I realized that this is not the case with shared example groups, it seemed like a gotcha.