Is it possible to give a sub-module the same name as a top-level class?

前端 未结 3 703
忘了有多久
忘了有多久 2020-12-16 11:07

Background:

  • ruby thinks I'm referencing a top-level constant even when I specify the full namespace
  • How do I refer to a submodule's "full
相关标签:
3条回答
  • 2020-12-16 11:22

    Excellent; your code sample is very clarifying. What you have there is a garden-variety circular dependency, obscured by the peculiarities of Ruby's scope-resolution operator.

    When you run the Ruby code require 'foo', ruby finds foo.rb and executes it, and then finds foo/bar.rb and executes that. So when Ruby encounters your Foo class and executes include Foo::Bar, it looks for a constant named Bar in the class Foo, because that's what Foo::Bar denotes. When it fails to find one, it searches other enclosing scopes for constants named Bar, and eventually finds it at the top level. But that Bar is a class, and so can't be included.

    Even if you could persuade require to run foo/bar.rb before foo.rb, it wouldn't help; module Foo::Bar means "find the constant Foo, and if it's a class or a module, start defining a module within it called Bar". Foo won't have been created yet, so the require will still fail.

    Renaming Foo::Bar to Foo::UserBar won't help either, since the name clash isn't ultimately at fault.

    So what can you do? At a high level, you have to break the cycle somehow. Simplest is to define Foo in two parts, like so:

    # 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
    

    Hope this helps.

    0 讨论(0)
  • 2020-12-16 11:31

    Here is a more minimal example to demonstrate this behavior:

    class Bar; end
    class Foo
      include Foo::Bar
    end
    

    Output:

    warning: toplevel constant Bar referenced by Foo::Bar
    TypeError: wrong argument type Class (expected Module)
    

    And here is even more minimal:

    Bar = 0
    class Foo; end
    Foo::Bar
    

    Output:

    warning: toplevel constant Bar referenced by Foo::Bar
    

    The explanation is simple, there is no bug: there is no Bar in Foo, and Foo::Bar is not yet defined. For Foo::Bar to be defined, Foo has to be defined first. The following code works fine:

    class Bar; end
    class Foo
      module ::Foo::Bar; end
      include Foo::Bar
    end
    

    However, there is something that is unexpected to me. The following two blocks behave differently:

    Bar = 0
    class Foo; end
    Foo::Bar
    

    produces a warning:

    warning: toplevel constant Bar referenced by Foo::Bar
    

    but

    Bar = 0
    module Foo; end
    Foo::Bar
    

    produces an error:

    uninitialized constant Foo::Bar (NameError)
    
    0 讨论(0)
  • 2020-12-16 11:41

    Here's another fun example:

    module SomeName
      class Client
      end
    end
    
    module Integrations::SomeName::Importer
      def perform
        ...
        client = ::SomeName::Client.new(...)
        ...
      end
    end
    

    That produces:

    block in load_missing_constant': uninitialized constant Integrations::SomeName::Importer::SomeName (NameError)

    Ruby (2.3.4) just goes to the first occurrence of "SomeName" it can find, not to the top-level.

    A way to get around it is to either use better nesting of modules/classes(!!), or to use Kernel.const_get('SomeName')

    0 讨论(0)
提交回复
热议问题