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


328

我们最近遇到了一个问题,即在进行了一系列提交之后,后端进程无法运行。现在,我们是个不错的男孩和女孩,rake test每次登记后都跑了,但由于Rails的库加载有些奇怪,这种情况仅在我们以生产模式直接从Mongrel进行运行时发生。

我跟踪了该错误,这是由于一个新的Rails gem覆盖了String类中的一种方法,从而打破了运行时Rails代码中的一种狭义用法。

总之,长话短说,在运行时是否有办法询问Ruby在哪里定义了方法?这样的东西whereami( :foo )回来了/path/to/some/file.rb line #45吗?在这种情况下,告诉我它在String类中定义是没有帮助的,因为它被某些库重载了。

我不能保证源代码存在于我的项目中,因此grepping 'def foo'并不一定能提供我所需要的东西,更不用说我是否有很多东西 def foo,有时我直到运行时才知道我会使用哪个。


1
在Ruby 1.8.7中,专门添加了一种特殊方法来查找此信息(并且在1.9.3中仍然存在)...详细信息请参见下面的答案。
Alex D

Answers:


419

这确实很晚,但是您可以通过以下方法找到方法的定义位置:

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]

请注意,这不适用于所有内容,例如本机编译代码。该方法类有一些巧妙的功能,太像方法#主人,返回的定义方法的文件。

编辑:在其他答案中也请参阅REE 的__file____line__和注释,它们也很方便。-WG


1
使用activesupport-2.3.14的source_location似乎适用于1.8.7-p334
Jeff Maass

找到方法后,请尝试方法的owner方法
Juguang 2013年

1
第二是什么2.method(:crime)
stack1

1
该类的一个实例Fixnum
kitteehh,2015年

1
重要说明:这不会从中提取任何动态定义的方法method_missing。所以,如果你有一个模块或祖先类与class_evaldefine_method内部method_missing,则此方法将无法正常工作。
cdpalmer

83

实际上,您可以比上述解决方案更进一步。对于Ruby 1.8 Enterprise Edition,实例上有__file____line__方法Method

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
我得到“NoMethodError:未定义的方法”两种__file__,并__line__在任何Method类的实例,例如:method(:method).__file__
Vikrant Chaudhary的

您有哪个版本的红宝石?
詹姆斯·亚当,

红宝石1.8.7(2010-06-23补丁程序299)[x86_64-linux]
Vikrant Chaudhary

19
在Ruby 1.9上,m.__file__m.__line__已替换为m.source_location
Jonathan Tran

Ruby 2.1呢?
罗克什

38

我迟到了这个话题,很惊讶没有人提及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 2012年

13

从较新的类似问题中复制我的答案,从而为该问题添加新信息。

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


7

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

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

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


6

如果您可以使该方法崩溃,则将获得回溯,该回溯将告诉您确切的位置。

不幸的是,如果您无法崩溃它,那么您将无法找到它的定义位置。如果您尝试通过覆盖或覆盖方法来使用该方法,那么崩溃将来自您被覆盖或覆盖的方法,将不会有任何用处。

崩溃方法的有用方法:

  1. nil在禁止的地方传递-在很多时候,该方法会在nil类上引发一个ArgumentError或永远存在NoMethodError的错误。
  2. 如果您具有该方法的内部知识,并且知道该方法又调用了其他方法,则可以覆盖另一个方法,并在其中进行提高。

如果你有机会获得代码,你可以很容易地插入require 'ruby-debug'; debugger 你的代码,你想在下降。
铁皮人

“不幸的是,如果您无法崩溃它,那么您将无法找到它的定义位置。” 是不对的。其他答案。
Martin T.

6

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

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
2014年

@ArupRakshit来自文档:«将proc建立为跟踪处理程序,如果参数为,则禁用跟踪nil。»
tig 2014年

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

(我只是把铁轨弄得一团糟,所以我不清楚,但我想有一种方法可以像这样启动它。)

这将向您显示对String#foo的所有重新定义。只需进行一些元编程,就可以将其概括为所需的任何功能。但是它确实需要在实际进行重新定义的文件之前加载。


3

您始终可以使用来回溯您所处的位置caller()


1
这对于查找调用您的内容很有用,但在尝试查找定义内容的位置时效果不好。
锡人
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.