rubymodule

Refer to a class in Ruby


In Ruby I create a module like this:

Mod = Module.new do
  class MyClass
    attr_reader :a
    def initialize(a)
      @a = a
    end
  end
end

And I'm trying to create an instance of the class:

puts Mod::MyClass.new(1).a

But I get an error:

(irb):10:in `<main>': uninitialized constant Mod::MyClass (NameError)

puts Mod::MyClass.new(1).a

Although that's how it works:

Mod.module_eval { puts MyClass.new(1).a }
1
 => nil

I'm also trying to make the class public:

Mod.instance_eval do
  public_constant :MyClass
end

But I also get an error:

(irb):16:in `public_constant': constant Mod::MyClass not defined (NameError)

  public_constant :MyClass

But if you create it like this, then everything works:

module Mod2
  class MyClass
    attr_reader :a
    def initialize(a)
      @a = a
    end
  end
end
puts Mod2::MyClass.new(1).a
1
 => nil

Is there a way to access Mod::MyClass without eval?

puts Mod::MyClass.new(1).a

Solution

  • The module keyword creates a namespace:

    Module.nesting #=> []       # <- top level
    
    module Mod
      Module.nesting #=> [Mod]  # <- module namespace
    
      class MyClass
        Module.nesting #=> [Mod::MyClass, Mod]
      end
    end
    

    whereas Module.new doesn't:

    Module.nesting #=> []       # <- top level
    
    Mod = Module.new do
      Module.nesting #=> []     # <- still top level
    
      class MyClass
        Module.nesting #=> [MyClass]
      end
    end
    

    Which is why your class is defined on the top level despite being created from inside the module. Namespace-wise, your code is equivalent to:

    Mod = Module.new do
      # ...
    end
    
    class MyClass
      # ...
    end
    

    You can use the scope resolution operator :: to explicitly define a class under an existing module. (see the docs on Nesting)

    Within the Module.new block this would either be self:

    Mod = Module.new do
      class self::MyClass
        # ...
      end
    end
    

    or the block's argument which refers to the module object:

    Mod = Module.new do |m|
      class m::MyClass
        # ...
      end
    end
    

    or the constant you're assigning the anonymous module to: (note that the constant becomes available only after the block has been evaluated)

    Mod = Module.new do
      # ...
    end
    
    class Mod::MyClass
       # ...
    end
    

    ... depending on your use case.