可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
Background:
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'
回答1:
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 finds foo.rb
and executes it, and then finds foo/bar.rb
and executes that. So when Ruby encounters your Foo
class and executes include Foo::Bar
, it looks for a constant named Bar
in the class Foo
, because that's what Foo::Bar
denotes. When it fails to find one, it searches other enclosing scopes for constants named Bar
, and eventually finds it at the top level. But that Bar
is a class, and so can't be include
d.
Even if you could persuade require
to run foo/bar.rb
before foo.rb
, it wouldn't help; module Foo::Bar
means "find the constant Foo
, and if it's a class or a module, start defining a module within it called Bar
". Foo
won't have been created yet, so the require will still fail.
Renaming Foo::Bar
to Foo::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:
# bar.rb class Bar A = 4 end # foo.rb class Foo # Stuff that doesn't depend on Foo::Bar goes here. end # foo/bar.rb module Foo::Bar A = 5 end class Foo # Yep, we re-open class Foo inside foo/bar.rb include Bar # Note that you don't need Foo:: as we automatically search Foo first. end Bar::A # => 4 Foo::Bar::A # => 5
Hope this helps.
回答2:
Here is a more minimal example to demonstrate this behavior:
class Bar; end class Foo include Foo::Bar end
Output:
warning: toplevel constant Bar referenced by Foo::Bar TypeError: wrong argument type Class (expected Module)
And here is even more minimal:
Bar = 0 class Foo; end Foo::Bar
Output:
warning: toplevel constant Bar referenced by Foo::Bar
The explanation is simple, there is no bug: there is no Bar
in Foo
, and Foo::Bar
is not yet defined. For Foo::Bar
to be defined, Foo
has to be defined first. The following code works fine:
class Bar; end class Foo module ::Foo::Bar; end include Foo::Bar end
However, there is something that is unexpected to me. The following two blocks behave differently:
Bar = 0 class Foo; end Foo::Bar
produces a warning:
warning: toplevel constant Bar referenced by Foo::Bar
but
Bar = 0 module Foo; end Foo::Bar
produces an error:
uninitialized constant Foo::Bar (NameError)
回答3:
Here's another fun example:
module SomeName class Client end end module Integrations::SomeName::Importer def perform ... client = ::SomeName::Client.new(...) ... end end
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')