実行時にメソッドが定義されている場所を見つけるにはどうすればよいですか?
-
05-07-2019 - |
質問
最近、一連のコミットが発生した後にバックエンド プロセスが実行に失敗するという問題が発生しました。さて、私たちは良い男の子と女の子でした、そして走った rake test
ただし、Rails のライブラリの読み込みにいくつかの奇妙な点があるため、本番モードで Mongrel から直接実行した場合にのみ発生しました。
私がバグを突き止めたところ、新しい Rails gem が String クラスのメソッドを上書きし、ランタイム Rails コードの 1 つの狭い用途を壊すことが原因でした。
とにかく、簡単に言うと、実行時にメソッドがどこに定義されているかを Ruby に問い合わせる方法はあるのでしょうか?何かのようなもの whereami( :foo )
それは戻ってきます /path/to/some/file.rb line #45
?この場合、何らかのライブラリによってオーバーロードされているため、クラス String で定義されていると伝えても役に立ちません。
ソースがプロジェクト内に存在するかどうかは保証できないので、grep してください。 'def foo'
必要なものを必ずしも与えてくれるわけではありません。 多くの def foo
、実行時までどれを使用しているかわからないことがあります。
解決
これは本当に遅いですが、メソッドが定義されている場所を見つける方法は次のとおりです。
# 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
他のヒント
実際には、上記のソリューションよりも少し先に進むことができます。 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]
このスレッドに遅れて来て、 Method#owner
に言及している人がいないことに驚いています。
class A; def hello; puts "hello"; end end
class B < A; end
b = B.new
b.method(:hello).owner
=> A
新しい回答からの回答をコピーします 同様の質問 これにより、この問題に新しい情報が追加されます。
ルビー 1.9 というメソッドがあります ソースの場所:
このメソッドを含む Ruby ソース ファイル名と行番号を返すか、このメソッドが Ruby で定義されていない場合は nil を返します (つまり、ネイティブ)
これはバックポートされました 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 は組み込みのサポートを提供していませんが、それに基づいて構築される優れた Gist があります。 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 では、指定した場所にエディターもポップアップ表示されます。
これは役立つかもしれませんが、自分でコーディングする必要があります。ブログから貼り付けました:
Rubyはmethod_added()を提供します 毎回呼び出されるコールバック メソッドが追加または再定義されます クラス。 Moduleクラスの一部であり、 そして、すべてのクラスはモジュールです。がある 呼び出される2つの関連コールバックも method_removed()および method_undefined()。
http://scie.nti.st / 2008/9/17 / making-methods-immutable-in-ruby
メソッドをクラッシュできる場合、バックトレースが表示され、正確な場所がわかります。
残念ながら、クラッシュできない場合は、どこで定義されているかを見つけることができません。メソッドを上書きまたは上書きすることでメソッドを操作しようとすると、上書きまたは上書きされたメソッドからクラッシュが発生し、使用できなくなります。
メソッドをクラッシュさせる便利な方法:
- パス
nil
でそれを禁止します-多くの場合、メソッドはArgumentError
または常に存在するNoMethodError
をnilクラス。 - メソッドの内部知識があり、そのメソッドが他のメソッドを呼び出していることがわかっている場合は、他のメソッドを上書きして、その内部で発生させることができます。
#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]
または
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]
非常に遅い回答:)しかし、以前の回答は私を助けませんでした
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
次のようなことができるかもしれません:
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
(レールをめちゃくちゃにしただけなので、正確にはわかりませんが、このように起動する方法があると思います。)
これにより、String#fooのすべての再定義が表示されます。少しのメタプログラミングで、必要な機能に合わせて一般化できます。ただし、実際に再定義を行うファイルの前にロードする必要があります。
caller()
を使用することにより、現在地のバックトレースをいつでも取得できます。