I have a love-hate relationship with spring, Rails’ application pre-loader. One one hand, it speeds up the feedback loop when doing TDD. Faster running specs, promote running them more often, which promotes writing code in smaller increments, and so forth. On the other hand, it is dark magic: In its quest to be unobtrusive, it starts automatically, and barely reports it’s being used at all. Occasionally it looses track of which code it needs to reload, causing much confusion to the user, as the code executing is different than the version saved on disk.

For a while, I disabled its use all together, by setting the DISABLE_SPRING environment variable. I found it tolerable while working on smaller rails apps, but not on the giant rails monolith I use everyday:

# spec/example_spec.rb
require 'rails_helper'

RSpec.describe 'A spec' do
  it 'states the obvious' do
    expect(1).to eq(1)
  end
end

Let’s time with and without spring:

$ time bin/rspec spec/example_spec.rb
Running via Spring preloader in process 26118

Randomized with seed 15334
.

Finished in 0.05529 seconds (files took 3.86 seconds to load)
1 example, 0 failures

Randomized with seed 15334

bin/rspec spec/example_spec.rb  0.27s user 0.11s system 7% cpu 5.050 total

$ bin/spring stop
Spring stopped.

$ export DISABLE_SPRING=yes_please

$ time bin/rspec spec/example_spec.rb
Randomized with seed 42078
.

Finished in 0.09926 seconds (files took 9.99 seconds to load)
1 example, 0 failures

Randomized with seed 42078

bin/rspec spec/example_spec.rb  11.03s user 1.35s system 98% cpu 12.547 total

Running with spring takes 0.27 seconds. Running without takes 11.03. Can I have my cake and eat it too?

Git Hooks

I don’t have conclusive evidence, but I’ve noticed that code loading issues creep into spring when changing git branches. Git provides a mechanism to hook into it’s events and run an arbitrary script. Putting it all together, I created git hooks that stop spring:

#!/usr/bin/env bash
# Copy this script to .git/hooks/post-checkout
# Make it executable (chmod +x ..git/hooks/post-checkout)

# The hook is given three parameters: the ref of the previous HEAD,
# the ref of the new HEAD (which may or may not have changed),
# and a flag indicating whether the checkout was a branch checkout
# (changing branches, flag=1) or a file checkout
# (retrieving a file from the index, flag=0).
if [[ "$3" == "1" ]] && [[ "$1" != "$2" ]]; then
  # Stop spring, if we have the binstub fot it

  spring_command="bin/spring"

  [[ -x "$spring_command" ]] || exit

  echo "Git Hook: Stopping spring"
  exec $spring_command stop
fi

I’ve been using the above hook for weeks. I haven’t encountered a code loading issue yet.