ruby-on-rails - rails教學 - ruby on rails缺點




如何在運行時找到方法的定義? (7)

我們最近出現了一個問題,在一系列提交發生後,後端進程無法運行。 現在,我們都是優秀的小男孩和女孩,每次辦理登機手續後都會進行rake test ,但由於Rails圖書館加載中存在一些問題,只有當我們在生產模式下直接從Mongrel運行它時才會發生這種情況。

我跟踪了這個bug,這是由於Rails的一個新的gem在Struts類中覆蓋了一個方法,這種方式打破了運行時Rails代碼中的一個狹窄用法。

無論如何,長話短說,在運行時有沒有辦法問Ruby在哪裡定義了一個方法? 像whereami( :foo )那樣返回/path/to/some/file.rb line #45 ? 在這種情況下,告訴我它是在String類中定義的,將無濟於事,因為它被某個庫重載。

我不能保證源代碼存在於我的項目中,所以對'def foo'挑剔不一定會給我所需要的東西,更不用說我是否有很多 def foo的東西,有時我只是在運行時才知道哪一個是我的可能正在使用。


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

你也許可以做這樣的事情:

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的所有重新定義。 有了一些元編程,你可以將它推廣到任何你想要的功能。 但它確實需要在實際重新定義的文件之前加載。


如果你可以使方法崩潰,你會得到一個回溯,它會告訴你它到底在哪裡。

不幸的是,如果你不能崩潰,那麼你無法找到它的定義。 如果您試圖通過覆蓋或覆蓋該方法來使用該方法,那麼任何崩潰都會來自您的覆蓋或重寫方法,並且不會有任何用處。

崩潰方法的有用方法:

  1. 通過nil禁止它的位置 - 很多時候該方法會引發ArgumentError或在無類的情況下永遠存在的NoMethodError
  2. 如果你對方法有深入的了解,並且你知道該方法又調用其他方法,那麼你可以重寫另一種方法,並在其中引發。

實際上你可以比上面的解決方案做得更進一步。 對於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]

從更新的類似問題複製我的答案,為此問題添加新信息。

Ruby 1.9有一個名為source_location方法:

返回包含此方法的Ruby源文件名和行號,如果此方法未在Ruby中定義(例如,本地),則返回nil

這個寶石已經回到了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/[email protected]/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/[email protected]/gems/activemodel-3.2.2/lib/active_model/validations/validates.rb", 81]

在安裝了TextMate的Mac上,這也會彈出指定位置的編輯器。


我遲到了這個線程,並且很驚訝沒有人提及Method#owner

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

這真的很晚,但您可以通過以下方式找到定義方法的位置:

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)
#<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__和其他答案中的REE註釋,它們也很方便。 - wg





definition