Як знайти, де метод визначений під час виконання?


328

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

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

У будь-якому випадку, якщо коротко розповісти, чи існує спосіб, коли під час виконання запитати Рубі, де визначений метод? Щось 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 #, який повертає файл, де визначено метод.

РЕДАКТУВАННЯ. Також в іншій відповіді див. __file__Та __line__примітки до REE, вони також зручні. - вгг


1
Схоже, джерело_локації працює на 1.8.7-p334, використовуючи activesupport-2.3.14
Jeff Maass

знайшовши метод, спробуйте метод 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 1.8.7 (2010-06-23 patchlevel 299) [x86_64-linux]
Vikrant Chaudhary

19
На Ruby 1.9, m.__file__і m.__line__були замінені на m.source_location.
Джонатан Тран

Що з Ruby 2.1?
Локеш

38

Я запізнююсь на цю тему і дивуюсь, що ніхто не згадав Method#owner.

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

2
Я здивований, що ви перший чітко посилаєтесь на клас Метод . Ще менш відомий скарб введений в версії 1.9: Method#parameters.
fny

13

Копіювання моєї відповіді з нового подібного запитання яке додає нову інформацію до цієї проблеми.

У Ruby 1.9 є метод, який називається source_location :

Повертає ім'я вихідного файлу і номер рядка Ruby, що містить цей метод або нуль, якщо цей метод не був визначений у 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]

На комп’ютерах Macs із встановленим TextMate це також спливає редактор у вказаному місці.


7

Це може допомогти, але вам доведеться кодувати це самостійно. Вкладено з блогу:

Ruby забезпечує зворотний виклик method_added (), який викликається щоразу, коли метод додається або переосмислюється в класі. Це частина класу Модуль, і кожен Клас - Модуль. Також є два пов'язані зворотні виклики під назвою 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]

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. За допомогою невеликого метапрограмування ви можете узагальнити його для будь-якої функції, яку ви хочете. Але його потрібно завантажувати перед тим файлом, який насправді робить повторне визначення.


3

Ви завжди можете отримати зворотній зв'язок, де ви знаходитесь, використовуючи caller().


1
Це корисно, щоб знайти те, що вас дзвонило, але не добре, коли ви намагаєтесь знайти там, де щось було визначено.
Олов'яний чоловік
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.