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

Ostatnio pojawił się problem, w którym po wystąpieniu serii commitów proces backendu nie został uruchomiony. Byliśmy grzecznymi chłopcami i dziewczynkami i biegaliśmy rake test po każdej odprawie, ale ze względu na pewne dziwactwa w ładowaniu biblioteki Rails, stało się to tylko wtedy, gdy uruchomiliśmy ją bezpośrednio z Kundla w trybie produkcyjnym.

Wyśledziłem błąd i było to spowodowane nowym gem Rails nadpisującym metodę w klasie String w sposób, który złamał jedno wąskie użycie w runtime Rails kod.

W każdym razie, krótko mówiąc, czy istnieje sposób, aby zapytać Rubiego, gdzie została zdefiniowana metoda? Coś w stylu whereami( :foo ), który zwraca /path/to/some/file.rb line #45? W tym przypadku powiedzenie mi, że jest zdefiniowany w łańcuchu klasy byłoby nieprzydatne, ponieważ zostało przeciążone przez jakąś bibliotekę.

Nie mogę zagwarantować, że źródło żyje w moim projekcie, więc granie dla 'def foo' niekoniecznie da mi to, czego potrzebuję, nie wspominając o tym, czy mam Wiele def foo's, czasami Nie wiem aż do czasu uruchomienia, które może używam.

Author: the Tin Man, 2008-10-06

10 answers

Jest to naprawdę późno, ale oto jak możesz znaleźć, gdzie zdefiniowana jest metoda:

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 jesteś na 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 będzie działać na wszystkim, tak jak natywny skompilowany kod. Klasa metody ma również kilka ciekawych funkcji, takich jak metoda # owner, która zwraca plik, w którym jest zdefiniowana metoda.

EDIT: Zobacz też __file__ i __line__ oraz notatki dla REE w druga odpowiedź, są też przydatne. -- wg

 391
Author: wesgarrison,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-16 03:34:36

Można rzeczywiście pójść nieco dalej niż rozwiązanie powyżej. Dla Ruby 1.8 Enterprise Edition istnieją metody __file__ i __line__ na instancjach 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

Dla Ruby 1.9 i dalej, jest 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]
 80
Author: James Adam,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-04-15 10:20:18

Spóźnię się na ten wątek i jestem zaskoczony, że nikt nie wspomniał o Method#owner.

class A; def hello; puts "hello"; end end
class B < A; end
b = B.new
b.method(:hello).owner
=> A
 36
Author: Alex D,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-02-20 04:07:21

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

Ruby 1.9 has method called source_location :

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

To zostało przeniesione do 1.8.7 przez ten klejnot:

Więc ty można zażądać metody:

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

A następnie poproś o source_location tej metody:

m.source_location

Zwróci tablicę z nazwą pliku i numerem linii. Np. ActiveRecord::Base#validates zwraca:

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]

Dla klas i modułów, Ruby Nie oferuje wbudowanego wsparcia, ale istnieje doskonały Gist, który opiera się na source_location, aby zwrócić plik dla danej metody lub pierwszy plik dla klasy, jeśli żadna metoda nie została określona:

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 pojawia się również edytor w podanej lokalizacji.

 11
Author: Laas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:14

To może pomóc, ale będziesz musiał sam to zakodować. Wklejony z bloga:

Ruby dostarcza method_added() wywołanie zwrotne, które jest wywoływane za każdym razem, gdy metoda jest dodawana lub redefiniowana w ramach klasy. To część klasy Module, a każda klasa jest modułem. Są również dwa powiązane wywołania zwane method_removed () oraz method_undefined ().

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

 6
Author: Ken,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-06 20:01:29

Jeśli uda Ci się rozbić metodę, otrzymasz backtrace, który powie Ci dokładnie, gdzie ona jest.

Niestety, jeśli nie możesz go rozbić, nie możesz dowiedzieć się, gdzie został zdefiniowany. Jeśli spróbujesz użyć metody przez nadpisanie jej lub nadpisanie, każda awaria będzie wynikać z nadpisanej lub nadpisanej metody i nie będzie ona w żadnym wypadku użyta.

Użyteczne sposoby rozbijania metod:

  1. Pass nil where it zakazuje-często metoda wychowa ArgumentError lub zawsze obecny NoMethodError na zerowej klasie.
  2. Jeśli posiadasz wewnętrzną wiedzę na temat metody i wiesz, że metoda z kolei wywołuje inną metodę, możesz ją nadpisać i podnieść wewnątrz niej.
 5
Author: Orion Edwards,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-22 15:22:03

Być może #source_location może pomóc dowiedzieć się, skąd pochodzi metoda.

Ex:

ModelName.method(:has_one).source_location

Return

[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

Return

[project_path/vendor/ruby/version_number/gems/activerecord-number/lib/active_record/validations.rb", line_number_of_where_method_is]
 4
Author: Samda,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-27 15:11:15

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
 3
Author: tig,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-05-14 18:36:18

Możesz 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 foo_finder jest załadowany jako pierwszy z czymś takim jak

ruby -r foo_finder.rb railsapp

(tylko namieszałem w railach, więc nie wiem dokładnie, ale wyobrażam sobie, że jest sposób, aby zacząć to tak.)

To pokaże Ci wszystkie re-definicje łańcucha # foo. Z odrobiną meta-programowania, możesz uogólnić go dla dowolnej funkcji, którą chcesz. Ale musi być załadowany przed plikiem, który właściwie to zmienia definicję.

 2
Author: AShelly,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2008-10-06 20:16:47

Zawsze możesz sprawdzić, gdzie jesteś, używając caller().

 2
Author: the Tin Man,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-31 23:34:37