19

特定のクラスのClassオブジェクトへの参照があります。クラスのファイルパスを見つける必要があります。それが助けになるなら、クラスはActiveRecordから継承します。

これを行う簡単な方法はありますか?

ありがとう。

4

3 に答える 3

38

source_locationそのメソッドで使用します:

YourClass.instance_methods(false).map { |m| 
  YourClass.instance_method(m).source_location.first
}.uniq

メソッドはさまざまな場所で定義されている可能性があるため、複数の場所を取得する可能性があります。

于 2012-05-02T02:58:35.153 に答える
7

すべてのクラス定義で機能するこれを行う方法はありません。いくつかの場合に機能するいくつかの簡単な方法があり、他の場合に機能するいくつかの複雑な方法があります。

Rubyでは、クラスとモジュールを再度開いて、追加の定義(モンキーパッチ)を何度も実行できます。クラスまたはモジュールのプライマリ定義の組み込み概念はありません。また、クラスの定義に寄与するすべてのファイルを一覧表示する組み込みの方法はありません。ただし、クラス内のメソッドを定義するファイルを一覧表示する組み込みの方法があります。他のコンポーネント(定数、宣言など)に寄与する静的定義を見つけるには、既知の規則(該当する場合)に従うか、静的ソースコード分析を適用します。

1.メソッドの場所の検査

Rubyメソッドには、1つの場所に1つの定義しかありません。これは、を介して決定できますMethod#source_locationClass#instance_methodsクラスまたはモジュールのインスタンスメソッドは、およびそのスコープ(、、、public_およびprotected_private_バリアントを介して(シンボルとして)リストできます。シングルトンメソッド(別名クラスメソッド)は、を介して一覧表示できますClass#singleton_methods。これらのメソッドに最初の引数として渡すfalseと、祖先から継承されたメソッドが省略されます。これらのシンボルの1つから、対応するMethodviaを取得しClass#instance_method、を使用Method#source_locationしてメソッドのファイルと行番号を取得できます。これは、静的に(を使用して)または動的に(と組み合わせてdefなどのさまざまな手段を使用して)定義されたメソッドに対して機能します。Module#class_evalModule#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のバージョンによって異なります。

1.1プライマリファイルの識別

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では、の最初の項目はです。したがって、最初の項目は、誰もが直感的にのプライマリファイルと見なすものではないことは明らかです。requireCModule#instance_methodsC#ifooinstance_methods_syms:ifooclass_files/tmp/m.rbC

さらに、からメソッド定義を削除し、宣言型の呼び出し、、、および/tmp/c.rbを残した場合にどうなるかを検討してください。この場合、は完全に存在しません。これは非現実的なシナリオではありません。たとえば、Active Recordでは、単純なモデルクラスの主要な定義は、検証とその他の宣言のみで構成され、その他はすべてフレームワークに任されている場合があります。この定義は、クラスのメソッドの場所を調べても見つかりません。extend Mhas_ibarhas_cbar/tmp/c.rbclass_files

1.2。詮索好きコマンドshow-source

Pryのshow-source(別名$)コマンドは、このアプローチの変形を使用して、メソッドとクラス定義ファイルの検査と並べ替えに独自のロジックを適用します。興味があるかどうかを確認Pry::WrappedModuleしてください。Pry::Method実際にはかなりうまく機能しますが、に依存しているため、Method#source_locationメソッドを定義しないクラス定義を見つけることができません。

2.次の規則

このアプローチは、検査対象のクラスがいくつかの明確に定義された規則に従って定義されているシナリオにのみ適用されます。検査しているクラスがそのような規則に従っていることがわかっている場合は、それを使用して、その主要な定義を確実に見つけることができます。

このアプローチは、メソッドの場所のアプローチが失敗した場合、つまり、プライマリ定義にメソッド定義が含まれていない場合でも機能します。ただし、明確に定義された規則に従うクラス定義に限定されます。

2.1。コンベンション:Railsモデルクラス

単純な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の規則と構成に従うコントローラーやその他のクラスに簡単に適合させることができます。

2.2。規則:GenericRailsの自動ロード解決

一部のクラスはRailsアプリケーションで自動ロードされますが、パスがRailsパス構成に登録されている標準カテゴリー(モデル、コントローラーなど)の1つに属するものとして決定論的に識別できません。それでも、そのようなクラスのプライマリ定義を含むファイルを決定論的に識別することは可能です。解決策は、Railsで使用される一般的な自動負荷解決アルゴリズムを実装することです。このような実装の例は、この回答の範囲を超えています。

3.静的ソースコード分析

他のアプローチが適用できないか不十分な場合は、ブルートフォースアプローチに頼ろうとするかもしれません。ロードされたすべての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検索でそのジェネレーターを認識する必要があります。このような実装ではディスクから多くのファイルを読み取る必要があることを考慮すると、複数のクラス定義ルックアップを実行することが目的の場合は、検出されたすべてのクラス定義をキャッシュするのが賢明です。このような実装は、この回答の範囲を超えています。

明確に定義された規則に従わないファイル内のクラス定義の場合、このアプローチが最も徹底的なアプローチです。ただし、実装は複雑で、ロードされたすべてのソースファイルの読み取りと解析が必要であり、基本的に制限されています。

于 2017-08-11T07:38:37.713 に答える
-4

Railsモデルを参照していて、デフォルトの構成を採用している場合は、アプリモデルフォルダーにあるはずであり、次のようにパスを取得できます。

File.join Rails.root,"app","models", "#{self.class.name.to_s.underscore}.rb"
于 2012-05-01T23:35:38.470 に答える