I've a module SecuritySystem::SecretsProxy.
SecuritySystem::SecretsProxy::AnyConstant evaluates to AnyConstant (as
long as AnyConstant is something Dependencies can load but is not
loaded yet), even though AnyConstant isn't defined at that level. The
second time you evaluate SecuritySystem::SecretsProxy::AnyConstant
then you get a NameError as I expected to get in the first place.
When load_missing_constant is called (to find AnyConstant inside
SecuritySystem::SecretsProxy) it does check whether any of its parents
defined AnyConstant (precisely so that
SecuritySystem::SecretsProxy::AnyConstant cannot find ::AnyConstant.
However in the edge case I describe AnyConstant has not been loaded at
all and so this check does nothing.
I think this is slightly unhelpful behaviour (I certainly spent some
time scratching my head as a result of it, the fact that it didn't
occur in development because of the the reloading that goes on made it
even more interesting). I've done a little playing around to see if I
could catch this and raise a more helpful NameError but can't (at
least not after half an hour's fiddling) come up with something that
correctly identifies this situation.
const_missing calls one way or another someting like
Dependencies.load_missing_constant(self, const_name)
which is the single entry point to dependencies.rb.
I think the key point is that the current approach to autoloading cannot distinguish
module M
CONSTANT_TO_AUTOLOAD
end
from
M::CONSTANT_TO_AUTOLOAD
The second use case is less flexible than the first one because there's no doubt that if there's any CONSTANT_TO_AUTOLOAD to look for it should be defined in some m/constant_to_autoload.rb. It should be a constant named "CONSTANT_TO_AUTOLOAD" that would belong to some module with at least basename "M". That can't end falling back
But as you noticed that's what it does in the second use case. For example, given
app/models/admin
lib/user.rb
a few added traces show that dependencies.rb looks for Admin::User this way:
$ script/runner 'puts Admin::User'
1) Class#const_missing for Object::Admin
2) Module#const_missing for Object::Admin
3) Module#const_missing for Admin::User
4) Class#const_missing for Object::User
5) Module#const_missing for Object::User
6) User
The Admin module is defined on-the-fly after 2). 3) fails resolving admin/user.rb because it does not exist. And then instead of halting it still finds user.rb and define User in Object. In my opinion this behaviour is wrong.
But it would be right if we had written instead
$ script/runner 'module Admin; User; end'
because you are here kind of emulating the lexical side of constant name resolution.
The bad news are that from within dependencies.rb I see no way at 1:34 AM to distinguish both use cases. As a quick thought I believe you'd need to be able to know what's Module.nesting in the caller.
Hmmm.
-- fxn