Wie finde ich heraus, wo eine Methode zur Laufzeit definiert ist?

327

Wir hatten kürzlich ein Problem, bei dem nach einer Reihe von Commits ein Backend-Prozess nicht ausgeführt werden konnte. Jetzt waren wir gute kleine Jungen und Mädchen und rannten rake testnach jedem Check-in, aber aufgrund einiger Kuriositäten beim Laden der Rails-Bibliothek trat dies nur auf, wenn wir es direkt von Mongrel im Produktionsmodus aus liefen.

Ich habe den Fehler aufgespürt und es lag daran, dass ein neues Rails-Juwel eine Methode in der String-Klasse so überschrieb, dass eine enge Verwendung im Rails-Laufzeitcode gebrochen wurde.

Wie auch immer, kurz gesagt, gibt es zur Laufzeit eine Möglichkeit, Ruby zu fragen, wo eine Methode definiert wurde? So etwas whereami( :foo )kehrt zurück /path/to/some/file.rb line #45? In diesem Fall wäre es nicht hilfreich, mir mitzuteilen, dass es in der Klasse String definiert wurde, da es von einer Bibliothek überladen wurde.

Ich kann nicht garantieren, dass die Quelle in meinem Projekt lebt, daher 'def foo'gibt mir das Greifen nach nicht unbedingt das, was ich brauche, ganz zu schweigen davon, ob ich viele habe def foo . Manchmal weiß ich erst zur Laufzeit, welche ich möglicherweise verwende.

Matt Rogish
quelle
1
In Ruby 1.8.7 wurde eine spezielle Methode hinzugefügt, um diese Informationen zu finden (und sie ist in 1.9.3 immer noch vorhanden) ... Details in meiner Antwort unten.
Alex D

Antworten:

418

Dies ist sehr spät, aber hier erfahren Sie, wo eine Methode definiert ist:

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>

Wenn Sie mit Ruby 1.9+ arbeiten, können Sie verwenden 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]

Beachten Sie, dass dies nicht bei allen Funktionen funktioniert, z. B. bei nativem kompiliertem Code. Die Method-Klasse hat auch einige nette Funktionen, wie z. B. den Method # -Eigentümer, der die Datei zurückgibt, in der die Methode definiert ist.

BEARBEITEN: Siehe auch die __file__und __line__und Hinweise für REE in der anderen Antwort, sie sind auch praktisch. - wg

Wesgarrison
quelle
1
source_location scheint für 1.8.7-p334 mit activesupport-2.3.14 zu funktionieren
Jeff Maass
Nachdem Sie die Methode gefunden haben, versuchen Sie die Methode der ownerMethode
Juguang
1
Was ist die Nummer zwei in 2.method(:crime)?
Stack1
1
eine Instanz der KlasseFixnum
kitteehh
1
Wichtiger Hinweis: Dadurch werden keine dynamisch definierten Methoden abgerufen method_missing. Wenn Sie also ein Modul oder eine Vorfahrenklasse mit einem class_evaloder define_methodinnerhalb von haben method_missing, funktioniert diese Methode nicht.
CDPALMER
82

Sie können tatsächlich ein bisschen weiter gehen als die obige Lösung. Für Ruby 1.8 Enterprise Edition gibt es die __file__und __line__-Methoden für MethodInstanzen:

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

Für Ruby 1.9 und höher gibt es source_location(danke 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
quelle
2
Ich erhalte "NoMethodError: undefined method" für beide __file__und __line__für jede MethodKlasseninstanz, z method(:method).__file__.
Vikrant Chaudhary
Welche Version von Ruby hast du?
James Adam
Ruby 1.8.7 (2010-06-23 Patchlevel 299) [x86_64-Linux]
Vikrant Chaudhary
19
Auf Ruby 1.9 m.__file__und m.__line__wurden durch ersetzt m.source_location.
Jonathan Tran
Was ist mit Ruby 2.1?
Lokesh
37

Ich komme zu spät zu diesem Thread und bin überrascht, dass niemand erwähnt hat Method#owner.

class A; def hello; puts "hello"; end end
class B < A; end
b = B.new
b.method(:hello).owner
=> A
Alex D.
quelle
2
Ich bin überrascht, dass Sie als erster explizit auf die Method-Klasse verweisen . Ein weiterer weniger bekannter Schatz, der in 1.9 eingeführt wurde : Method#parameters.
fny
12

Kopieren meiner Antwort von einer neueren ähnlichen Frage , die diesem Problem neue Informationen hinzufügt.

Ruby 1.9 hat eine Methode namens source_location :

Gibt den Ruby-Quelldateinamen und die Zeilennummer zurück, die diese Methode oder Null enthalten, wenn diese Methode nicht in Ruby definiert wurde (dh native).

Dies wurde von diesem Juwel auf 1.8.7 zurückportiert :

Sie können also die Methode anfordern:

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

Und dann fragen Sie nach der source_locationMethode:

m.source_location

Dies gibt ein Array mit Dateiname und Zeilennummer zurück. ZB für ActiveRecord::Base#validatesdiese Rückkehr:

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]

