Friday, February 11, 2011

Agile rspec with let()

Rspec 1.3 has the least-used, yet significant feature called let().

Here's how you set up Rspec 2, for those who have not caught on to let().

describe Friendship do
before :all do
@users = (1..5).collect { Factory(:user) }
end

after :all do
@users.each { |user| user.destroy! }
end

it 'should do something' do
@users.each do |user|
user.should be_valid
end

# Something interesting with @users
end
end

You should be able to replace everything you use @variables for with let(), like this:

describe Friendship do

let(:users) { (1..5).collect { Factory(:user) } }

before :all do
users
end

after :all do
users.each { |user| user.destroy! }
end

it 'should do something' do
@users.each do |user|
user.should be_valid
end

# Something interesting with @users
end
end
I prefer not to preload everything. The tradeoff is slower specs:

describe Friendship do
let(:users) { (1..5).collect { Factory(:user) } }

it 'should do something' do
users.each do |user|
user.should be_valid
end
# Something interesting with @users
end
end
let() cleans up a lot of your code. But where it really shines comes in combination with two things: (1) rspec 2 will preload everthing under spec/support, and (2) Rails 3's secret weapon, ActiveSupport::Concern. You can factor out your let() declarations and share it across your spec, like so:
# Put this in spec/support/application.rb

module SpecHelpers
module Application
extend ActiveSupport::Concern

included do
let(:users) { (1..5).collect { Factory(:user) } }
end
end
end

# spec/friendship_spec.rb
# Rspec 2 automatically loads everything in spec/support
require 'spec_helper'

describe Friendship do
include SpecHelpers::Application

it 'should do something' do
users.each do |user|
user.should be_valid
end
# Something interesting with @users
end
end

# spec/comment_spec.rb
require 'spec_helper'

describe Comment do
include SpecHelpers::Application

it 'should do something' do
users.each do |user|
user.should be_valid
end
# Something interesting with @users
end
end

Tuesday, January 18, 2011

Scoping and Closure in Ruby 1.8


The other day, I had to wrote code in this form:
class_eval do
[:foo, :bar].each do |a|
_cache = :"@#{a}_cache"
define_method a do
_cache
end
end
end
I had to ask myself: does this really do what I expect? I know the define_method closes over the _cache variable, but is it really isolated in each iteration? Am I really referencing a different _cache on each iteration? I tried this in the Rails console:
=> [:foo, :bar]
irb(main):009:0> foo
=> :@foo_cache
irb(main):010:0> bar
=> :@bar_cache
Should this have been surprising? I suppose not.