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

ruby-on-rails ruby runtime methods definition

81028 просмотра

10 ответа

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

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

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

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

Автор: Matt Rogish Источник Размещён: 22.10.2019 01:44

Ответы (10)


412 плюса

Решение

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

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 Размещён: 18.03.2009 09:12

81 плюса

На самом деле вы можете пойти немного дальше, чем решение выше. Для 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]
Автор: James Adam Размещён: 25.08.2010 09:52

37 плюса

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

class A; def hello; puts "hello"; end end
class B < A; end
b = B.new
b.method(:hello).owner
=> A
Автор: Alex D Размещён: 20.02.2012 04:07

12 плюса

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

В 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 Размещён: 22.10.2012 04:29

6 плюса

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

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

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

Автор: Ken Размещён: 06.10.2008 08:01

5 плюса

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

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

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

  1. Пройдите, nilгде это запрещено, - в большинстве случаев метод будет вызывать ArgumentErrorили всегда присутствовать NoMethodErrorв нулевом классе.
  2. Если вы обладаете внутренними знаниями о методе и знаете, что метод в свою очередь вызывает какой-то другой метод, то вы можете перезаписать другой метод и поднять его внутри.
Автор: Orion Edwards Размещён: 06.10.2008 08:01

5 плюса

Может быть, #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 Размещён: 14.07.2016 07:51

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
Автор: tig Размещён: 14.05.2010 06:36

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

Автор: AShelly Размещён: 06.10.2008 08:16

2 плюса

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

Автор: Garry Размещён: 07.10.2008 05:07
Вопросы из категории :
32x32