Autoloading (and re-loading) generally works fine in my Ruby (2.3.0
) on Rails (5.0.1
) project. However, in development mode I occasionally see an error like the following:
Unable to autoload constant Foo::Bar, expected /app/models/foo/bar.rb to define it
This is unexpected because:
foo/bar.rb
) does, in fact, define Foo::Bar
.Moreover, the code for foo/bar.rb
is dead simple:
module Foo
class Bar < CustomRecord
end
end
The simple work-around is to restart the server and then send the request again (which always succeeds). FWIW, I'm using zeus server
.
My best-guess is that something is not being reloaded, but I'm not sure how to go about debugging. I can't seem to point to any specific action which causes the problem. Sometimes editing code causes it to happen, sometimes not.
There are a couple of good explanations for Rails autoloading behavior that you can find. This answer provides a useful explanation, which it grabs from this blog post at urbanautomaton.com.
Summary:
Basically, Rails first uses Ruby's constant lookup. If that fails, then it has its own lookup, which will miss files if
config.autoload_paths += %W(#{config.root}/foo)
is not added to config/application.rb)It will find Foo::Bar
in app/foo/bar.rb if it's defined as:
module Foo
class Bar
end
end
but not
module Foo
module Bar
end
end
Quote from the blog post describing what I mean:
At this point, we’ve only seen how a single constant name maps to a single file name. But as we know, a constant reference in Ruby can resolve to a number of different constant definitions, which vary depending on the nesting in which the reference was made. How does Rails handle this?
The answer is, “partially”. As
Module#const_missing
passes no nesting information to the receiver, Rails does not know the nesting in which the reference was made, and it must make an assumption. For any reference to a constantFoo::Bar::Baz
, it assumes the following:module Foo module Bar Baz # Module.nesting => [Foo::Bar, Foo] end
end
In other words, it assumes the maximum nesting possible for a given constant reference. The example reference is therefore treated exactly the same as the following:
Foo::Bar::Baz # Module.nesting => []
module Foo::Bar Baz # Module.nesting => [Foo::Bar] end
While there’s been a significant loss of information, Rails does have some extra information it can use. It knows that Ruby failed to resolve this particular constant reference using its regular lookup, meaning that whatever constant it should refer to cannot be already loaded.
When
Foo::Bar::Baz
is referred to, then, Rails will attempt to load the following constants in turn, until it finds one that is already loaded:
Foo::Bar::Baz
Foo::Baz
Baz
As soon as an already-loaded constant
Baz
is encountered, Rails knows this cannot be theBaz
it is looking for, and the algorithm raises aNameError
.
It's difficult to say what exactly is causing your problem(s) without more specifics about how your code is set up, but hopefully understanding what's going on with autoloading in Rails might help a little. I would highly recommend reading through the blog post and answer linked above.
Edited for possible solution:
Make sure 'app/models/foo' is added to your autoload_load paths in config/application.rb - or move 'foo' to app/models/concerns, which should be there by default.
Often adding explicit require
statements helps - especially if things are loading properly in development but not in test. I recently solved this problem by adding a file that required all of my nested files (I had a directory structure that did not mirror my namespace structure) and then requiring that file in my test helper.
Second edit
Including information relevant to the question, why a constant might load successfully only once.
From the same blog post:
If constants are loaded only when they’re first encountered at runtime, then by necessity their load order depends on the individual execution path. This can mean that the same constant reference resolves to different constant definitions in two runs of the same code. Worse still, the same constant reference twice in a row can give different results.
Let’s go back to our last example. What happens if we call .print_qux twice?
> Foo::Bar.print_qux I'm in Foo! => nil > Foo::Bar.print_qux
NameError: uninitialized constant Foo::Bar::Qux This is disastrous! First we’ve been given the wrong result, and then we’ve been incorrectly told that the constant we referred to doesn’t exist. What on earth led to this?
The first time, as before, is down to the loss of nesting information. Rails can’t know that Foo::Qux isn’t what we’re after, so once it realises that Foo::Bar::Qux does not exist, it happily loads it.
The second time, however, Foo::Qux is already loaded. So our reference can’t have been to that constant, otherwise Ruby would have resolved it, and autoloading would never have been invoked. So the lookup terminates with a NameError, even though our reference could (and should) have resolved to the as-yet-unloaded ::Qux.
We can fix this by referring to ::Qux first, ensuring that it’s loaded for Ruby to resolve the reference:
> Qux => "I'm at the root!" > Foo::Bar.print_qux I'm at the root! => nil > Foo::Bar.print_qux I'm at the root! => nil
A funny thing has happened here. In order to get correct behaviour, we deliberately loaded the constant we needed before we used it (albeit indirectly, by referring to it, rather than loading the file that defined it).
But wait; isn’t this suspiciously close to explicitly loading our dependencies with require, the very thing autoloading was supposed to save us from?
To be fair, we could also have fixed the issue by fully qualifying all of our constant references, i.e. making sure that within .print_qux we referred to ::Qux and not the ambiguous Qux. But this still costs us our existing intuitions about Ruby’s behaviour. Moreover, without intimate knowledge of the autoloading process, we would have been hard pressed to deduce that this was necessary.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With