Last active
December 19, 2015 22:58
-
-
Save urbanautomaton/6031355 to your computer and use it in GitHub Desktop.
Fuck Rails const autoloading
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# lib/a.rb | |
module A | |
end | |
# lib/b.rb | |
class B | |
end | |
# $ rails console | |
# > A.const_missing("B") | |
# => B | |
# > A.const_missing("B") | |
# NameError: uninitialized constant A::B |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
It turns out that this more-or-less makes sense, if you take it on faith that reimplementing the entirety of Ruby's loading system in the service of questionable convenience is a sensible thing to attempt in the first place.
On the first call to
A.const_missing("B")
, no constantsB
in any nesting are loaded. But that doesn't mean they don't exist, because of autoloading. ThereforeA.const_missing("B")
can't simply say "oh, there's noA::B
" and give up, because a reference toB
within the scope of A might have been to a not-yet-loaded top-levelB
.So it starts climbing the list of
A
's parents. It next callsObject.const_missing("B")
, which findsB
and says "here, you were looking for this."On the second call to
A.const_missing("B")
, there's still noA::B
. But this time the top-levelB
is already loaded, and therefore can't have causedA.const_missing("B")
to be invoked, because that reference would have been resolved normally, without recourse toconst_missing
.Therefore the reference to
B
withinA
can only have meantA::B
, which does not exist, and therefore aNameError
is raised.Nesting
As a side note, this highlights a weakness of Rails' re-implementation of constant loading. As Ruby does not pass nesting information along with
const_missing
calls, Rails'const_missing
implementation is unable to distinguish between the following:It therefore makes the assumption that the former nesting is the case. This means that the reference to
C
in the second example will potentially (and incorrectly) resolve to a constantA::C
.So what?
The reason this was relevant to me is that it bollockses up
String#constantize
in Activesupport versions before 4.0, because it incorrectly interpreted a response fromA.const_missing("B")
to mean that a constantA::B
had been found. This has now been fixed, so you don't get results like this: