如何在运行时找到一个方法的定义位置?

353

我们最近遇到了一个问题,一系列提交之后,一个后端进程无法运行。我们很有良心地在每次检入后都运行了 rake test ,但由于Rails库加载中的某些奇怪问题,只有当我们直接从Mongrel以生产模式运行它时才会发现此问题。

我追踪到这个bug的原因是一个新的Rails gem在 String 类中重写了一个方法,导致在运行时的Rails代码中出现了一些狭窄的使用问题。

总之,长话短说,是否有一种在运行时询问Ruby方法定义位置的方法?类似于 whereami(:foo) ,返回 /path/to/some/file.rb line #45 的结果?在这种情况下,告诉我它是在 String 类中定义的将没有任何帮助,因为它被某个库重载了。

我无法保证源代码位于我的项目中,所以用 'def foo' 进行全局搜索并不能给我想要的结果,更不用说如果我有许多 def foo ,有时我直到运行时才知道哪一个我可能正在使用。


1
在Ruby 1.8.7中,专门添加了一种特殊的方法来查找这些信息(在1.9.3中仍然存在)...详见下面我的回答。 - Alex D
很多年前,我写了一篇文章回答了这些问题 https://blog.widefix.com/find-method-source-location/ - ka8725
11个回答

453

虽然有点晚了,但是这里介绍一下如何找到一个方法被定义的位置:

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

编辑:还可以查看另一个答案中关于REE的__file____line__的注释,它们也很有用。-- wg


1
source_location 看起来在使用 activesupport-2.3.14 的 1.8.7-p334 上工作。 - Jeff Maass
找到方法后,尝试使用该方法的 owner 方法。 - Juguang
1
2.method(:crime) 中的数字二代表什么? - stack1
1
Fixnum类的一个实例 - user2713480
2
重要提示:这将不会从method_missing中提取任何动态定义的方法。因此,如果您有一个带有class_evaldefine_method的模块或祖先类在method_missing内部,则此方法将无法工作。 - cdpalmer
显示剩余4条评论

87

你实际上可以比上面的解决方案更进一步。对于 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可用(感谢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]

2
我在任何Method类实例上都会得到“NoMethodError: undefined method”错误,例如:method(:method).__file____file____line__都未定义。 - Vikrant Chaudhary
你使用的 Ruby 版本是哪个? - James Adam
ruby 1.8.7 (2010-06-23 patchlevel 299) [x86_64-linux] - Vikrant Chaudhary
21
在 Ruby 1.9 中,m.__file__m.__line__ 已被替换为 m.source_location。需要翻译的内容已完成。 - Jonathan Tran
source_location 可用于 Ruby 1.9 及以上版本,包括 2.1。 - James Adam

39

我来晚了,很惊讶没人提到Method#owner

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

2
我很惊讶你是第一个明确引用Method类的人。另一个在1.9中引入的不太知名的宝藏是Method#parameters - fny

15

以下是我从一个较新的类似问题中提取的答案。

Ruby 1.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不提供内置的支持,但有一个很好的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电脑上,这也会在指定位置弹出编辑器。


9
也许 #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]

7

这可能有所帮助,但您需要自己编写代码。以下是从博客中粘贴的内容:

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

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


6
如果你能使方法崩溃,你将获得一个回溯(backtrace),它会告诉你它在哪里定义的。
不幸的是,如果你不能让它崩溃,那么你就找不到它被定义的地方。如果你试图通过覆盖或重写方法来搞破坏,那么任何崩溃都将来自于你覆盖或重写的方法,这没有任何用处。
让方法崩溃的有用方法:
  1. 在禁止使用nil的地方使用nil- 大多数情况下,该方法会在nil类上引发ArgumentError或永远存在的NoMethodError
  2. 如果你对该方法有内部知识,并且你知道该方法反过来调用了其他方法,那么你可以覆盖另一个方法,并在其中引发错误。

如果您可以访问代码,那么您可以轻松地在您想要插入的位置中插入 require 'ruby-debug'; debugger - the Tin Man
很遗憾,如果你无法使其崩溃,那么你就无法找出它在哪里被定义。这并不是真的。请查看其他答案。 - Martin T.

4

非常晚的回答 :) 但之前的回答并没有帮助我

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

你为什么要传递 nil - Arup Rakshit
从文档中@ArupRakshit:「如果参数为nil,则将proc设置为跟踪处理程序,或禁用跟踪。」 - tig

3
您可以通过使用caller()来获取您所在位置的回溯信息。

1
这对于查找调用者很有用,但在尝试查找定义位置时并不好。 - the Tin Man

3
你可能可以像这样做:
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的重新定义。通过一些元编程,您可以将其泛化为任何所需功能。但它确实需要在实际进行重新定义的文件之前加载。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接