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.

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)

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.

Flog as a Surrogate Pair

I’m a fan of Flog. I like using it every now and then to make sure that I’m not getting too complex, trying to be clever, or setting myself up for testing failure. Typically these are things I look to my MILF slaying pair for guidance, but sometimes Flog can be just as good.

As a relative newcomer to Ruby, I’m still in the process of learning to identify code smells, poor design and needless complexity. I’m sold on the “readable code is better” idea so I try to keep that in mind as I’m implementing some piece of functionality. Over at Integrum, where I work, we pair on everything each day, save for the occasional sick day. Today I was working alone and was reviewing some code that my pair and I had wrote about a week earlier. It was complex, and I feel that we made a compromise with the idea that the “ugly” working code was good enough for what we needed at the time, but it was ugly enough that I remembered to revisit it.

Flog Builds Confidence

When I really started looking at the code my intuition told me that it needed to be re-factored. I knew that it could be improved, but I suppose I’ve been in the pair mindset so heavily since I’ve been here that I did not immediately jump in and fix things. However, when I ran Flog and saw the comparatively high scores for two complex methods I had been investigating, I had that supportive “yea I agree” feeling that I usually get from pairing. Flog helped push me over the threshold, from “maybe I should change this” to “this should be changed”.

This isn’t to say that I wouldn’t have changed it without Flog, but it gave me that extra confidence in knowing that I was on the right track. The direct benefit was that the code is now more readable and easier to test. Indirectly I’ve added another point to my mental Ruby experience score and will be more inclined to follow my intuition in the future.

Installing LaTeX packages on Ubuntu for RTeX Gem

Looking for a really confusing and cryptic layout language for generating PDFs through your rails app? If you enjoy nonsensical markup languages and have the urge to read poorly written how-to documents from 1995, say hello to LaTeX! Unfortunately LaTeX can be slightly difficult to configure, but with a little help from apt-get, you can make your life easier.

Packages Needed on Ubuntu

I’ve tried the following on Ubuntu Intrepid Ibex 8.10 but I would imagine that it works on other recent versions as well.

First let’s search for available texlive related packages

$> apt-cache search ^texlive
texlive - TeX Live: A decent selection of the TeX Live packages
texlive-base - TeX Live: Essential programs and files
texlive-base-bin - TeX Live: Essential binaries
texlive-base-bin-doc - TeX Live: Documentation files for texlive-base-bin
texlive-bibtex-extra - TeX Live: Extra BibTeX styles
texlive-common - TeX Live: Base component
... snip...
texlive-plain-extra - TeX Live: Plain TeX supplementary packages
texlive-science - TeX Live: Typesetting for natural and computer sciences

You’ll notice that there are a lot of packages available. Now, you could go through and probably piece together the exact set of packages you need, or you could just do this…

$> apt-get install texlive-full

This will install everything, docs, languages, fonts, advanced math typesetting, you’ll even be able to generate PDFs in Mongolian. All of the packages together are about 1.1GB, and it takes a long time to download/build, but avoiding the apt-get hassle is worth it.

RTex Gem for Rails Support

If you’re planning on using a LaTeX template to generate a PDF in your rails app, you’ll need to make use of the RTeX gem. Once you’ve got the RTeX gem installed, create the view for your ActiveRecord model.

In this example application, we have an invoice model. We want an employee to be able to view the LaTeX generated invoice by accessing something like http://localhost:3000/invoices/1.pdf

Our controller is pretty straightforward.

1
2
3
4
5
class InvoiceController < ActionController::Base
  def show
    @invoice = Invoice.find(params[:id])
  end
end

Next we need to create a view that will store the LaTeX markup that creates our PDF.

$> touch app/views/invoices/show.pdf.rtex

Once you’ve pulled your hair out writing the LaTeX markup required to get the invoice PDF looking the way you want, save the LaTeX to the show file you’ve just created in app/views/invoices/show.pdf.rtex

Drink a Beer

You should now be able to access your pretty LaTeX formatted PDF invoice @ http://localhost:3000/invoices/1.pdf

Using RSpec to Test system calls with %x and backticks (grave accents)

I’ve been working on a project that requires a call to the shell from inside a controller. I knew that I would need the output of the shell command, in this case a rake task, so that I could display the result to the user. However, when I went to implement the spec, I wasn’t sure how to setup the expectation.

Our Controller

1
2
3
4
5
6
7
8
InfoController < ApplicationController
 
  def index
    flash[:notice] = `cat /home/clayton/info` # sets the notice to "clayton@lengelzigich.com"
    redirect to people_url
  end
 
