Ruby类名解析中的歧义

时间:2019-01-18 20:04:41

标签: ruby-on-rails ruby

最近,我在Rails代码库中进行了一些代码重构,在该代码库中,我转换了一些代码,这些代码定义了这样的类(消除了类体):

foo/user_bar.rb

module Foo
  class UserBar; end
end

foo/sub_user_bar.rb

module Foo
  class SubUserBar < UserBar; end
end

...至:

foo/bar/user.rb中:

module Foo
  module Bar
    class User; end
  end
end

foo/bar/sub_user.rb中:

module Foo
  module Bar
    class SubUser < User; end
  end
end

这引入了一个微妙的问题,因为已经存在一个名为User(Rails模型)的顶级类,并且SubUser是从中派生的,而不是从User在同一模块中。我猜这是因为Rails加载类的顺序是不可预测的...?

无论如何,我发现可以通过声明SubUser类来消除歧义:

class SubUser < self::User

...这似乎有用,尽管我在少量搜索中找不到任何示例。不过,它看起来确实有些奇怪。现在,我担心我真的应该对模块中的每个类都这样做,以防万一引入了同名的顶级类。

我公司的代码库中有两种样式的模块内类声明:

class A::B::Foo; end

并且:

module A; module B; class Foo; end; end; end

我一直首选后一种方法,因为它允许访问模块中的其他名称而无需完全限定的名称,但是现在我发现使用它时可能存在陷阱。那么,这里的最佳做法是什么?

  • 不要在module中声明类;始终使用完全限定的名称(class A::B::C::Foo < A::B::C::Bar
  • module中声明类,但始终完全限定超类名称(module A; module B; module C; class Foo < A::B::C::Bar
  • module中声明类并使用非限定名称,但在同一模块中扩展类时始终使用self::
  • module中声明类并使用不合格的名称,但要注意顶级名称空间中可能发生的名称冲突(似乎有风险)

还是Ruby和/或Rails提供了其他更干净的方法来解决此问题?

2 个答案:

答案 0 :(得分:3)

通常,在您不确定完全指定名称空间路径的地方:

module Foo
  module Bar
    class SubUser < Foo::Bar::User; end
  end
end

这可能看起来很冗长,但又是明确而明确的。

如果要引用文字顶级User,则应指定::User

答案 1 :(得分:1)

模块由常量标识。 1 因此,模块查找的分辨率由查找常量引用的过程确定。根据David Flanagan和Yukihero Matsumoto(第一版)的“ Ruby编程语言”。 2008(p。261-262),将按以下顺序执行常量查找,其中mod是最内部的模块,其中包含对常量的引用:

  • mod
  • 下一个封闭模块,一直进行到没有其他封闭模块为止。 (在此步骤中不考虑顶级/全局常量)。类方法Module::nesting返回按顺序搜索的模块数组。
  • mod.ancestors的元素,按索引顺序(即继承层次结构)。
  • 顶级常量定义。
  • 方法Module#const_missing返回的值,其中mod是接收者,表示为符号的常量是自变量(前提是已定义该方法)。

让我们看看通过在代码中插入puts Module.nesting.inspectputs ancestors语句可以学到什么。

module Foo
  class UserBar
    puts "nesting for UserBar=#{Module.nesting.inspect}"
    puts "#{self}.ancestors=#{ancestors}"
  end
end
  # nesting for UserBar=[Foo::UserBar, Foo]
  # Foo::UserBar.ancestors=[Foo::UserBar, Object, Kernel,
  #   BasicObject]

module Foo
  class SubUserBar < UserBar
    puts "nesting for SubUserBar=#{Module.nesting.inspect}"
    puts "#{self}.ancestors=#{ancestors}"
  end
end
  # nesting for SubUserBar=[Foo::SubUserBar, Foo]
  # Foo::SubUserBar.ancestors=[Foo::SubUserBar,
  #   Foo::UserBar, Object, Kernel, BasicObject]

module Foo
  module Bar
    class User
      puts "nesting for User=#{Module.nesting.inspect}"
      puts "#{self}.ancestors=#{ancestors}"
    end
  end
end
  # nesting for User=[Foo::Bar::User, Foo::Bar, Foo]
  # Foo::Bar::User.ancestors=[Foo::Bar::User, Object, Kernel,
  #   BasicObject]

module Foo
  module Bar
    class SubUser < User
      puts "nesting for SubBar=#{Module.nesting.inspect}"
      puts "#{self}.ancestors=#{ancestors}"
    end
  end
end
  # nesting for SubBar=[Foo::Bar::SubUser, Foo::Bar, Foo]
  # Foo::Bar::SubUser.ancestors=[Foo::Bar::SubUser,
  #   Foo::Bar::User, Object, Kernel, BasicObject]

这告诉我们,如果User中不包含某个类Foo(如在Rails中一样),则该类将成为类{中对User的引用对象{1}}和Foo::UserBar,但不在Foo::SubUserBarFoo::Bar::User中。这种理解应该有助于组织模块以避免命名空间问题。

1因为类是模块,所以每当我在下面提到“模块”时,我都在指代类以及不是类的模块。