关于ruby on rails:如何找到在运行时定义方法的位置?

How to find where a method is defined at runtime?

我们最近遇到了一个问题,在发生一系列提交之后,后端进程无法运行。现在,我们都是好孩子,每次登记入住后都会运行rake test,但是,由于Rails的库装载有点奇怪,只有在生产模式下直接从Mongrel运行时才会发生这种情况。

我跟踪了这个bug,这是由于一个新的rails gem在string类中重写了一个方法,从而打破了运行时rails代码中的一个狭隘用法。

总之,长话短说,有没有一种方法,在运行时,问ruby在哪里定义了一个方法?像whereami( :foo )那样返回/path/to/some/file.rb line #45的东西?在本例中,告诉我它是在类字符串中定义的,这是没用的,因为它被某些库重载了。

我不能保证源代码在我的项目中存在,所以对'def foo'进行grepping并不一定能满足我的需要,更不用说如果我有很多def foo,有时我直到运行时才知道我可能使用哪一个。


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

http://gist.github.com/76951

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 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>

如果您使用的是Ruby1.9+,则可以使用source_location

1
2
3
4
5
6
7
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的注释,它们也很方便。--WG


实际上,您可以比上面的解决方案更进一步。对于Ruby1.8企业版,在Method实例上有__file____line__方法:

1
2
3
4
5
6
7
8
9
10
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

对于Ruby1.9及更高版本,有source_location(谢谢Jonathan!):

1
2
3
4
5
6
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

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


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

Ruby1.9有一个名为source_location的方法:

Returns the Ruby source filename and line number containing this method or nil if this method was not defined in Ruby (i.e. native)

此GEM已将其恢复为1.8.7:

  • 18卢布来源地

因此,您可以请求使用以下方法:

1
m = Foo::Bar.method(:create)

然后要求使用该方法的source_location

1
m.source_location

这将返回一个带有文件名和行号的数组。例如,对于ActiveRecord::Base#validates而言,该报表:

1
2
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不提供内置支持,但有一个很好的要点,它基于source_location来返回给定方法的文件,如果没有指定方法,则返回类的第一个文件:

  • ruby哪里是模块

行动中:

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


如果你能使这个方法崩溃,你会得到一个回溯,它会准确地告诉你它在哪里。

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

碰撞方法的有用方法:

  • 在禁止的地方传递nil—很多时候,该方法会在nil类上引发ArgumentError或一直存在的NoMethodError
  • 如果您对该方法有内部知识,并且您知道该方法依次调用其他方法,那么您可以重写其他方法,并在其中进行提升。

  • 这可能会有所帮助,但您必须自己编写代码。从日志中粘贴:

    Ruby provides a method_added()
    callback that is invoked every time a
    method is added or redefined within a
    class. It’s part of the Module class,
    and every Class is a Module. There are
    also two related callbacks called
    method_removed() and
    method_undefined().

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


    也许#source_location可以帮助找到方法的来源。

    前任:

    1
    ModelName.method(:has_one).source_location

    返回

    1
    [project_path/vendor/ruby/version_number/gems/activerecord-number/lib/active_record/associations.rb", line_number_of_where_method_is]

    1
    ModelName.new.method(:valid?).source_location

    返回

    1
    [project_path/vendor/ruby/version_number/gems/activerecord-number/lib/active_record/validations.rb", line_number_of_where_method_is]

    很晚的回答:)但是更早的回答对我没有帮助。

    1
    2
    3
    4
    5
    6
    set_trace_func proc{ |event, file, line, id, binding, classname|
      printf"%8s %s:%-2d %10s %8s
    "
    , event, file, line, id, classname
    }
    # call your method
    set_trace_func nil


    通过使用caller(),您总是可以得到您所处位置的回溯。


    您可以这样做:

    FuffyField.Rb:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     class String
       def String.method_added(name)
         if (name==:foo)
            puts"defining #{name} in:
    \t"

            puts caller.join("
    \t"
    )
         end
       end
     end

    然后确保foo-finder首先加载了

    1
    ruby -r foo_finder.rb railsapp

    (我只弄乱了铁轨,所以我不太清楚,但我想有一种方法可以像这样启动它。)

    这将显示字符串foo的所有重新定义。通过一点元编程,您可以将它概括为您想要的任何函数。但它确实需要在执行重新定义的文件之前加载。