如何在运行时find方法的定义?
我们最近遇到了一系列提交后发生后端进程失败的问题。 现在,我们都是优秀的小男孩和女孩,每次办理登机手续后都会进行rake test
,但是由于Rails图书馆的装载有些怪异,我们只能在生产模式下直接从Mongrel上运行。
我跟踪了这个bug,这是由于Rails的一个新的gem在一个String类中覆盖了一个方法,这个方法在运行时Rails代码中的一个狭隘的用法。
无论如何,长话短说,在运行时有没有办法问Ruby在哪里定义了一个方法? 像whereami( :foo )
,返回/path/to/some/file.rb line #45
? 在这种情况下,告诉我它是在String类中定义的将是无益的,因为它被一些库重载。
我不能保证源码存在于我的项目中,所以对于'def foo'
并不一定会给我所需要的东西,更不用说我有很多 def foo
的东西了,有时候我只是在运行时才知道哪一个是我的可能正在使用。
这真的很晚,但是这里是如何find一个方法的定义:
# 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) #<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类也有一些简洁的函数,比如Method#owner ,它返回定义方法的文件。
编辑:另请参阅__file__
和__line__
和稀土在其他答案的笔记,他们也很方便。 – wg
实际上你可以比上面的解决scheme进一步。 对于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 :
- ruby18_source_location
所以你可以请求这个方法:
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
基础上构build,如果没有指定方法,则返回给定方法的文件或类的第一个文件。
- rubywhere_is模块
在行动:
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上,这也会popup指定位置的编辑器。
这可能会有帮助,但你必须自己编写代码。 从博客粘贴:
Ruby提供了一个method_added()callback函数,每次在类中添加或重新定义一个方法时,都会调用该callback函数。 它是Module类的一部分,每个Class都是一个Module。 还有两个相关的callback称为method_removed()和method_undefined()。
http://scie.nti.st/2008/9/17/making-methods-immutable-in-ruby
如果你可以崩溃的方法,你会得到一个回溯,这将告诉你到底在哪里。
不幸的是,如果你不能崩溃,那么你不能找出它已经定义的地方。 如果您试图通过覆盖或覆盖该方法来使用该方法,则任何崩溃将来自覆盖或重写的方法,并且不会有任何用处。
碰撞方法的有用方法:
- 在禁止它的地方通过
nil
– 很多时候这个方法会引发一个ArgumentError
或一个nil类的永远存在的NoMethodError
。 - 如果你掌握了方法的内部知识,而且你知道方法又调用其他方法,那么你可以覆盖另一个方法,并在其中引发。
很晚回答:)但更早的答案并没有帮助我
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
你也许可以做这样的事情:
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的重新定义。 有了一些元编程,你可以把它推广到任何你想要的function。 但是它确实需要在实际重新定义的文件之前加载。
你总是可以通过使用caller()
来获得你所在的位置。
也许#source_location
可以帮助find方法来自哪里。
例如:
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]