Rubyでモジュールの親モジュールを取得する

2 min read読了の目安(約2400字

概要

以下のようなモジュールの階層があるときに、ChildモジュールからParentモジュールのVALUEを参照したかった。

module Parent
  VALUE = 'PARENT'

  module Child
    VALUE = 'CHILD'

    def self.parent_value
      # 'PARENT' がほしい
    end
  end
end

Rails(activesupport) が使える環境なら簡単

流石に Module#parent 的なメソッドがRubyで提供されてるだろうと思い調べて見たが、どうやらRuby(2.7現在)本体にはそのような機能はなく、Rails の拡張(activesupport)で提供されていることがわかった。

Module#parent

そのため、Rails環境では以下のように単純にparentを参照することで対応可能

module Parent
  VALUE = 'PARENT'

  module Child
    VALUE = 'CHILD'

    def self.parent_value
      self.parent::VALUE
    end
  end
end
[4] pry(main)> Parent::Child.parent_value
=> "PARENT"

Rails以外でも使いたいので実装する

Railsでない(activesupportが使用できない) 状態の場合、以下のようにparentメソッドがないよと怒られてしまう。

irb(main):008:0> Parent::Child.parent_value
Traceback (most recent call last):
        5: from /Users/shingo.sasaki/.rbenv/versions/2.6.5/bin/irb:23:in `<main>'
        4: from /Users/shingo.sasaki/.rbenv/versions/2.6.5/bin/irb:23:in `load'
        3: from /Users/shingo.sasaki/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/irb-1.0.0/exe/irb:11:in `<top (required)>'
        2: from (irb):8
        1: from /Users/shingo.sasaki/Docker/teachme/app/libraries/hoge.rb:8:in `parent_value'
NoMethodError (undefined method `parent' for Parent::Child:Module)

activesupport では以下のように実装されている。

def parent
  parent_name ? ActiveSupport::Inflector.constantize(parent_name) : Object
end
def parent_name
  if defined?(@parent_name)
    @parent_name
  else
    parent_name = name =~ /::[^:]+\Z/ ? $`.freeze : nil
    @parent_name = parent_name unless frozen?
    parent_name
  end
end

と、コードを追っていくと以下のような手段を用いていることがわかる

  • Module#name を使って、モジュール名を文字列で取得
  • :: を元に、親モジュールまでのモジュール名を正規表現で抽出
  • Object.const_get を用いて文字列からモジュールを取得

よって、Moduleクラス自体を以下のように拡張すれば近いことができる(シンプルさを重視して、実際のactivesupportほど手広くカバーしてません)

class Module
  def parent
    parent_name = self.name =~ /::[^:]+\Z/ ? $`.freeze : nil
    parent_name ? Object.const_get(parent_name) : Object
  end
end

↑を読み込んだ状態ならこんな構造があっても

module Parent
  module Child
    module GrandChild
    end
  end
end

階層をたどることが出来る

[3] pry(main)> Parent::Child::GrandChild.parent
=> Parent::Child
[4] pry(main)> Parent::Child::GrandChild.parent.parent
=> Parent
[5] pry(main)> Parent::Child::GrandChild.parent.parent.parent
=> Object