Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

ruby

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

like image 407
Joe_P Avatar asked Dec 10 '19 19:12

Joe_P


People also ask

What is Namespacing in Ruby?

Namespace in Ruby allows multiple structures to be written using hierarchical manner. Thus, one can reuse the names within the single main namespace. The namespace in Ruby is defined by prefixing the keyword module in front of the namespace name. The name of namespaces and classes always start from a capital letter.

What is the difference between a class and a module Ruby?

What is the difference between a class and a module? Modules are collections of methods and constants. They cannot generate instances. Classes may generate instances (objects), and have per-instance state (instance variables).

Can we define module inside class Ruby?

Discussion. You can define and access instance variables within a module's instance methods, but you can't actually instantiate a module. A module's instance variables exist only within objects of a class that includes the module.

What does Module do in Ruby?

In Ruby, modules are somewhat similar to classes: they are things that hold methods, just like classes do. However, modules can not be instantiated. I.e., it is not possible to create objects from a module. And modules, unlike classes, therefore do not have a method new .


1 Answers

It may seem counter-intuitive, but constant lookup in Ruby is done using current lexical scope, i.e. 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.

like image 77
Casper Avatar answered Sep 21 '22 22:09

Casper