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

时间:2008-10-06 18:44:25

标签: ruby-on-rails ruby runtime methods definition

我们最近遇到了一个问题,即在发生一系列提交后,后端进程无法运行。现在,我们都是优秀的小男孩和女孩,并在每次办理登机手续后都跑rake test,但是由于Rails库加载中的一些奇怪现象,它只发生在我们直接从Mongrel以生产模式运行时。

我跟踪了这​​个错误,这是因为一个新的Rails gem以一种破坏运行时Rails代码中一个狭隘用法的方式覆盖了String类中的一个方法。

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

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

10 个答案:

答案 0 :(得分:408)

这已经很晚了,但是你可以在这里找到定义方法的地方:

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

编辑:另请参阅__file____line__以及其他答案中的REE注释,它们也很方便。 - wg

答案 1 :(得分:81)

实际上你可以比上面的解决方案更进一步。对于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 :(得分:37)

我迟到了这个帖子,很惊讶没有人提到Method#owner

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

答案 3 :(得分:12)

从较新的similar question复制我的答案,为此问题添加新信息。

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

答案 4 :(得分:6)

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

  

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

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

答案 5 :(得分:6)

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

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

崩溃方法的有用方法:

  1. 通过nil禁止它的地方 - 很多时候该方法会在nil类上引发ArgumentError或永远存在的NoMethodError
  2. 如果您对该方法有内部知识,并且您知道该方法又会调用其他方法,那么您可以覆盖其他方法,并在其中进行提升。

答案 6 :(得分:5)

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

OR

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 :(得分:3)

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

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

答案 8 :(得分: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

(我只是搞砸了轨道,所以我不确切知道,但我想有一种方法可以像这样开始。)

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

答案 9 :(得分:2)

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