Background:
- ruby thinks I'm referencing a top-level constant even when I specify the full namespace
- How do I refer to a submodule's "full path" in ruby?
Here's the problem, distilled down to a minimal example:
# bar.rb
class Bar
end
# foo/bar.rb
module Foo::Bar
end
# foo.rb
class Foo
include Foo::Bar
end
# runner.rb
require 'bar'
require 'foo'
➔ ruby runner.rb ./foo.rb:2: warning: toplevel constant Bar referenced by Foo::Bar ./foo.rb:2:in `include': wrong argument type Class (expected Module) (TypeError) from ./foo.rb:2 from runner.rb:2:in `require' from runner.rb:2
Here's another fun example:
That produces:
block in load_missing_constant': uninitialized constant Integrations::SomeName::Importer::SomeName (NameError)
Ruby (2.3.4) just goes to the first occurrence of "SomeName" it can find, not to the top-level.
A way to get around it is to either use better nesting of modules/classes(!!), or to use
Kernel.const_get('SomeName')
Here is a more minimal example to demonstrate this behavior:
Output:
And here is even more minimal:
Output:
The explanation is simple, there is no bug: there is no
Bar
inFoo
, andFoo::Bar
is not yet defined. ForFoo::Bar
to be defined,Foo
has to be defined first. The following code works fine:However, there is something that is unexpected to me. The following two blocks behave differently:
produces a warning:
but
produces an error:
Excellent; your code sample is very clarifying. What you have there is a garden-variety circular dependency, obscured by the peculiarities of Ruby's scope-resolution operator.
When you run the Ruby code
require 'foo'
, ruby findsfoo.rb
and executes it, and then findsfoo/bar.rb
and executes that. So when Ruby encounters yourFoo
class and executesinclude Foo::Bar
, it looks for a constant namedBar
in the classFoo
, because that's whatFoo::Bar
denotes. When it fails to find one, it searches other enclosing scopes for constants namedBar
, and eventually finds it at the top level. But thatBar
is a class, and so can't beinclude
d.Even if you could persuade
require
to runfoo/bar.rb
beforefoo.rb
, it wouldn't help;module Foo::Bar
means "find the constantFoo
, and if it's a class or a module, start defining a module within it calledBar
".Foo
won't have been created yet, so the require will still fail.Renaming
Foo::Bar
toFoo::UserBar
won't help either, since the name clash isn't ultimately at fault.So what can you do? At a high level, you have to break the cycle somehow. Simplest is to define
Foo
in two parts, like so:Hope this helps.