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

328

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

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

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

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

Мэтт Рогиш
источник
1
В Ruby 1.8.7 был специально добавлен специальный метод для поиска этой информации (и он все еще там в 1.9.3) ... подробности в моем ответе ниже.
Алекс Д

Ответы:

419

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

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 также имеет несколько полезных функций, таких как Method # owner, который возвращает файл, в котором определен метод.

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

wesgarrison
источник
1
source_location, кажется, работает для 1.8.7-p334 с использованием activesupport-2.3.14
Джефф Маасс
после того, как найти метод, попробуйте метода ownerметод
Juguang
1
Что такое номер два в 2.method(:crime)?
stack1
1
экземпляр классаFixnum
Kitteehh
1
Важное примечание: это не вызовет никаких динамически определенных методов из method_missing. Так что если у вас есть класс модуля или предка с class_evalили define_methodвнутри method_missing, то этот метод не будет работать.
cdpalmer
83

На самом деле вы можете пойти немного дальше, чем решение выше. Для 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]
Джеймс Адам
источник
2
Я получаю «NoMethodError: неопределенный метод» для обоих __file__и __line__на любом Methodэкземпляре класса, экс: method(:method).__file__.
Викрант Чаудхари
Какая версия ruby ​​у вас есть?
Джеймс Адам
ruby 1.8.7 (2010-06-23 patchlevel 299) [x86_64-linux]
Викрант Чаудхари
19
На Ruby 1.9 m.__file__и m.__line__были заменены на m.source_location.
Джонатан Тран
Что насчет Ruby 2.1?
Lokesh
38

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

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

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

В 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 также появляется редактор в указанном месте.

Laas
источник
7

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

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

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

кругозор
источник
6

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

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

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

  1. Пройдите, nilгде это запрещено, - в большинстве случаев метод будет вызывать ArgumentErrorили всегда присутствовать NoMethodErrorв нулевом классе.
  2. Если вы обладаете внутренними знаниями о методе и знаете, что метод в свою очередь вызывает какой-то другой метод, то вы можете перезаписать другой метод и поднять его внутри.
Орион Эдвардс
источник
Если у вас есть доступ к коду, вы также можете легко вставить его require 'ruby-debug'; debugger в код, куда хотите его вставить .
Жестянщик
«К сожалению, если вы не можете разбить его, то вы не можете узнать, где он был определен». Это не правда. Смотри другие ответы.
Мартин Т.
6

Может быть, #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]
Samda
источник
4

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

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

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

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

AShelly
источник
3

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

жестяной человек
источник
1
Это полезно, чтобы найти то, что вас вызвало, но не очень хорошо, когда вы пытаетесь найти, где что-то было определено.
Жестянщик