end

Our Spec

1
2
3
4
5
6
7
8
describe InfoController do
  describe "index" do
    it "should set the contents of the flash notice to clayton's contact info" do
      controller.should_receive(:'`').with("cat /").and_return("clayton@lengelzigich.com")
      get :index
    end
  end
end

The important part of the spec is line 43. We tell the controller to expect a call to '`' with our shell command and return the contents of the file. The '`':, backtick1, is a method in the Kernel class2. It is also possible to use @%x@ to run commands in the shell from ruby, the two are the same.

`cat /etc/motd`
# is the same as
%x[cat /etc/motd]

If you are using %x@, and need a way to write a spec, consider changing @%x to @“@ and using the above approach.

1 Some people call backticks “grave accents”, some people are dumb.

2 The @`@ method that comes from Kernel isn’t actually called on the Kernel class, it’s mixed into your Object at run time. Or so I’ve read.

3 Some credit for this discovery goes to my straight up ballin’ pair.

Why I switched to nginx

When I originally setup my VPS I didn’t think twice about using Apache for my web serving duties. Setting up the LAMP environment was very easy and everything that I needed to host worked perfectly with this setup. However, when I decided to create my blog using Rails, I knew I would have to modify my setup. Originally I had planned to use mod_proxy_balancer and mongrel, but then Phusion’s Passenger came out and that was easy enough to get running. However, I was itching to try something new, improve the speed of my sites and lower the amount of resources that were being used on my Slice. Enter Nginx.

Here’s why I decided to give Nginx a shot:

  • Nginx is lightweight and has a small memory footprint
  • Nginx is super fast
  • Nginx can be easily configured to send rails requests to mongrel, thin and others
  • Nginx has easy to read configuration logs and rewriting rules
  • I get to learn something new!

Note: All ApacheBench test results displayed are the from the 4th consecutive test. This minimizes the negative effects of the slow startup of ruby processes with Phusion Passenger where the initial requests take a considerably long time.

Apache vs. Nginx Memory Usage

For this test I ran ApacheBench against the index page of a well trafficked PunBB forum that I host on my slice. I fired up top and found the following:

Apache

Mem: 262316k total, 207140k used, 55176k free, 4280k buffers

Nginx

Mem: 262316k total, 146956k used, 115360k free, 3844k buffers

That’s about 40MB less with Nginx. That makes a big difference when you’re only starting out with 256MB.

Apache vs. Nginx AB results

Apache

This is ApacheBench, Version 2.0.41-dev &lt;$Revision: 1.141 $&gt; apache-2.0
		Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
		Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/
 
		Benchmarking www.example.com (be patient).....done
 
 
		Server Software:        Apache
		Server Hostname:        www.example.com
		Server Port:            80
 
		Document Path:          /
		Document Length:        8857 bytes
 
		Concurrency Level:      10
		Time taken for tests:   1.732965 seconds
		Complete requests:      100
		Failed requests:        0
		Write errors:           0
		Total transferred:      928532 bytes
		HTML transferred:       897838 bytes
		Requests per second:    57.70 [#/sec] (mean)
		Time per request:       173.296 [ms] (mean)
		Time per request:       17.330 [ms] (mean, across all concurrent requests)
		Transfer rate:          522.80 [Kbytes/sec] received
 
		Connection Times (ms)
		              min  mean[+/-sd] median   max
		Connect:       22   26   4.3     26      40
		Processing:    70  136  41.5    133     268
		Waiting:       46  105  40.9    100     239
		Total:         93  163  42.4    160     294
 
		Percentage of the requests served within a certain time (ms)
		  50%    160
		  66%    174
		  75%    186
		  80%    197
		  90%    213
		  95%    250
		  98%    274
		  99%    294
		 100%    294 (longest request)

Nginx

	This is ApacheBench, Version 2.0.41-dev &lt;$Revision: 1.141 $&gt; apache-2.0
	Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
	Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/
 
	Benchmarking www.example.com (be patient).....done
 
 
	Server Software:        Nginx/0.6.32
	Server Hostname:        www.example.com
	Server Port:            8080
 
	Document Path:          /
	Document Length:        8858 bytes
 
	Concurrency Level:      10
	Time taken for tests:   1.107068 seconds
	Complete requests:      100
	Failed requests:        0
	Write errors:           0
	Total transferred:      929232 bytes
	HTML transferred:       897920 bytes
	Requests per second:    90.33 [#/sec] (mean)
	Time per request:       110.707 [ms] (mean)
	Time per request:       11.071 [ms] (mean, across all concurrent requests)
	Transfer rate:          819.28 [Kbytes/sec] received
 
	Connection Times (ms)
	              min  mean[+/-sd] median   max
	Connect:       22   25   4.3     25      42
	Processing:    62   80  29.4     75     345
	Waiting:       37   49  10.6     47      93
	Total:         85  106  29.5    100     368
 
	Percentage of the requests served within a certain time (ms)
	  50%    100
	  66%    105
	  75%    109
	  80%    112
	  90%    119
	  95%    146
	  98%    147
	  99%    368
	 100%    368 (longest request)

There are a few things that stand out to me from these tests:

  1. Requests Per Second: 90 vs 58, Nginx wins
  2. Mean Processing Time: 80 vs 136, Nginx wins
  3. Percentage of Requess served within 100ms: 50% vs. ??, Nginx Wins

Nginx and Thin vs. Apache and Phusion Passenger

So the PHP/MySQL stuff was all fine and good and that’s something that Apache and Nginx do very well, so no real surprises there. What about handling requests to a simple rails application like this blog?

For this test I again ran apache bench against the non-cached version of my root url. On this page there are some partials being rendered, active record database calls, images loading from Amazon’s S3 and some static files being served.

Apache 2.2 + Phusion Passenger 2.0.3

	This is ApacheBench, Version 2.0.41-dev &lt;$Revision: 1.141 $&gt; apache-2.0
	Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
	Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/
 
	Benchmarking www.lengelzigich.com (be patient).....done
 
 
	Server Software:        Apache
	Server Hostname:        www.lengelzigich.com
	Server Port:            80
 
	Document Path:          /
	Document Length:        14894 bytes
 
	Concurrency Level:      10
	Time taken for tests:   26.118296 seconds
	Complete requests:      100
	Failed requests:        0
	Write errors:           0
	Total transferred:      1544644 bytes
	HTML transferred:       1493235 bytes
	Requests per second:    3.83 [#/sec] (mean)
	Time per request:       2611.830 [ms] (mean)
	Time per request:       261.183 [ms] (mean, across all concurrent requests)
	Transfer rate:          57.74 [Kbytes/sec] received
 
	Connection Times (ms)
	              min  mean[+/-sd] median   max
	Connect:       22   24   1.5     25      27
	Processing:   769 2477 389.8   2524    3076
	Waiting:      744 2448 387.8   2498    3048
	Total:        792 2501 390.1   2547    3102
 
	Percentage of the requests served within a certain time (ms)
	  50%   2547
	  66%   2637
	  75%   2793
	  80%   2819
	  90%   2906
	  95%   2963
	  98%   3079
	  99%   3102
	 100%   3102 (longest request)

Nginx + Thin

I decided to use thin instead of mongrel because, well, I guess I just wanted to try something new. I heard that thin had recently hit version 1.0 and figured it was worth checking out.

This is ApacheBench, Version 2.0.41-dev &lt;$Revision: 1.141 $&gt; apache-2.0
Copyright (c) 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Copyright (c) 1998-2002 The Apache Software Foundation, http://www.apache.org/
 
Benchmarking www.lengelzigich.com (be patient).....done
 
 
Server Software:        Nginx/0.6.32
Server Hostname:        www.lengelzigich.com
Server Port:            80
 
Document Path:          /
Document Length:        14894 bytes
 
Concurrency Level:      10
Time taken for tests:   17.334487 seconds
Complete requests:      100
Failed requests:        0
Write errors:           0
Total transferred:      1534900 bytes
HTML transferred:       1489400 bytes
Requests per second:    5.77 [#/sec] (mean)
Time per request:       1733.449 [ms] (mean)
Time per request:       173.345 [ms] (mean, across all concurrent requests)
Transfer rate:          86.42 [Kbytes/sec] received
 
Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:       22   24   1.4     25      26
Processing:   526 1663 489.6   1544    2995
Waiting:      477 1613 491.6   1496    2949
Total:        549 1687 489.9   1567    3020
 
Percentage of the requests served within a certain time (ms)
  50%   1567
  66%   1882
  75%   1947
  80%   2034
  90%   2394
  95%   2648
  98%   3020
  99%   3020
 100%   3020 (longest request)

The results here are a little more impressive.

  1. Requests Per Second: 6 vs. 4, not great, but Nginx wins
  2. Mean Processing time: 1660 vs. 2448, Nginx wins
  3. Percentage of Requess served within 1560ms: 50% vs. ??, Nginx Wins

I don’t have the top memory output for these two tests handy, but with Apache free memory was hovering around 3MB. Nginx had free memory hovering around 12MB.

The key difference between Nginx and Apache is that with Apache there is a noticeable lag while Ruby processes are spun up by Passenger on the initial request, however, as I mentioned above, this is not reflected in these ApacheBench tests.

Plus, I Learned A Lot!

I think the best part of all of this was the overall learning experience. I broke out of my Apache shell and learned about a new web server, here are the highlights:

  • Learned how to install and configure Nginx, php-cgi, spawn-fcgi and thin on Ubuntu
  • Wrote new Capistrano deployment recipes to handle deploying to my new Nginx + thin setup
  • Created some new shell scripts to mimic common tasks from apache2ctl and a2ensite/a2dissite
  • Recreated my existing Apache virtual host setup with Nginx

If you’re currently running LAMP or using Apache and “mod rails”, give Nginx a shot. It’s easy to setup, doesn’t use too many resources, and best of all, it’s fast.

Who Needs The Show Action Anyway

We all know that Rails has seven standard REST actions, index, show, new, create, edit, update and delete. Usually there are only views associated with four of them, index, show, new and edit with new and edit usually being the same. This all makes perfect sense for the public facing part of the application, but what about the administration portion of your app? Do you really need that show action? Are you going to be displaying the information in the same way as the public facing side of things? I’m starting to think that skipping the show action in the administration portion of the application is not a bad idea.

Context Is Key

One thing that I’ve noticed with your average “show” action in the administration portion of the web app is that it ignores the context in which the administrator wants to see the data. Rather than displaying what the public user will see, the admin show action is usually just a basic display of the information from the record.

For example, say you’ve got a content page that you have the ability to edit through the administration area. When you’re done editing it, you want to check the formatting, colors, links and general look of the page. Unless you’re using the same CSS styles and layouts for the public facing portion of the site and the administration portion of the site, you’re not going to be able to get a good idea of what it’s really going to look like. Sure you could recreate the public facing styles and part of the layout in the administration area but that’s jumping through a lot of hoops and getting out of DRY land.

If the information that you’re displaying isn’t view/layout dependent (like a user record) then there is probably no harm in having a simple show view that looks different in the administration area and the public facing area. However, if the information ends up being styled and displayed differently between the two areas, it might be worth for going the show action in the administration area.

How Rails Can Help

You’ve decided that it’s important to always view the public facing version of a resources show action when you’re in the administration area, luckily Rails makes it pretty easy to implement this functionality.

1. Add a new member route to your existing resource

  map.resources :pages, :member => { :preview => :get }

2. Add the new preview action to your controller

1
2
3
4
5
6
7
8
9
  class PagesController < ApplicationController
 
    # ...
 
    def preview
      @page = Page.find(params[:id])
    end
 
    # ...

3. Tell your action which layout to use

1
2
3
4
5
6
7
8
9
10
  class PagesController < ApplicationController
 
    # ...
 
    def preview
      @page = Page.find(params[:id])
      render :action => "pages/show", :layout => "public"
    end
 
    # ...

4. Add your new “Preview” link somewhere in your Administration area

1
2
  # show.html.erb in app/views/admin/pages/show.html.erb
  <%= link_to("Preview", preview_page_url(@page), :target => "_blank") %>

This assumes that you’ve got the public facing show template in app/views/pages/show and a layout file named “public”.

Now you can edit your records in the administration area but view them in the context that makes the most sense. While I might not do this to something like a user record or order receipt, this is definitely a good idea for blog posts, content pages and other administrable content.

Blog Improvements and Updates

Tonight was the night of much needed updates and improvements to this here blog. It’s been a while since I spent a considerable amount of time doing anything major, but I did make some kinda-sorta-major updates, but those were mainly to the back end system. Hope everyone who’s visiting keeps enjoying it (or starts?)!

Thumbs Up!

Thumbs Up!

What’s New

  • Added comment display to post preview
  • Added post photo credit url field
  • Added post preview field
  • Added view all method to posts controller
  • Added view all link to post index
  • Added E-Mail link to sidebar

What’s Improved

  • Improved comment formatting display on post show
  • Improved photo display on post preview and show
  • Upgraded to Rails 2.1

What’s Changed

  • Changed popular post list display
  • Changed Photo re-size options

What’s Fixed

  • Fixed a problem with my paperclip plugin not being in my git repo correctly

In The Future

  • Add an AJAXy post-preview for the admin area
  • Add some styles so that the comment’s display better (damn reset.css)
  • Automatically post my git commit messages (_that_ should keep me honest)
  • Automatically tweet new posts via Twitter API (fun exercise)