Как найти, где метод определяется во время выполнения?
недавно у нас была проблема, когда после серии коммитов произошел сбой бэкэнд-процесса. Теперь мы были хорошими маленькими мальчиками и девочками и бежали rake test после каждой регистрации, но из-за некоторых странностей в загрузке библиотеки Rails это произошло только тогда, когда мы запустили его непосредственно из Mongrel в производственном режиме.
я отследил ошибку, и это было связано с новым драгоценным камнем Rails, перезаписывающим метод в классе String таким образом, что он нарушил одно узкое использование во время выполнения Rails код.
Короче говоря, есть ли способ во время выполнения спросить Ruby, где был определен метод? Что-то вроде whereami( :foo ) возвращает /path/to/some/file.rb line #45? В этом случае говорить мне, что он был определен в строке класса, было бы бесполезно, потому что он был перегружен некоторой библиотекой.
Я не могу гарантировать, что источник живет в моем проекте, поэтому grepping for 'def foo' не обязательно даст мне то, что мне нужно, не говоря уже о том, если у меня есть многоdef foos, Иногда я не знаю до времени выполнения, какой из них я могу использовать.
10 ответов:
Это очень поздно, но вот как вы можете найти, где определяется метод:
# 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_locationrequire '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
Если вы можете разбить метод, вы получите обратную трассу, которая точно скажет вам, где она находится.
к сожалению, если вы не можете разбить его, то вы не можете выяснить, где он был определен. Если вы попытаетесь обезьянничать с методом, перезаписывая его или переопределяя его, то любой сбой будет исходить из вашего перезаписанного или переопределенного метода, и это не будет никакой пользы.
полезные способы сбоя методы:
- передать
nilгде это запрещает - много в то время как метод будет подниматьArgumentErrorили вездесущуюNoMethodErrorна нулевом классе.- если у вас есть внутреннее знание метода, и вы знаете, что метод в свою очередь вызывает какой-то другой метод, то вы можете перезаписать другой метод и поднять внутри него.
может быть
#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. С небольшим метапрограммированием вы можете обобщить его для любой функции, которую вы хотите. Но это действительно нужно для загрузки перед файлом, который фактически выполняет повторное определение.
Comments