Как найти, где метод определен во время выполнения?

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

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

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

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

Ответ 1

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

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]

Обратите внимание, что это не будет работать на все, как собственный скомпилированный код. класс метода содержит некоторые аккуратные функции, например Владелец метода #, который возвращает файл, в котором определяется метод.

EDIT: Также см. __file__ и __line__ и примечания для REE в другом ответе, они также удобны. - wg

Ответ 2

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

Ответ 3

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

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

Ответ 4

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

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

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

Этот символ был передан 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]

На Mac с установленным TextMate это также выведет редактор в указанном месте.

Ответ 5

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

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

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

Ответ 6

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

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

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

  • Pass nil, где он запрещает это - много времени метод поднимет ArgumentError или вездесущий NoMethodError в классе nil.
  • Если у вас есть внутреннее знание метода, и вы знаете, что метод, в свою очередь, вызывает какой-то другой метод, тогда вы можете переписать другой метод и поднять его внутри.

Ответ 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().

Ответ 10

Возможно, #source_location может помочь найти, откуда пришел метод.

ex:

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]