Für Klassen und Module bietet Ruby keine integrierte Unterstützung, aber es gibt eine hervorragende Übersicht, die darauf aufbaut, source_locationeine Datei für eine bestimmte Methode oder eine erste Datei für eine Klasse zurückzugeben, wenn keine Methode angegeben wurde:

In Aktion:

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]

Auf Macs mit installiertem TextMate wird dadurch auch der Editor am angegebenen Speicherort angezeigt.

Laas
quelle
6

Dies kann helfen, aber Sie müssten es selbst codieren. Aus dem Blog eingefügt:

Ruby bietet einen Rückruf method_added (), der jedes Mal aufgerufen wird, wenn eine Methode innerhalb einer Klasse hinzugefügt oder neu definiert wird. Es ist Teil der Modulklasse und jede Klasse ist ein Modul. Es gibt auch zwei verwandte Rückrufe, method_removed () und method_undefined ().

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

Ken
quelle
5

Wenn Sie die Methode zum Absturz bringen können, erhalten Sie eine Rückverfolgung, die Ihnen genau sagt, wo sie sich befindet.

Wenn Sie es nicht zum Absturz bringen können, können Sie leider nicht herausfinden, wo es definiert wurde. Wenn Sie versuchen, mit der Methode einen Affen zu erstellen, indem Sie sie überschreiben oder überschreiben, kommt es zu einem Absturz Ihrer überschriebenen oder überschriebenen Methode, der keinen Nutzen bringt.

Nützliche Methoden zum Absturz von Methoden:

  1. Übergeben Sie, nilwo es verboten ist - die meiste Zeit wird die Methode eine ArgumentErroroder die allgegenwärtige NoMethodErrorKlasse auf Null setzen.
  2. Wenn Sie Insiderwissen über die Methode haben und wissen, dass die Methode wiederum eine andere Methode aufruft, können Sie die andere Methode überschreiben und darin erhöhen.
Orion Edwards
quelle
Wenn Sie Zugriff auf den Code haben, können Sie genauso einfach require 'ruby-debug'; debugger in Ihren Code einfügen , wo Sie vorbeischauen möchten.
The Tin Man
"Wenn Sie es nicht zum Absturz bringen können, können Sie leider nicht herausfinden, wo es definiert wurde." Ist nicht wahr. Siehe andere Antworten.
Martin T.
5

Vielleicht #source_locationkann das helfen, herauszufinden, woher die Methode kommt.

Ex:

ModelName.method(:has_one).source_location

Rückkehr

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

ODER

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

Rückkehr

[project_path/vendor/ruby/version_number/gems/activerecord-number/lib/active_record/validations.rb", line_number_of_where_method_is]
Samda
quelle
3

Sehr späte Antwort :) Aber frühere Antworten haben mir nicht geholfen

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
quelle
Warum gehst du vorbei nil?
Arup Rakshit
@ArupRakshit aus der Dokumentation: «Richtet proc als Handler für die Ablaufverfolgung ein oder deaktiviert die Ablaufverfolgung, wenn der Parameter lautet nil
Tig
2

Möglicherweise können Sie Folgendes tun:

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

Stellen Sie dann sicher, dass foo_finder zuerst mit etwas wie geladen wird

ruby -r foo_finder.rb railsapp

(Ich habe nur mit Schienen herumgespielt, daher weiß ich es nicht genau, aber ich stelle mir vor, dass es einen Weg gibt, es so zu starten.)

Dies zeigt Ihnen alle Neudefinitionen von String # foo. Mit ein wenig Metaprogrammierung können Sie es für jede gewünschte Funktion verallgemeinern. Es muss jedoch VOR der Datei geladen werden, die die Neudefinition tatsächlich durchführt.

AShelly
quelle
2

Mit können Sie jederzeit einen Rückblick darauf erhalten, wo Sie sich befinden caller().

der Blechmann
quelle
1
Dies ist nützlich, um herauszufinden, was Sie angerufen hat, aber nicht gut, wenn Sie herausfinden möchten, wo etwas definiert wurde.
der Blechmann