web-dev-qa-db-ja.com

実行時にメソッドが定義されている場所を見つける方法

一連のコミットが発生した後、バックエンドプロセスの実行に失敗するという問題が最近発生しました。さて、私たちは小さな男の子で、チェックインのたびにrake testを実行しましたが、Railsのライブラリーのロードに奇妙な点があるため、実稼働モードでMongrelから直接実行した場合にのみ発生しました。

バグを追跡しましたが、それは新しいRails gemがStringクラスのメソッドをランタイムRailsコードでの1つの狭い使用を壊す方法で上書きしたためです。

とにかく、要するに、実行時に、メソッドが定義されている場所をRubyに尋ねる方法はありますか? /path/to/some/file.rb line #45を返すwhereami( :foo )のようなものこの場合、クラスStringで定義されていると言っても、ライブラリによってオーバーロードされたため、役に立ちません。

ソースがプロジェクト内に存在することを保証することはできません。したがって、'def foo'をgrepしても、必要なものが必ずしも得られません。多くdef fooがある場合はもちろん、どちらを使用するかは実行時までわかりません。

324
Matt Rogish

これは本当に遅いですが、メソッドが定義されている場所を見つける方法は次のとおりです。

http://Gist.github.com/76951

# How to find out where a method comes from.
# Learned this from Dave Thomas while teaching Advanced Ruby Studio
# Makes the case for separating method definitions into
# modules, especially when enhancing built-in classes.
module Perpetrator
  def crime
  end
end

class Fixnum
  include Perpetrator
end

p 2.method(:crime) # The "2" here is an instance of Fixnum.
#<Method: Fixnum(Perpetrator)#crime>

Ruby 1.9+を使用している場合は、 source_location を使用できます

require 'csv'

p CSV.new('string').method(:flock)
# => #<Method: CSV#flock>

CSV.new('string').method(:flock).source_location
# => ["/path/to/Ruby/1.9.2-p290/lib/Ruby/1.9.1/forwardable.rb", 180]

これは、ネイティブコンパイルされたコードのように、すべてでは機能しないことに注意してください。 メソッドクラス には、メソッドが定義されているファイルを返す Method#owner のような、きちんとした機能もあります。

編集:また、他の答えの__file____line__とREEのメモを参照してください、それらも便利です。 -wg

410
wesgarrison

実際には、上記のソリューションよりも少し先に進むことができます。 Ruby 1.8 Enterprise Editionの場合、Methodインスタンスに__file__および__line__メソッドがあります。

require 'rubygems'
require 'activesupport'

m = 2.days.method(:ago)
# => #<Method: Fixnum(ActiveSupport::CoreExtensions::Numeric::Time)#ago>

m.__file__
# => "/Users/james/.rvm/gems/ree-1.8.7-2010.01/gems/activesupport-2.3.8/lib/active_support/core_ext/numeric/time.rb"
m.__line__
# => 64

Ruby 1.9以降には、source_locationがあります(ジョナサンに感謝!):

require 'active_support/all'
m = 2.days.method(:ago)
# => #<Method: Fixnum(Numeric)#ago>    # comes from the Numeric module

m.source_location   # show file and line
# => ["/var/lib/gems/1.9.1/gems/activesupport-3.0.6/.../numeric/time.rb", 63]
81
James Adam

私はこのスレッドに遅れて来ており、誰もMethod#ownerに言及していないことに驚いています。

class A; def hello; puts "hello"; end end
class B < A; end
b = B.new
b.method(:hello).owner
=> A
37
Alex D

この問題に新しい情報を追加する新しい 類似の質問 から私の回答をコピーします。

Ruby1.9には source_location というメソッドがあります:

このメソッドを含むRubyソースファイル名と行番号を返します。このメソッドがRuby(つまりネイティブ)で定義されていない場合はnilを返します

これは、このgemによって1.8.7にバックポートされています。

そのため、メソッドをリクエストできます。

m = Foo::Bar.method(:create)

そして、そのメソッドのsource_locationを要求します:

m.source_location

これは、ファイル名と行番号を含む配列を返します。例えばActiveRecord::Base#validatesの場合、これは以下を返します:

ActiveRecord::Base.method(:validates).source_location
# => ["/Users/laas/.rvm/gems/Ruby-1.9.2-p0@arveaurik/gems/activemodel-3.2.2/lib/active_model/validations/validates.rb", 81]

クラスとモジュールの場合、Rubyは組み込みサポートを提供しませんが、source_locationに基づいて特定のメソッドのファイルまたはクラスの最初のファイルを返す優れた要点がありますメソッドが指定されました:

動作中:

where_is(ActiveRecord::Base, :validates)

# => ["/Users/laas/.rvm/gems/Ruby-1.9.2-p0@arveaurik/gems/activemodel-3.2.2/lib/active_model/validations/validates.rb", 81]

TextMateがインストールされているMacでは、指定した場所にエディターがポップアップします。

12
Laas

これは役立つかもしれませんが、自分でコーディングする必要があります。ブログから貼り付けました:

Rubyは、メソッドがクラス内で追加または再定義されるたびに呼び出されるmethod_added()コールバックを提供します。これはモジュールクラスの一部であり、すべてのクラスはモジュールです。 method_removed()およびmethod_undefined()と呼ばれる2つの関連するコールバックもあります。

http://scie.nti.st/2008/9/17/making-methods-immutable-in-Ruby

6
Ken

メソッドをクラッシュできる場合は、バックトレースが表示され、正確にどこにあるかがわかります。

残念ながら、クラッシュできない場合は、定義されている場所を見つけることができません。メソッドを上書きまたはオーバーライドすることでメソッドを操作しようとすると、上書きまたはオーバーライドされたメソッドからクラッシュが発生し、使用できなくなります。

メソッドをクラッシュさせる便利な方法:

  1. nilを禁止する場所に渡します-多くの場合、メソッドはArgumentErrorまたは常に存在しないNoMethodErrorをnilクラスで発生させます。
  2. メソッドの内部知識があり、そのメソッドが他のメソッドを呼び出すことを知っている場合は、他のメソッドを上書きして、その内部で発生させることができます。
5
Orion Edwards

#source_locationは、メソッドがどこから来たのかを見つけるのに役立つかもしれません。

例:

ModelName.method(:has_one).source_location

戻る

[project_path/vendor/Ruby/version_number/gems/activerecord-number/lib/active_record/associations.rb", line_number_of_where_method_is]

OR

ModelName.new.method(:valid?).source_location

戻る

[project_path/vendor/Ruby/version_number/gems/activerecord-number/lib/active_record/validations.rb", line_number_of_where_method_is]
5
Samda

非常に遅い回答:)しかし、以前の回答は私を助けませんでした

set_trace_func proc{ |event, file, line, id, binding, classname|
  printf "%8s %s:%-2d %10s %8s\n", event, file, line, id, classname
}
# call your method
set_trace_func nil
3
tig

caller()を使用することにより、現在地のバックトレースをいつでも取得できます。

2
Garry

次のようなことができるかもしれません:

foo_Finder.rb:

 class String
   def String.method_added(name)
     if (name==:foo)
        puts "defining #{name} in:\n\t"
        puts caller.join("\n\t")
     end
   end
 end

次に、foo_Finderが次のようなもので最初にロードされていることを確認します

Ruby -r foo_Finder.rb railsapp

(私はRailsを台無しにしているだけなので、正確にはわかりませんが、これを開始する方法があると思います。)

これにより、String#fooのすべての再定義が表示されます。少しのメタプログラミングで、必要な機能に合わせて一般化できます。ただし、実際に再定義を行うファイルの前にロードする必要があります。

2
AShelly