Jak znaleźć gdzie metoda jest zdefiniowana w czasie wykonywania?

328

Niedawno mieliśmy problem z tym, że po serii zatwierdzeń proces backendu nie został uruchomiony. Byliśmy dobrymi małymi chłopcami i dziewczynkami i biegaliśmy rake testpo każdym zameldowaniu, ale z powodu pewnych dziwnych cech w ładowaniu biblioteki Rails, pojawiło się to tylko wtedy, gdy uruchomiliśmy go bezpośrednio z Mongrel w trybie produkcyjnym.

Wyśledziłem błąd i było to spowodowane tym, że nowy klejnot Rails nadpisał metodę w klasie String w sposób, który przerwał jedno wąskie zastosowanie w kodzie wykonawczym Railsa.

W każdym razie, w skrócie, czy jest sposób, aby w czasie wykonywania zapytać Ruby, gdzie zdefiniowano metodę? Coś takiego whereami( :foo )powraca /path/to/some/file.rb line #45? W takim przypadku powiedzenie mi, że został zdefiniowany w klasie String, byłoby nieprzydatne, ponieważ zostało przeciążone przez jakąś bibliotekę.

Nie mogę zagwarantować, że źródło będzie żyło w moim projekcie, więc szukanie 'def foo'go niekoniecznie da mi to, czego potrzebuję, nie wspominając o tym, że mam wiele def foo , czasem nie wiem do czasu uruchomienia, z którego mogę korzystać.

Matt Rogish
źródło
1
W Ruby 1.8.7 została dodana specjalna metoda w celu znalezienia tych informacji (i wciąż tam jest w 1.9.3) ... szczegóły w mojej odpowiedzi poniżej.
Alex D

Odpowiedzi:

419

Jest naprawdę późno, ale oto, jak znaleźć definicję metody:

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>

Jeśli korzystasz z Ruby 1.9+, możesz użyć 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]

Zauważ, że to nie działa na wszystko, na przykład na natywny kod skompilowany. Klasa metoda ma kilka funkcji, schludne, zbyt, jak Method # właściciela , która zwraca plik, w którym metoda jest zdefiniowana.

EDYCJA: Zobacz także __file__i __line__i dla REE w drugiej odpowiedzi, one też są przydatne. - wg

wesgarrison
źródło
1
lokalizacja_źródła wydaje się działać dla 1.8.7-p334 przy użyciu activesupport-2.3.14
Jeff Maass
Po znalezieniu metody, spróbuj metody jest ownermetoda
Juguang
1
W czym jest numer dwa 2.method(:crime)?
stos 1
1
wystąpienie klasyFixnum
kitteehh
1
Ważna uwaga: nie spowoduje to pobrania żadnych dynamicznie zdefiniowanych metod method_missing. Więc jeśli masz klasę modułu lub przodka z class_evallub define_methodwewnątrz method_missing, to ta metoda nie będzie działać.
cdpalmer
83

Możesz faktycznie pójść nieco dalej niż powyższe rozwiązanie. W Ruby 1.8 Enterprise Edition jest__file____line__ metody i Methodinstancje:

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

Dla Ruby 1.9 i nowszych istnieje source_location(dzięki Jonathan!):

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
źródło
2
I Get „NoMethodError: metody niezdefiniowanej” dla obu __file__i __line__na każdej Methodinstancji klasy, ex: method(:method).__file__.
Vikrant Chaudhary
Którą wersję rubinu posiadasz?
James Adam
ruby 1.8.7 (poziom aktualizacji 29-06 2010-29) [x86_64-linux]
Vikrant Chaudhary
19
Na Ruby 1.9 m.__file__i m.__line__zostały zastąpione przez m.source_location.
Jonathan Tran
Co z Ruby 2.1?
Lokesh
38

Spóźniam się na ten wątek i jestem zaskoczona, że ​​nikt o nim nie wspominał Method#owner.

class A; def hello; puts "hello"; end end
class B < A; end
b = B.new
b.method(:hello).owner
=> A
Alex D.
źródło
2
Dziwię się, że jako pierwszy wyraźnie odniosłeś się do klasy Method . Innym mniej znane skarb wprowadzono 1,9: Method#parameters.
fny
13

Kopiowanie mojej odpowiedzi z nowszego podobnego pytania, które dodaje nowe informacje do tego problemu.

Ruby 1.9 ma metodę o nazwie source_location :

