Как найти, где метод определяется во время выполнения? - PullRequest
322 голосов
/ 06 октября 2008

Недавно у нас возникла проблема, когда после серии коммитов серверный процесс не запускался. Теперь мы были хорошими мальчиками и девочками и запускали rake test после каждой регистрации, но из-за некоторых странностей в загрузке библиотеки Rails это происходило только тогда, когда мы запускали его непосредственно из Mongrel в производственном режиме.

Я отследил ошибку, и это произошло из-за того, что новый драгоценный камень Rails переписал метод в классе String таким образом, что сломал одно узкое использование в коде Rails времени выполнения.

В любом случае, короче говоря, есть ли способ, во время выполнения, спросить Ruby, где был определен метод? Что-то вроде whereami( :foo ), которое возвращает /path/to/some/file.rb line #45? В этом случае, сказать мне, что он был определен в классе String, было бы бесполезно, потому что он был перегружен какой-то библиотекой.

Я не могу гарантировать, что источник живет в моем проекте, поэтому поиск значения 'def foo' не обязательно даст мне то, что мне нужно, не говоря уже о том, есть ли у меня много def foo, иногда я не знаю до времени выполнения, какой я могу использовать.

Ответы [ 10 ]

408 голосов
/ 19 марта 2009

Это действительно поздно, но вот как вы можете найти, где определен метод:

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 # owner , который возвращает файл, в котором определен метод.

РЕДАКТИРОВАТЬ: См. Также __file__ и __line__ и примечания для REE в другом ответе, они также удобны. - РГ

81 голосов
/ 25 августа 2010

Вы действительно можете пойти немного дальше, чем решение выше. Для 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 (спасибо Джонатан!):

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]
37 голосов
/ 20 февраля 2012

Я опаздываю к этой теме и удивляюсь, что никто не упомянул Method#owner.

class A; def hello; puts "hello"; end end
class B < A; end
b = B.new
b.method(:hello).owner
=> A
12 голосов
/ 22 октября 2012

Копирование моего ответа из более нового аналогичного вопроса , который добавляет новую информацию к этой проблеме.

Ruby 1.9 имеет метод с именем source_location :

Возвращает имя файла и номер строки исходного кода Ruby, содержащие этот метод, или nil, если этот метод не был определен в Ruby (т.е. нативном)

Этот драгоценный камень был перенесен на 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]

На компьютерах Mac с установленным TextMate также появляется редактор в указанном месте.

6 голосов
/ 07 октября 2008

Если вы можете потерпеть крах метода, вы получите обратную трассировку, которая точно скажет вам, где он находится.

К сожалению, если вы не можете разбить его, то вы не можете узнать, где он был определен. Если вы попытаетесь изменить метод, перезаписав его или переопределив, любой сбой произойдет из-за перезаписанного или переопределенного метода, и он не будет использоваться.

Полезные способы сбоя методов:

  1. Передайте nil там, где это запрещено - большую часть времени метод вызовет ArgumentError или вездесущий NoMethodError для нулевого класса.
  2. Если вы обладаете внутренними знаниями о методе и знаете, что метод в свою очередь вызывает какой-то другой метод, то вы можете перезаписать другой метод и поднять его внутри.
6 голосов
/ 07 октября 2008

Это может помочь, но вам придется кодировать его самостоятельно. Вставлено из блога:

Ruby предоставляет method_added () обратный вызов, который вызывается каждый раз, когда метод добавлен или переопределен в пределах учебный класс. Это часть класса Module, и каждый класс является модулем. Есть также два связанных обратных вызовов method_removed () и method_undefined ().

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

5 голосов
/ 14 июля 2016

Может быть, #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]
3 голосов
/ 14 мая 2010

Очень поздний ответ :) Но более ранние ответы мне не помогли

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
2 голосов
/ 07 октября 2008

Вы всегда можете узнать, где находитесь, используя caller().

2 голосов
/ 07 октября 2008

Вы можете сделать что-то вроде этого:

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. Немного метапрограммирования вы можете обобщить для любой функции, которую захотите. Но он должен быть загружен ПЕРЕД файлом, который фактически выполняет переопределение.

...