提问



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


我追踪了这个错误,这是因为一个新的Rails gem在一个方法中覆盖了一个方法,该方法打破了运行时Rails代码中的一个狭隘用法。


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


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

最佳参考


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


http://gist.github.com/76951[38]


# 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 [39]


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]


请注意,这不会对所有内容起作用,就像本机编译的代码一样.Scope类也有一些简洁的函数,比如Method#owner,它返回定义方法的文件。[40] [41]


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

其它参考1


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


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

其它参考3


从一个更新的类似问题复制我的答案,为这个问题增加新的信息。


Ruby 1.9 有一个名为source_location的方法:[43]



  返回包含此方法的Ruby源文件名和行号,如果未在Ruby中定义此方法,则返回nil(即本机)



这个gem被反向移植到 1.8.7 :



  • ruby​​18_source_location



所以你可以要求这个方法:[44]


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构建,如果没有指定方法,则返回给定方法的文件或类的第一个文件:



  • ruby​​ where_is module



在行动:[45]


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


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



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



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

其它参考5


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


不幸的是,如果你不能崩溃,那么你就找不到它的定义。如果你试图通过覆盖或覆盖它来使用该方法,那么任何崩溃都将来自你的覆盖或覆盖方法,并且它不会有任何用处。


崩溃方法的有用方法:



  1. 传递nil禁止它的地方 - 很多时候该方法会在零级别上提出ArgumentError或永远存在NoMethodError

  2. 如果你对该方法有内在的了解,并且你知道该方法又会调用其他方法,那么你可以覆盖其他方法,然后在其中加注。


其它参考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]

其它参考7


答案很晚:)但早期的答案并没有帮助我


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


你可以做这样的事情:


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


您可以使用caller()随时获取您所在位置的回溯。