Bad names for autoloaded constants don't fail until the second try (Rails 3/4) #12837

Closed
rcrogers opened this Issue Nov 11, 2013 · 7 comments

Projects

None yet

4 participants

@rcrogers

Create a new Rails app with a couple empty modules in the models directory:

rails new bad_constants
cd 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
 => Bar 
1.9.3-p448 :002 > Foo::Bar
NameError: uninitialized constant Foo::Bar

This only seems to affect autoloaded constants.

@frodsan
frodsan commented Nov 11, 2013

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.

@rcrogers

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.

@rafaelfranca
Member

I think @fxn can help to enlighten us here. You can also watch this talk http://www.youtube.com/watch?v=8lYR9WxIRH0

@fxn
Member
fxn commented Nov 11, 2013

@rcrogers yeah, the problem is the first call. There is no way AS can distinguish whether you mean

module Foo
  Bar
end

or rather

Foo::Bar

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".

@rcrogers

@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.

@fxn
Member
fxn commented Nov 11, 2013

@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 fxn closed this Nov 11, 2013
@frodsan
frodsan commented Nov 11, 2013

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment