如何在运行时find方法的定义?

我们最近遇到了一系列提交后发生后端进程失败的问题。 现在,我们都是优秀的小男孩和女孩,每次办理登机手续后都会进行rake test ,但是由于Rails图书馆的装载有些怪异,我们只能在生产模式下直接从Mongrel上运行。

我跟踪了这​​个bug,这是由于Rails的一个新的gem在一个String类中覆盖了一个方法,这个方法在运行时Rails代码中的一个狭隘的用法。

无论如何,长话短说,在运行时有没有办法问Ruby在哪里定义了一个方法? 像whereami( :foo ) ,返回/path/to/some/file.rb line #45 ? 在这种情况下,告诉我它是在String类中定义的将是无益的,因为它被一些库重载。

我不能保证源码存在于我的项目中,所以对于'def foo'并不一定会给我所需要的东西,更不用说我有很多 def foo的东西了,有时候我只是在运行时才知道哪一个是我的可能正在使用。

这真的很晚,但是这里是如何find一个方法的定义:

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) #<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类也有一些简洁的函数,比如Method#owner ,它返回定义方法的文件。

编辑:另请参阅__file____line__和稀土在其他答案的笔记,他们也很方便。 – wg

实际上你可以比上面的解决scheme进一步。 对于Ruby 1.8企业版, 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 (感谢Jonathan!):

 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 

复制我的答案从一个新的类似的问题 ,添加新的信息,这个问题。

Ruby 1.9有一个名为source_location的方法:

返回包含此方法的Ruby源文件名和行号,如果此方法未在Ruby中定义,则返回nil(即本机)

这个gem已经回到了1.8.7

  • ruby18_source_location

所以你可以请求这个方法:

 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基础上构build,如果没有指定方法,则返回给定方法的文件或类的第一个文件。

  • rubywhere_is模块

在行动:

 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上,这也会popup指定位置的编辑器。

这可能会有帮助,但你必须自己编写代码。 从博客粘贴:

Ruby提供了一个method_added()callback函数,每次在类中添加或重新定义一个方法时,都会调用该callback函数。 它是Module类的一部分,每个Class都是一个Module。 还有两个相关的callback称为method_removed()和method_undefined()。

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

如果你可以崩溃的方法,你会得到一个回溯,这将告诉你到底在哪里。

不幸的是,如果你不能崩溃,那么你不能找出它已经定义的地方。 如果您试图通过覆盖或覆盖该方法来使用该方法,则任何崩溃将来自覆盖或重写的方法,并且不会有任何用处。

碰撞方法的有用方法:

  1. 在禁止它的地方通过nil – 很多时候这个方法会引发一个ArgumentError或一个nil类的永远存在的NoMethodError
  2. 如果你掌握了方法的内部知识,而且你知道方法又调用其他方法,那么你可以覆盖另一个方法,并在其中引发。

很晚回答:)但更早的答案并没有帮助我

 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的重新定义。 有了一些元编程,你可以把它推广到任何你想要的function。 但是它确实需要在实际重新定义的文件之前加载。

你总是可以通过使用caller()来获得你所在的位置。

也许#source_location可以帮助find方法来自哪里。

例如:

 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]