Подтвердить что ты не робот

Можно ли дать подмодулю то же имя, что и класс верхнего уровня?

Фон:

Здесь проблема, отведенная до минимального примера:

# 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
4b9b3361

Ответ 1

Отлично; ваш образец кода очень разъясняет. То, что у вас есть, - это круговая зависимость, характерная для сада, скрытая особенностями оператора Ruby scope-resolution.

Когда вы запускаете код 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

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

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)

Ответ 3

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

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