如何找到一个方法是定义在运行时?

我们最近遇到了一个问题,在一系列提交之后,一个后端进程无法运行。现在,我们是好孩子,在每次签入后运行rake test,但是,由于Rails库加载中的一些奇怪之处,它只发生在我们直接从Mongrel在生产模式下运行它时。

我追踪了这个bug,它是由于一个新的Rails gem覆盖了String类中的一个方法,这种方法破坏了运行时Rails代码中的一个狭窄使用。

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

我不能保证源存在于我的项目中,所以对'def foo'的grepping不一定会给我我需要的东西,更不用说如果我有许多 def foo,有时直到运行时我才知道我可能会使用哪一个。

92931 次浏览

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

Ruby提供了一个method_added() 调用的回调函数 方法中添加或重新定义 类。它是Module类的一部分, 每个类都是一个模块。有 也调用了两个相关的回调 method_removed()和 method_undefined () . < / p >

< a href = " http://scie.nti。圣/ 2008/9/17 making-methods-immutable-in-ruby noreferrer“rel = > http://scie.nti.st/2008/9/17/making-methods-immutable-in-ruby < / >

如果你能让这个方法崩溃,你会得到一个回溯,它会告诉你它的确切位置。

不幸的是,如果不能使它崩溃,那么就无法找到它的定义位置。如果您试图通过重写或重写方法来操纵该方法,那么任何崩溃都将来自您所重写或重写的方法,并且它将没有任何用处。

崩溃方法的有用方法:

  1. 在禁止的地方传递nil——很多时候该方法会在nil类上引发ArgumentError或始终存在的NoMethodError
  2. 如果您有方法的内部知识,并且知道该方法反过来调用其他方法,那么您可以重写其他方法,并在其中引发。

你可以这样做:

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的所有重新定义。通过一点元编程,您可以将其泛化到任何您想要的函数。但它确实需要在实际执行重定义的文件之前加载。

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

这真的很晚了,但下面是如何找到定义方法的地方:

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]

注意,这并不适用于所有东西,比如本机编译代码。方法类也有一些简洁的函数,比如方法#所有者,它返回定义方法的文件。

编辑:也可以在另一个答案中看到__file____line__以及REE的注释,它们也很方便。——工作组

很晚才回答:)但之前的回答对我没有帮助

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

实际上,您可以比上面的解决方案更进一步。对于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:

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

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

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