是否可以为子模块提供与顶级类相同的名称?

背景:

  • 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')