Kassis Netz / blog

Today I had (again) trouble with spork, the rails pre-loader.
Here’s how we could finally beat it and may prevent others from falling into
the same trap.

The problem

After being off of the project for some time I realized, that suddenly one of
the rspec tests was failing. The mysterious test output was something like

×

–

⬈

expected <Stories::StatusPost...> to be kind of Stories::Base

We added those tests because we use a factory, which is set up in a way the
rails sources also do it, i.e. we have a base.rb under stories and all
subtypes defined here as well.
And thus all Stories::* inherit from Stories::Base.

Additionally, when running all the specs via rake, everything shows up green,
so only when run via guard this particular test was failing.

Digging into the problem, why this is suddenly not the case anymore, I printed
out .superclass of the object in question, expecting it to be something else,
but it wasn’t. Printing story.class.superclass == Stories::Base revealed a
false and the object_ids of the classes themselves were different as well.
So the question was: what’s going on here?

First thing was to find out the commit that introduced the bug. Luckily, because
we had a test passing and failing immediately running guard, with the help of
git bisect the “bad” commit could be found very fast – but wait: all it does
is adding a new subclass exactly the way we had several existing already. WTF?

Ok - it’s only failing when using guard. Guard is set up to use spork to
gain speed and the note from my colleague Björn who raised
the question about some spork preloader code lead me to the old problem we had
at the very beginning of our project, which we thought we had solved already.
Here’s an excerpt of our spec_helper.rb

`ruby title=spec_helper.rb
Spork.each_run do
# reload all the models
Dir["#{Rails.root}/app/models/**/*.rb"].each do |model|
load model
end
# reload factories
FactoryGirl.reload
end

While this code helped us out of the trouble once, it unfortunately didn’t this
time.
Checking the responsible commit again and taking a look at the sidebar of files
I realized that there actually is a difference between this (straight forward)
addition and the former ones: The new class added was named ad.rb and was the
only one in the file system above our base class named base.rb.

Conclusion

If you set up a class hierarchy the rails way, i.e. having your base class
parallel to your subclasses, be aware of class loading order, because what the
above spork code actually does is to (re)load all models one by one in
alphabetical order!

First it finds ad.rb and loads it.

×

–

⬈

moduleStoriesclassAd<Baseendend

Base is already loaded, so the base class of Ad is set.

Next it finds base.rb and reloads it – thus leading to a new Base class in
memory while Ad‘s base class is pointing to the old one – letting the test
fail.

One way to prove this was to add an explicit require_relative statement to the
Ad class, but this was more than hacky and felt awkward. To avoid having to do
this to all classes we might add in the future, a solution for the spork
preloader was needed.

First I tried adding

×

–

⬈

ActiveSupport::Dependencies.clear

but this didn’t change anything. So we came up with

Spork.each_rundo# reload all the models, base classes first to avoid having different base# classes loaded.Dir["#{Rails.root}/app/models/**/base.rb"].eachdo|model|loadmodelendDir["#{Rails.root}/app/models/**/*.rb"].eachdo|model|loadmodelend# reload factoriesFactoryGirl.reloadend

While this does the trick, it still feels a bit strange, because

we have two Dir blocks searching the file system

base classes are loaded twice

but our tests are passing even when using guard with spork and that was the main
issue.