是否可以为子模块提供与顶级类相同的名称?
背景:
- ruby认为即使我指定了完整的命名空间,我也引用了顶级常量
- 如何在ruby中引用子模块的“完整路径”?
这是问题,简化为最小的例子:
# 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'
➔rubyrunner.rb ./foo.rb:2:warning:Foo :: Bar引用的toplevel常量Bar ./foo.rb:2:in`include':错误的参数类型Class(expect Module)(TypeError) 来自./foo.rb:2 来自runner.rb:2:在`require'中 来自runner.rb:2
优秀; 您的代码示例非常清晰。 你所拥有的是一个花园式的循环依赖,被Ruby的范围分辨算子的特殊性所掩盖。
当您运行Ruby代码foo.rb
require 'foo'
,ruby会找到foo.rb
并执行它,然后找到foo/bar.rb
并执行它。 所以当Ruby遇到你的Foo
类并执行include Foo::Bar
,它会在类Foo
查找一个名为Bar
的常量,因为这就是Foo::Bar
代表的。 当找不到它时,它会在其他封闭的范围内搜索名为Bar
常量,并最终在顶层找到它。 但那个 Bar
是一个类,所以不能include
d。
即使你可以说服foo/bar.rb
之前require
运行foo/bar.rb
foo.rb
,它也无济于事; module Foo::Bar
意思是“找到常量Foo
,如果是类或模块,则开始在其中定义一个名为Bar
的模块”。 Foo
还没有被创建,所以require仍然会失败。
将Foo::Bar
重命名为Foo::UserBar
也无济于事,因为名称冲突最终没有错。
那你能做什么? 在高层次上,你必须以某种方式打破这个循环。 最简单的是将Foo
分为两部分,如下所示:
# 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
希望这可以帮助。
以下是演示此行为的更简单示例:
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
解释很简单,没有错误: Foo
没有Bar
,而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)
这是另一个有趣的例子:
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')