Come trovare dove viene definito un metodo in fase di esecuzione?


328

Di recente abbiamo riscontrato un problema a causa del quale non è stato possibile eseguire un processo di back-end dopo una serie di commit. Eravamo bravi ragazzini e correvamo rake testdopo ogni check-in ma, a causa di alcune stranezze nel caricamento della libreria di Rails, si verificava solo quando lo gestivamo direttamente da Mongrel in modalità produzione.

Ho rintracciato il bug ed era dovuto a una nuova gemma di Rails che sovrascriveva un metodo nella classe String in un modo che ha interrotto un uso ristretto nel codice di runtime di Rails.

Comunque, per farla breve, c'è un modo, in fase di esecuzione, di chiedere a Ruby dove è stato definito un metodo? Qualcosa del genere whereami( :foo )ritorna /path/to/some/file.rb line #45? In questo caso, dirmi che è stato definito nella classe String non sarebbe utile, perché era sovraccarico di alcune librerie.

Non posso garantire la vita delle fonti nel mio progetto, quindi 'def foo'non posso necessariamente fornirmi ciò di cui ho bisogno, per non parlare del fatto che ne ho molti def foo , a volte non so fino al momento dell'esecuzione quale potrei usare.


1
In Ruby 1.8.7, un metodo speciale è stato aggiunto specificamente per trovare queste informazioni (ed è ancora lì in 1.9.3) ... dettagli nella mia risposta di seguito.
Alex D,

Risposte:


419

Questo è davvero in ritardo, ma ecco come puoi trovare dove è definito un metodo:

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>

Se usi Ruby 1.9+, puoi usarlo 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]

Nota che questo non funzionerà su tutto, come il codice compilato nativo. La classe Method ha anche alcune funzioni pulite, come il metodo # proprietario che restituisce il file in cui è definito il metodo.

EDIT: vedi anche __file__e __line__e note per REE nell'altra risposta, sono anche utili. - wg


1
source_location sembra funzionare per 1.8.7-p334 usando activesupport-2.3.14
Jeff Maass,

dopo aver trovato il metodo, prova il ownermetodo
Juguang

1
Qual è il numero due in 2.method(:crime)?
stack1

1
un'istanza della classeFixnum
k Committeehh

1
Nota importante: questo non richiamerà alcun metodo definito dinamicamente da method_missing. Quindi se hai un modulo o una classe antenata con class_evalo define_methodall'interno di method_missing, allora questo metodo non funzionerà.
cdpalmer,

83

Puoi effettivamente andare un po 'oltre la soluzione sopra. Per Ruby 1.8 Enterprise Edition, c'è il__file____line__ metodi e sulle Methodistanze:

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

Per Ruby 1.9 e oltre, c'è source_location(grazie 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
Ottengo "NoMethodError: metodo non definito" per entrambi __file__e __line__su ogni Methodistanza di classe, ad esempio: method(:method).__file__.
Vikrant Chaudhary,

Quale versione di ruby ​​hai?
James Adam,

ruby 1.8.7 (2010-06-23 patchlevel 299) [x86_64-linux]
Vikrant Chaudhary

19
Su Ruby 1.9, m.__file__e m.__line__sono stati sostituiti con m.source_location.
Jonathan Tran

Che dire di Ruby 2.1?
Lokesh,

38

Sto arrivando tardi a questa discussione e sono sorpreso che nessuno abbia menzionato Method#owner.

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

2
Sono sorpreso che tu sia il primo a fare riferimento esplicitamente alla classe Method . Un altro tesoro meno noto introdotto in 1.9: Method#parameters.
fny

13

Copia la mia risposta da una nuova domanda simile che aggiunge nuove informazioni a questo problema.

Ruby 1.9 ha un metodo chiamato source_location :

Restituisce il nome del file di origine Ruby e il numero di riga contenente questo metodo o zero se questo metodo non è stato definito in Ruby (ovvero nativo)

Questo gioiello è stato riportato a 1.8.7 da questo gioiello:

Quindi puoi richiedere il metodo:

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

E quindi chiedere il source_locationdi quel metodo:

m.source_location

Ciò restituirà un array con nome file e numero riga. Ad esempio per ActiveRecord::Base#validatesquesto ritorni:

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]

Per classi e moduli, Ruby non offre supporto integrato, ma esiste un eccellente Gist che si basa sulla source_locationrestituzione del file per un determinato metodo o del primo file per una classe se non è stato specificato alcun metodo:

In azione:

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]

Su Mac con TextMate installato, questo fa apparire l'editor nella posizione specificata.


7

Questo può aiutare, ma dovresti codificarlo da solo. Incollato dal blog:

Ruby fornisce un callback method_added () che viene richiamato ogni volta che un metodo viene aggiunto o ridefinito all'interno di una classe. Fa parte della classe Module e ogni classe è un modulo. Esistono anche due callback correlati chiamati method_removed () e method_undefined ().

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


6

Se riesci a bloccare il metodo, otterrai una backtrace che ti dirà esattamente dove si trova.

Sfortunatamente, se non riesci a bloccarlo, non puoi scoprire dove è stato definito. Se si tenta di scimmiottare con il metodo sovrascrivendolo o sovrascrivendolo, qualsiasi arresto proviene dal metodo sovrascritto o sovrascritto e non sarà di alcuna utilità.

Metodi utili per bloccare i metodi:

  1. Passa nildove lo proibisce - per la maggior parte del tempo il metodo solleverà uno ArgumentErroro il sempre presente NoMethodErrorsu una classe zero.
  2. Se hai una conoscenza interna del metodo e sai che il metodo a sua volta chiama qualche altro metodo, puoi sovrascrivere l'altro metodo e sollevare al suo interno.

Se hai accesso al codice, puoi anche inserire facilmente require 'ruby-debug'; debugger il codice nel punto in cui vuoi inserire .
Tin Man

"Sfortunatamente, se non riesci a bloccarlo, non puoi scoprire dove è stato definito." Non è vero. Vedi altre risposte.
Martin T.

6

Forse il #source_location può aiutare a trovare da dove viene il metodo.

ex:

ModelName.method(:has_one).source_location

Ritorno

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

O

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

Ritorno

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

4

Risposta molto tardi :) Ma le risposte precedenti non mi hanno aiutato

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

Perché stai passando nil?
Arup Rakshit,

@ArupRakshit dalla documentazione: «Stabilisce proc come gestore per la traccia o disabilita la traccia se il parametro è nil
Tig

3

Potresti essere in grado di fare qualcosa del genere:

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

Quindi assicurati che foo_finder sia caricato prima con qualcosa del genere

ruby -r foo_finder.rb railsapp

(Ho solo incasinato le rotaie, quindi non lo so esattamente, ma immagino che ci sia un modo per avviarlo in questo modo.)

Questo ti mostrerà tutte le ridefinizioni di String # foo. Con un po 'di meta-programmazione, potresti generalizzarla per qualunque funzione tu voglia. Ma deve essere caricato PRIMA del file che esegue effettivamente la ridefinizione.


3

Puoi sempre ottenere una traccia di dove ti trovi utilizzando caller().


1
Questo è utile per trovare ciò che ti ha chiamato, ma non va bene quando stai cercando di trovare dove è stato definito qualcosa.
Tin Man,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.