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



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



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



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



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

643   10  

10 ответов:

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

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]

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

EDIT: Также см. __file__ и __line__ и заметки для РИ в другом ответе, они тоже удобны. -- РГ

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

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

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

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

Рубин 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 не предлагает встроенную поддержку, но есть отличная суть, которая основывается на 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, это также всплывает редактор в указанном месте.

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

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

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

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

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

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

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

может быть #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]

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

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

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

foo_finder.РБ:

 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

(Я только возился с рельсами, поэтому я точно не знаю, но я думаю, что есть способ начать это вроде этого.)

Это покажет вам все переопределения строки#foo. С небольшим метапрограммированием вы можете обобщить его для любой функции, которую вы хотите. Но это действительно нужно для загрузки перед файлом, который фактически выполняет повторное определение.

вы всегда можете получить на след того, где вы находитесь с помощью caller().

Comments

    Ничего не найдено.