Zwraca źródłową nazwę pliku Ruby i numer linii zawierający tę metodę lub zero, jeśli ta metoda nie została zdefiniowana w Ruby (tj. Natywna)

Ten klejnot został przeniesiony do 1.8.7 :

Możesz więc poprosić o metodę:

m = Foo::Bar.method(:create)

A następnie poproś source_locationo tę metodę:

m.source_location

Zwróci tablicę z nazwą pliku i numerem linii. Np. Dla ActiveRecord::Base#validatestego zwrotu:

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]

W przypadku klas i modułów Ruby nie oferuje wbudowanej obsługi, ale istnieje doskonała Gist, która opiera się source_locationna zwróceniu pliku dla danej metody lub pierwszego pliku dla klasy, jeśli nie określono żadnej metody:

W akcji:

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]

Na komputerach Mac z zainstalowanym programem TextMate powoduje to także wyświetlenie edytora w określonej lokalizacji.

Laas
źródło
7

To może pomóc, ale trzeba by to samemu zakodować. Wklejono z bloga:

Ruby zapewnia wywołanie zwrotne method_added (), które jest wywoływane za każdym razem, gdy metoda jest dodawana lub ponownie definiowana w klasie. Jest częścią klasy Module, a każda klasa jest modułem. Istnieją również dwa powiązane wywołania zwrotne o nazwie method_removed () i method_undefined ().

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

Rozpoznać
źródło
6

Jeśli możesz zawiesić metodę, otrzymasz ślad, który powie ci dokładnie, gdzie ona jest.

Niestety, jeśli nie możesz go zawiesić, nie możesz dowiedzieć się, gdzie został zdefiniowany. Jeśli spróbujesz małpować metodę, nadpisując ją lub zastępując, wtedy nastąpi awaria z nadpisanej lub zastąpionej metody i nie będzie to przydatne.

Przydatne sposoby zawieszania się metod:

  1. Przechodź niltam, gdzie to zabrania - przez większość czasu metoda podnosi ArgumentErrorlub zawsze obecna NoMethodErrorw klasie zero.
  2. Jeśli masz wewnętrzną wiedzę na temat metody i wiesz, że ta metoda z kolei wywołuje inną metodę, możesz zastąpić inną metodę i podnieść ją w środku.
Orion Edwards
źródło
Jeśli masz dostęp do kodu, możesz równie łatwo wprowadzić require 'ruby-debug'; debugger kod w miejscu, w którym chcesz wpaść.
Tin Man
„Niestety, jeśli nie można go zawiesić, nie można dowiedzieć się, gdzie został zdefiniowany”. To nie jest prawda. Zobacz inne odpowiedzi.
Martin T.
6

Może #source_location może pomóc ustalić, skąd pochodzi metoda.

dawny:

ModelName.method(:has_one).source_location

Powrót

[project_path/vendor/ruby/version_number/gems/activerecord-number/lib/active_record/associations.rb", line_number_of_where_method_is]

LUB

ModelName.new.method(:valid?).source_location

Powrót

[project_path/vendor/ruby/version_number/gems/activerecord-number/lib/active_record/validations.rb", line_number_of_where_method_is]
Samda
źródło
4

Bardzo późna odpowiedź :) Ale wcześniejsze odpowiedzi mi nie pomogły

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
źródło
Czemu przejściu nil?
Arup Rakshit
@ArupRakshit z dokumentacji: «Ustanawia proc jako moduł obsługi śledzenia lub wyłącza śledzenie, jeśli parametr to nil
tig
3

Możesz być w stanie zrobić coś takiego:

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

Następnie upewnij się, że najpierw załadowano foo_finder z czymś takim

ruby -r foo_finder.rb railsapp

(Pomieszałem tylko z szynami, więc nie wiem dokładnie, ale wyobrażam sobie, że istnieje sposób, aby rozpocząć coś takiego.)

Spowoduje to wyświetlenie wszystkich ponownych definicji ciągu # foo. Przy odrobinie programowania można uogólnić go na dowolną funkcję. Ale należy go załadować PRZED plikiem, który faktycznie dokonuje ponownej definicji.

AShelly
źródło
3

Zawsze możesz uzyskać informacje o tym, gdzie jesteś, używając caller().

Blaszany Człowiek
źródło
1
Jest to przydatne, aby znaleźć to, co Cię wezwało, ale nie jest dobre, gdy próbujesz znaleźć, gdzie coś zostało zdefiniowane.
Tin Man