背景:
这是问题所在,简化为最小的例子:
# 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
答案 0 :(得分:19)
出色;您的代码示例非常清晰。你所拥有的是一个花园式的循环依赖,被Ruby的范围分辨算子的特殊性所掩盖。
当你运行Ruby代码require 'foo'
时,ruby会找到foo.rb
并执行它,然后找到foo/bar.rb
并执行它。因此,当Ruby遇到您的Foo
类并执行include Foo::Bar
时,它会在类Bar
中查找名为Foo
的常量,因为这是Foo::Bar
所表示的。当找不到它时,它会在其他封闭范围内搜索名为Bar
的常量,并最终在顶层找到它。但 Bar
是一个类,因此不能include
d。
即使您能说服require
在foo/bar.rb
之前运行foo.rb
,也无济于事; module Foo::Bar
表示“找到常量Foo
,如果它是类或模块,则开始在其中定义名为Bar
的模块”。 <{1}}尚未创建,因此需求仍然会失败。
将Foo
重命名为Foo::Bar
也无济于事,因为名称冲突最终没有错。
那么可以你做什么?在高层次上,你必须以某种方式打破这个循环。最简单的方法是将Foo::UserBar
分为两部分,如下所示:
Foo
希望这有帮助。
答案 1 :(得分:2)
以下是演示此行为的更简单示例:
class Bar; end
class Foo
include Foo::Bar
end
输出:
warning: toplevel constant Bar referenced by Foo::Bar
TypeError: wrong argument type Class (expected Module)
这里更加微不足道:
Bar = 0
class Foo; end
Foo::Bar
输出:
warning: toplevel constant Bar referenced by Foo::Bar
解释很简单,没有错误:Bar
中没有Foo
,而Foo::Bar
尚未定义。要定义Foo::Bar
,必须先定义Foo
。以下代码工作正常:
class Bar; end
class Foo
module ::Foo::Bar; end
include Foo::Bar
end
然而,有些事情对我来说是意想不到的。以下两个块的行为不同:
Bar = 0
class Foo; end
Foo::Bar
发出警告:
warning: toplevel constant Bar referenced by Foo::Bar
但
Bar = 0
module Foo; end
Foo::Bar
产生错误:
uninitialized constant Foo::Bar (NameError)
答案 2 :(得分:0)
这是另一个有趣的例子:
module SomeName
class Client
end
end
module Integrations::SomeName::Importer
def perform
...
client = ::SomeName::Client.new(...)
...
end
end
产生:
block in load_missing_constant': uninitialized constant Integrations::SomeName::Importer::SomeName (NameError)
Ruby(2.3.4)只是发现它可以找到的第一个“SomeName”,而不是顶层。
解决它的方法是使用更好的模块/类嵌套(!!),或者使用Kernel.const_get('SomeName')