web-dev-qa-db-ja.com

リフレクションを介してRubyのモジュールクラスで定義された定数を取得するにはどうすればよいですか?

私はマッツとフラナガンの "Rubyプログラミング言語"メタプログラミングの章を頭に入れようとしていましたが、思い描いた次のコードスニペットからの出力を理解できませんでした。

p Module.constants.length           # => 88
$snapshot1 = Module.constants       
class A
  NAME=:abc

  $snapshot2 = Module.constants
  p $snapshot2.length               # => 90
  p $snapshot2 - $snapshot1         # => ["A", "NAME"]

end
p Module.constants.length           # => 89
p Module.constants - $snapshot1     # => ["A"]
p A.constants                       # => ["NAME"]

この本は、クラスメソッドconstantsがクラスの定数のリストを返すと述べています(A.constantsの出力で確認できます)。上記の奇妙な動作に遭遇したとき、私はモジュールクラスに定義された定数のリストを取得しようとしていました。

Aの定数はModule.constantsに表示されます。 Moduleクラスで定義されている定数のリストを取得するにはどうすればよいですか?

docs 状態

Module.constantsは、システムで定義されているすべての定数を返します。すべてのクラスとメソッドの名前を含む

Aはその実装をModule.constantsから継承しているため、基本型と派生型ではどのように異なる動作をしますか?

p A.class               # => Class
p A.class.ancestors       # => [Class, Module, Object, Kernel]

注:Ruby 1.9を使用している場合、constantsは文字列ではなくシンボルの配列を返します。

43
Gishu

良い質問!

クラスメソッド_Module.constants_がインスタンスメソッド_Module#constants_をModuleに対して非表示にしているため、混乱が生じます。

Ruby 1.9では、オプションのパラメータを追加することで対処されています。

_# No argument: same class method as in 1.8:
Module.constants         # ==> All constants
# One argument: uses the instance method:
Module.constants(true)   # ==> Constants of Module (and included modules)
Module.constants(false)  # ==> Constants of Module (only).
_

上記の例では、_A.constants_は_Module#constants_(インスタンスメソッド)を呼び出し、_Module.constants_は_Module.constants_を呼び出します。

したがって、Ruby 1.9では、Module.constants(true)を呼び出す必要があります。

Ruby 1.8では、Moduleに対してインスタンスメソッド_#constants_を呼び出すことができます。インスタンスメソッドを取得し、それをクラスメソッドとして(別の名前を使用して)バインドする必要があります。

_class << Module
  define_method :constants_of_module, Module.instance_method(:constants)
end

# Now use this new class method:
class Module
   COOL = 42
end
Module.constants.include?("COOL")  # ==> false, as you mention
Module.constants_of_module         # ==> ["COOL"], the result you want
_

backports gemの1.9機能を完全に1.8にバックポートできたらいいのにと思いますが、Ruby 1.8。

編集:これを正しく反映するように公式ドキュメントを変更しました...

56

マルクの返答の後、しばらくの間、私は思考洞窟に戻らなければなりませんでした。より多くのコードスニペットをいじり、さらにいくつか。最後に、Rubyのメソッド解決が理にかなっているように思えたときに、それをブログの投稿として書き留めたので、忘れないようにしてください。

表記:A "[〜#〜] a [〜#〜]

A.constantsが呼び出されると、メソッド解決(視覚的な支援を得るために 私のブログ投稿 の画像を参照)が次の場所を順に検索します

  • MyClass"Object"BasicObject"(シングルトンメソッド)
  • Class(インスタンスメソッド)
  • Module(インスタンスメソッド)
  • Object(インスタンスメソッド)とカーネル
  • BasicObject(インスタンスメソッド)

RubyはインスタンスメソッドModule#constantsを見つけます

Module.constantsが呼び出されると、Rubyは

  • Module"Object"BasicObject"(シングルトンメソッド)
  • Class(インスタンスメソッド)
  • Module(インスタンスメソッド)
  • Object(インスタンスメソッド)とカーネル
  • BasicObject(インスタンスメソッド)

今回、Rubyは、マークが言ったようにModule".constantsでシングルトン/クラスメソッドを見つけます。

モジュールは、インスタンスメソッドをシャドウするシングルトンメソッドを定義します。シングルトンメソッドはすべての既知の定数を返しますが、インスタンスメソッドは現在のクラスとその祖先で定義された定数を返します。

3
Gishu