简体   繁体   中英

Ruby Class namespacing with modules: Why do I get NameError with double colons but not module blocks?

I am working with a lot of pre-existing files, classes, and modules and trying to come up with better namespacing for the different components of the framework. I've been using modules as a way to namespace mainly because this seems like the standard convention (and being able to 'include' different parts of the framework could be useful).

The problem is that there was a ton of classes underneath the global namespace that should exist underneath a module. For example, let's say there is a class that was simply defined as:

class FirstClass
  def meth
    puts "HELLO"
  end
end

But now I want to have this class within a module:

Using Double Colons:

module Foo; end

class Foo::FirstClass
  def meth
    puts 'HELLO'
  end
end

Using Module Blocks:

module Foo
  class FirstClass
    def meth
      puts 'HELLO'
    end
  end

Using double colons is a lot cleaner and also a lot easier to implement since I am changing many class definitions. Both of these ways work and I thought that they are both effectively the same thing, but evidently they are not. The double colon method seems to result in a different namespace within each class compared to the module block. For instance, with two classes underneath "Foo":

Using Module Blocks:

module Foo
  class FirstClass
    def meth
      puts 'HELLO'
    end
  end

  class SecondClass
    def meth
      FirstClass.new.meth
    end
  end
end

Foo::SecondClass.new.meth

Using Double Colons:

module Foo; end

class Foo::FirstClass
  def meth
    puts 'HELLO'
  end
end

class Foo::SecondClass
  def meth
    FirstClass.new.meth
  end
end

Foo::SecondClass.new.meth

The code works when using module blocks, but doesn't work with double colons. With the double colons, NameError is raised because it resolves FirstClass as Foo::SecondClass::FirstClass (instead of Foo::FirstClass ), which doesn't exist.

This can easily be solved by including Foo in SecondClass , but how come this isn't done by default?

Note: I'm using Ruby 2.1.5, which I know is outdated, but I get the same results on repl.it with ruby 2.5.5p157: https://repl.it/@joep2/Colon-vs-Block-Namespacing

It may seem counter-intuitive, but constant lookup in Ruby is done using current lexical scope , ie the current lexical nesting level (location in the source code), not the semantic nesting level.

This can be tested by inspecting Module.nesting , which prints the current lexical scope:

class Foo::SecondClass
  pp Module.nesting       # -> [Foo::SecondClass]
end

module Foo
  class SecondClass
    pp Module.nesting     # -> [Foo::SecondClass, Foo]
  end
end

Since Ruby uses this nesting level for symbol lookup, it means in the situation where you try to look up FirstClass within nesting [Foo::SecondClass] , Ruby will not find it.

However when you try to look it up within nesting [Foo::SecondClass, Foo] , it will find FirstClass under Foo , just like you expect.

To get around this, you could do:

class Foo::SecondClass
  def meth
    Foo::FirstClass.new.meth
  end
end

Which will now work as you expect, since you provided the necessary lookup hint for FirstClass , and told Ruby it is inside Foo .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM