Matt
Matt

Reputation: 10564

Ruby: Is there a way to get the enclosing Module const of a Class?

I'm doing some metaprogramming in Ruby, and I need to dynamically generate a sibling class inside of a module. In doing so, I want to call const_set on the module, but I don't know which Module constant to call that on until runtime. An example:

Given classes

Foo::Bar::Baz
Foo::Quox::Quack

I want to be able to call a function like this (oversimplified here):

def generate_from klass
  mod = klass.enclosing_module # <- THIS LINE is the one I need to figure out
  mod.const_set("GeneratedClassName", Class.new)
end

and what I want to end up with, when calling with Baz, is a new class defined as

Foo::Bar::GeneratedClassName

and with a Quack, I want

Foo::Quox::GeneratedClassName

The only way I know of is to split up klass.name, then repeatedly call const_get on those strings, constantized. Does anyone know of a more elegant way?

Upvotes: 14

Views: 10451

Answers (5)

nakwa
nakwa

Reputation: 1213

As of 2024 and Rails 7.x:

Module.parent is deprecated. It's now module_parent:

> Auth::User.module_parent
 => Auth

> Auth::User.module_parent_name
 => "Auth"

> Auth::User.module_parents
 => [Auth,Object]

See: https://github.com/rails/rails/blob/9e01d93547e2082e2e88472748baa0f9ea63c181/activesupport/lib/active_support/core_ext/module/introspection.rb#L34

And, in the docs: https://api.rubyonrails.org/classes/Module.html

Upvotes: 2

jwood
jwood

Reputation: 373

In case anyone is looking for a pure ruby version:

  def get_parent_type
    #Note: This will break for base types (lacking '::' in the name)
    parent_type=self.class.name.split('::')[0...-1]
    begin
      Object.const_get(parent_type.join('::'))
    rescue NameError => e
      nil
    end
  end

Upvotes: 2

Ben
Ben

Reputation: 837

In Rails you can use a combination of deconstantize and constantize.

'Foo::Bar::Baz'.deconstantize.constantize # => Foo::Bar

so in a method of the class it can be used like this:

self.class.name.deconstantize.constantize

Upvotes: 1

Michael Kohl
Michael Kohl

Reputation: 66837

This should get you on track:

module Foo
  module Bar
    class Baz
      def initialize
        @nesting = Module.nesting
      end

      def enclosing_module
        @nesting.last
      end
    end
  end
end

puts Foo::Bar::Baz.new.enclosing_module #=> Foo

Relevant documentation:

http://ruby-doc.org/core/classes/Module.html#M000441

Upvotes: 18

Matt
Matt

Reputation: 10564

Got it. ActiveSupport has this Ruby extension, Module#parent. It's good enough for my use.

Upvotes: 13

Related Questions