Можно ли дать субмодулю то же имя, что и классу верхнего уровня? - PullRequest
26 голосов
/ 06 июня 2011

Фон:

Вот проблема, приведенная к минимальному примеру:

# 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

Ответы [ 3 ]

19 голосов
/ 08 июня 2011

Отлично; Ваш пример кода очень понятен. То, что у вас есть, - это круговая зависимость для разнообразия садов, скрытая из-за особенностей оператора разрешения области видимости в 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». Foo еще не было создано, поэтому требование все равно не будет выполнено.

Переименование 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

Надеюсь, это поможет.

2 голосов
/ 30 марта 2013

Вот более минимальный пример, демонстрирующий это поведение:

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

Объяснение простое, ошибки нет: в 1014 * нет 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)
0 голосов
/ 14 ноября 2017

Вот еще один забавный пример:

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

...