Spork and the Supermodel
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_id
s 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.
module Stories
class Ad < Base
end
end
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_run do
# reload all the models, base classes first to avoid having different base
# classes loaded.
Dir["#{Rails.root}/app/models/**/base.rb"].each do |model|
load model
end
Dir["#{Rails.root}/app/models/**/*.rb"].each do |model|
load model
end
# reload factories
FactoryGirl.reload
end
```
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.
If anyone has a better solution for this, please let me know.
Es gibt noch keine Kommentare