Using Closures to Avoid Leaking State

I’ve often found myself sandboxing a state change like so:

spec_helper.rb
shared_context "with isolated current dir", :isolated_dir => true do
  before do
    @dir = Dir.pwd
    Dir.chdir(Dir.mktmpdir)
  end

  after do
    Dir.chdir(@dir)
  end
end
my_spec.rb
describe MyClass, :isolated_dir => true do
  # some examples that operate on the current dir
end

This works fine, but the host example group may also have a @dir instance variable, which would lead to surprising bugs. In the past, I’ve dealt with this possibility by naming the instance variable with something the host group is unlikely to use (such as @__dir), but that’s just putting a band aid on the underlying problem: our shared_context declaration is leaking state.

I realized recently that I can use the fact that ruby blocks are closures to avoid the problem entirely:

spec_helper.rb
shared_context "with isolated current dir", :isolated_dir => true do
  dir = nil

  before do
    dir = Dir.pwd
    Dir.chdir(Dir.mktmpdir)
  end

  after do
    Dir.chdir(dir)
  end
end

Ruby blocks retain bindings to the local variables present at their point of declaration, so we can create a local dir variable in the parent context, and both the before and after blocks will have access to it.

I think this is a superior way to write this code. It’s easier to reason about (there’s no way the host example group can accidentally modify the dir variable) and state held by the host example group (such as @dir) can’t possibly be affected by including this shared context.

blog comments powered by Disqus

About Me

Husband and father, musician, software engineer at SEOmoz, open source developer specializing in Ruby and Rails, world traveler and Christian.