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

272

У нас недавно возникла проблема, когда после совершения ряда коммитов бэкэнд-процесс не выполнялся. Теперь мы были хорошими мальчиками и девочками и побежали 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
    В Ruby 1.8.7 был специально добавлен специальный метод для поиска этой информации (и он все еще там в 1.9.3) ... подробности в моем ответе ниже.
Теги:
methods
definition
runtime

10 ответов

345
Лучший ответ

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

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

  • 1
    source_location работает для 1.8.7-p334 с использованием activesupport-2.3.14
  • 0
    после нахождения метода попробуйте метод owner метода
Показать ещё 6 комментариев
77

Вы можете пойти немного дальше, чем решение выше. Для 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]
  • 1
    Я получаю «NoMethodError: undefined method» для обоих __file__ и __line__ на любом экземпляре класса Method , например: method(:method).__file__ .
  • 0
    Какая версия ruby у вас есть?
Показать ещё 5 комментариев
31

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

class A; def hello; puts "hello"; end end
class B < A; end
b = B.new
b.method(:hello).owner
=> A
  • 1
    Я удивлен, что вы первый, кто явно ссылается на класс Method . Еще одно менее известное сокровище, введенное в 1.9: Method#parameters .
10

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

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/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

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

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

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

4

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

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

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

  • Pass nil, где он запрещает это - много времени метод поднимет ArgumentError или вездесущий NoMethodError в классе nil.
  • Если у вас есть внутреннее знание метода, и вы знаете, что метод, в свою очередь, вызывает какой-то другой метод, тогда вы можете переписать другой метод и поднять его внутри.
  • 0
    Если у вас есть доступ к коду, вы можете просто вставить require 'ruby-debug'; debugger в вашем коде, куда вы хотите зайти.
3

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

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
  • 0
    Почему ты пропускаешь nil ?
  • 0
    @ArupRakshit из документации: «Устанавливает proc в качестве обработчика для трассировки или отключает трассировку, если параметр равен nil
2

Возможно, #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]
2

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

  • 0
    Это полезно, чтобы найти то, что вас вызвало, но не очень хорошо, когда вы пытаетесь найти, где что-то было определено.
2

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню