Create a new Rails app with a couple empty modules in the models directory:
rails new bad_constants
echo 'module Foo; end' > app/models/foo.rb
echo 'module Bar; end' > app/models/bar.rb
Then open a console (rails c) and try to load a constant by a bad name. It works the first time, but not the second:
1.9.3-p448 :001 > Foo::Bar
1.9.3-p448 :002 > Foo::Bar
NameError: uninitialized constant Foo::Bar
This only seems to affect autoloaded constants.
I guess this is one of the trade-offs explained here https://github.com/rails/rails/blob/master/activesupport/lib/active_support/dependencies.rb#L481
In some point, we do Object.const_missing('Bar'), that's why it returned the Bar constant.
I can understand the need for the trade-off, but I don't see any indication that it was intended to cause inconsistent results from successive calls.
I think @fxn can help to enlighten us here. You can also watch this talk http://www.youtube.com/watch?v=8lYR9WxIRH0
@rcrogers yeah, the problem is the first call. There is no way AS can distinguish whether you mean
and assumes we are in the first case and it returns the top-level Bar. The first call should fail, but there is no way around it. Due to the lack of metadata given to const_missing AS cannot emulate Ruby constant name resolution, and it does not pretend to. In particular it won't fail in that corner-case in the first run.
If Foo::Bar is a bug, the solution is to fix it. If it is not a bug, the solution is to require_dependency "bar".
@fxn What state change occurs between the first and second calls?
I appreciate the workaround, but that only helps me, not the many other developers who will encounter this confusing behavior.
@rcrogers The difference is that in the second call AS knows a top-level Bar exists. That is what changes. If there is a Bar then you are sure the use case was Foo::Bar because otherwise const_missing would not have been triggered.
That's the way it is, you cannot expect to throw any constant name resolution use case and expect that it works, because AS does not emulate constant name resolution.
In some cases AS resolves as Ruby would, in other cases it does not because it cannot. This is one of those cases and we cannot do anything about it.
You have to use AS the other way around: use constants the way AS understands them, rather than the way Ruby expects hoping it matches AS.
Going to close this one, it may be surprising if you do not know how AS works, but it is not a bug.
@fxn Thanks for the clarification :)! Yesterday, I was struggling to fix this but ended up with the same conclusion. I didn't know how to explain it though :p