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ć.


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


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]

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

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.


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


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.

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]

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

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.


3

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


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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.