特定のクラスのClassオブジェクトへの参照があります。クラスのファイルパスを見つける必要があります。それが助けになるなら、クラスはActiveRecordから継承します。
これを行う簡単な方法はありますか?
ありがとう。
特定のクラスのClassオブジェクトへの参照があります。クラスのファイルパスを見つける必要があります。それが助けになるなら、クラスはActiveRecordから継承します。
これを行う簡単な方法はありますか?
ありがとう。
source_location
そのメソッドで使用します:
YourClass.instance_methods(false).map { |m|
YourClass.instance_method(m).source_location.first
}.uniq
メソッドはさまざまな場所で定義されている可能性があるため、複数の場所を取得する可能性があります。
すべてのクラス定義で機能するこれを行う方法はありません。いくつかの場合に機能するいくつかの簡単な方法があり、他の場合に機能するいくつかの複雑な方法があります。
Rubyでは、クラスとモジュールを再度開いて、追加の定義(モンキーパッチ)を何度も実行できます。クラスまたはモジュールのプライマリ定義の組み込み概念はありません。また、クラスの定義に寄与するすべてのファイルを一覧表示する組み込みの方法はありません。ただし、クラス内のメソッドを定義するファイルを一覧表示する組み込みの方法があります。他のコンポーネント(定数、宣言など)に寄与する静的定義を見つけるには、既知の規則(該当する場合)に従うか、静的ソースコード分析を適用します。
Rubyメソッドには、1つの場所に1つの定義しかありません。これは、を介して決定できますMethod#source_location
。Class#instance_methods
クラスまたはモジュールのインスタンスメソッドは、およびそのスコープ(、、、public_
およびprotected_
)private_
バリアントを介して(シンボルとして)リストできます。シングルトンメソッド(別名クラスメソッド)は、を介して一覧表示できますClass#singleton_methods
。これらのメソッドに最初の引数として渡すfalse
と、祖先から継承されたメソッドが省略されます。これらのシンボルの1つから、対応するMethod
viaを取得しClass#instance_method
、を使用Method#source_location
してメソッドのファイルと行番号を取得できます。これは、静的に(を使用して)または動的に(と組み合わせてdef
などのさまざまな手段を使用して)定義されたメソッドに対して機能します。Module#class_eval
Module#define_method
たとえば、モジュールM
とクラスを定義する次のファイルについて考えてみますC
。
/tmp/m.rb
module M
def self.extended(klass)
klass.class_eval do
define_method(:ifoo) do
'ifoo'
end
define_singleton_method(:cfoo) do
'cfoo'
end
end
end
def has_ibar
self.class_eval do
define_method(:ibar) do
'ibar'
end
end
end
def has_cbar
self.class_eval do
define_singleton_method(:cbar) do
'cbar'
end
end
end
end
/tmp/c.rb
require_relative 'm'
class C
extend M
has_ibar
has_cbar
def im
'm'
end
def self.cm
'cm'
end
end
/tmp/c_ext.rb
class C
def iext
'iext'
end
def self.cext
'cext'
end
end
これらの定義が与えられると、次のPryセッションが示すように、クラスを調べてそのソースファイルを見つけることができます。
2.4.0 (main):0 > require '/tmp/c'; require '/tmp/c_ext';
2.4.0 (main):0 > instance_methods_syms = C.instance_methods(false)
=> [:im, :ifoo, :ibar, :iext]
2.4.0 (main):0 > class_methods_syms = C.singleton_methods(false)
=> [:cm, :cfoo, :cbar, :cext]
2.4.0 (main):0 > instance_methods_locs = instance_methods_syms.map { |method_sym| C.instance_method(method_sym).source_location }
=> [["/tmp/c.rb", 9], ["/tmp/m.rb", 4], ["/tmp/m.rb", 16], ["/tmp/c_ext.rb", 2]]
2.4.0 (main):0 > class_methods_locs = class_methods_syms.map { |method_sym| C.singleton_class.instance_method(method_sym).source_location }
=> [["/tmp/c.rb", 13], ["/tmp/m.rb", 8], ["/tmp/m.rb", 24], ["/tmp/c_ext.rb", 6]]
2.4.0 (main):0 > methods_locs = instance_methods_locs + class_methods_locs;
2.4.0 (main):0 > class_files = methods_locs.map(&:first).uniq
=> ["/tmp/c.rb", "/tmp/m.rb", "/tmp/c_ext.rb"]
によって返される値の順序はModule#instance_methods
ドキュメントで指定されておらず、Rubyのバージョンによって異なります。
Module#instance_methods
を介して取得された複数の候補ファイルの中からクラスのプライマリファイルを識別することMethod#source_location
は、単純な問題ではありません。一般的な場合、それは不可能です。
上記の例では、はを定義する最初のdファイルである/tmp/c.rb
ため、直感的にプライマリファイルです。おそらくこれが、Ruby 2.3.3および2.4.0で、そのメソッドを最初にリストする理由です。ただし、前述のように、順序は文書化されておらず、Rubyのバージョンによって異なります。で定義された最初のメソッドは、実行順に。であることに注意してください。ちなみに、Ruby 1.9.3から2.2.6では、の最初の項目はです。したがって、最初の項目は、誰もが直感的にのプライマリファイルと見なすものではないことは明らかです。require
C
Module#instance_methods
C
#ifoo
instance_methods_syms
:ifoo
class_files
/tmp/m.rb
C
さらに、からメソッド定義を削除し、宣言型の呼び出し、、、および/tmp/c.rb
を残した場合にどうなるかを検討してください。この場合、は完全に存在しません。これは非現実的なシナリオではありません。たとえば、Active Recordでは、単純なモデルクラスの主要な定義は、検証とその他の宣言のみで構成され、その他はすべてフレームワークに任されている場合があります。この定義は、クラスのメソッドの場所を調べても見つかりません。extend M
has_ibar
has_cbar
/tmp/c.rb
class_files
show-source
Pryのshow-source
(別名$
)コマンドは、このアプローチの変形を使用して、メソッドとクラス定義ファイルの検査と並べ替えに独自のロジックを適用します。興味があるかどうかを確認Pry::WrappedModule
してください。Pry::Method
実際にはかなりうまく機能しますが、に依存しているため、Method#source_location
メソッドを定義しないクラス定義を見つけることができません。
このアプローチは、検査対象のクラスがいくつかの明確に定義された規則に従って定義されているシナリオにのみ適用されます。検査しているクラスがそのような規則に従っていることがわかっている場合は、それを使用して、その主要な定義を確実に見つけることができます。
このアプローチは、メソッドの場所のアプローチが失敗した場合、つまり、プライマリ定義にメソッド定義が含まれていない場合でも機能します。ただし、明確に定義された規則に従うクラス定義に限定されます。
単純なRailsアプリケーションでは、アプリケーションのモデルクラスはそのapp/models
ディレクトリ内で定義され、ファイルパスはクラス名から決定論的に導出できます。このようなモデルクラスklass
がある場合、そのプライマリ定義を含むファイルは次の場所にあります。
Rails.root.join('app', 'models', "#{klass.name.underscore}.rb").to_s
たとえば、モデルクラスProductWidget
はで定義されます。APP_ROOT/app/models/product_widget.rb
ここAPP_ROOT
で、はアプリケーションのルートディレクトリパスです。
これを一般化するには、単純なRails構成の拡張を検討する必要があります。モデル定義のカスタムパスを定義するRailsアプリケーションでは、それらすべてを考慮する必要があります。また、アプリケーションによってロードされた任意のRailsエンジンで任意のモデルクラスを定義できるため、カスタムパスを考慮して、ロードされたすべてのエンジンも調べる必要があります。次のコードは、これらの考慮事項を組み合わせたものです。
candidates = Rails.application.config.paths['app/models'].map do |model_root|
Rails.root.join(model_root, "#{klass.name.underscore}.rb").to_s
end
candidates += Rails::Engine::Railties.engines.flat_map do |engine|
engine.paths['app/models'].map do |model_root|
engine.root.join(model_root, "#{klass.name.underscore}.rb").to_s
end
end
candidates.find { |path| File.exist?(path) }
この例は特にRailsモデルに適用されますが、定義の場所がRailsの規則と構成に従うコントローラーやその他のクラスに簡単に適合させることができます。
一部のクラスはRailsアプリケーションで自動ロードされますが、パスがRailsパス構成に登録されている標準カテゴリー(モデル、コントローラーなど)の1つに属するものとして決定論的に識別できません。それでも、そのようなクラスのプライマリ定義を含むファイルを決定論的に識別することは可能です。解決策は、Railsで使用される一般的な自動負荷解決アルゴリズムを実装することです。このような実装の例は、この回答の範囲を超えています。
他のアプローチが適用できないか不十分な場合は、ブルートフォースアプローチに頼ろうとするかもしれません。ロードされたすべてのRubyソースファイルで特定のクラスの定義を探します。残念ながら、これは制限があり複雑です。
を使用してロードされるファイルKernel#require
はにリストされている$LOADED_FEATURES
ため、パスの配列でクラスの定義を含むRubyファイルを検索できます。ただし、を使用してロードされたファイルは、Kernel#load
必ずしもどこにもリストされていないため、検索できません。config.cache_classes
これに対する1つの例外は、false(開発モードのデフォルト)のときにRails自動ロードメカニズムを介してロードされるファイルです。この場合、回避策があります。Railsの自動ロードパスを検索します。効率的な検索は、Railsの自動負荷解決アルゴリズムに従いますが、を介して取得できるすべての自動負荷パスを検索することでも十分Rails.application.send(:_all_autoload_paths)
です。
リストできるクラス定義ファイルの場合でも、特定のクラスの定義を識別することは簡単ではありません。ルート名前空間のステートメントで定義されているクラスの場合class
、これは簡単です。に一致する行を見つけます/^\s*class\s+#{klass}[\s$]/
。ただし、定義が本体にネストされてmodule
いるクラスの場合、またはを使用して動的に定義されているクラスの場合Class::new
、これには、各ファイルを抽象構文ツリー(AST)に解析し、ツリーでそのような定義を検索する必要があります。他のクラスジェネレーターを使用して定義されたクラスの場合、AST検索でそのジェネレーターを認識する必要があります。このような実装ではディスクから多くのファイルを読み取る必要があることを考慮すると、複数のクラス定義ルックアップを実行することが目的の場合は、検出されたすべてのクラス定義をキャッシュするのが賢明です。このような実装は、この回答の範囲を超えています。
明確に定義された規則に従わないファイル内のクラス定義の場合、このアプローチが最も徹底的なアプローチです。ただし、実装は複雑で、ロードされたすべてのソースファイルの読み取りと解析が必要であり、基本的に制限されています。
Railsモデルを参照していて、デフォルトの構成を採用している場合は、アプリモデルフォルダーにあるはずであり、次のようにパスを取得できます。
File.join Rails.root,"app","models", "#{self.class.name.to_s.underscore}.rb"