Comment trouver où une méthode est définie lors de l'exécution?

328

Nous avons récemment rencontré un problème où, après une série de validations, un processus d'arrière-plan n'a pas pu s'exécuter. Maintenant, nous étions de bons petits garçons et filles et avons couru rake testaprès chaque enregistrement, mais, en raison de certaines bizarreries dans le chargement de la bibliothèque de Rails, cela ne s'est produit que lorsque nous l'avons exécuté directement depuis Mongrel en mode production.

J'ai suivi le bogue et cela était dû à un nouveau joyau Rails écrasant une méthode dans la classe String d'une manière qui a rompu une utilisation étroite dans le code Rails d'exécution.

Quoi qu'il en soit, pour faire court, y a-t-il un moyen, au moment de l'exécution, de demander à Ruby où une méthode a été définie? Quelque chose comme whereami( :foo )ça revient /path/to/some/file.rb line #45? Dans ce cas, il ne serait pas utile de me dire qu'il a été défini dans la classe String, car il était surchargé par une bibliothèque.

Je ne peux pas garantir la vie de la source dans mon projet, donc chercher 'def foo'ne me donnera pas nécessairement ce dont j'ai besoin, sans mentionner si j'en ai plusieurs def foo , parfois je ne sais pas avant l'exécution laquelle j'utilise.

Matt Rogish
la source
1
Dans Ruby 1.8.7, une méthode spéciale a été ajoutée spécifiquement pour trouver cette information (et elle est toujours là dans 1.9.3) ... détails dans ma réponse ci-dessous.
Alex D

Réponses:

419

C'est vraiment tard, mais voici comment vous pouvez trouver où une méthode est définie:

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>

Si vous utilisez Ruby 1.9+, vous pouvez utiliser 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]

Notez que cela ne fonctionnera pas sur tout, comme le code natif compilé. La classe Method possède également quelques fonctions intéressantes, comme le propriétaire de la méthode # qui retourne le fichier dans lequel la méthode est définie.

EDIT: Voir également les notes __file__et __line__et pour REE dans l'autre réponse, elles sont également utiles. - wg

Wesgarrison
la source
1
source_location semble fonctionner pour 1.8.7-p334 en utilisant activesupport-2.3.14
Jeff Maass
après avoir trouvé la méthode, essayez la méthode de la ownerméthode
Juguang
1
Quel est le numéro deux 2.method(:crime)?
stack1
1
une instance de la classeFixnum
kitteehh
1
Remarque importante: cela ne récupérera aucune méthode définie dynamiquement method_missing. Donc, si vous avez un module ou une classe ancêtre avec class_evalou à l' define_methodintérieur de method_missing, cette méthode ne fonctionnera pas.
cdpalmer
83

Vous pouvez en fait aller un peu plus loin que la solution ci-dessus. Pour Ruby 1.8 Enterprise Edition, il existe les méthodes __file__et __line__sur les Methodinstances:

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

Pour Ruby 1.9 et au-delà, il y a source_location(merci 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
la source
2
Je reçois « NoMethodError: méthode non définie » pour les deux __file__et __line__sur une Methodinstance de classe, ex: method(:method).__file__.
Vikrant Chaudhary
Quelle version de ruby ​​possédez-vous?
James Adam
ruby 1.8.7 (2010-06-23 patchlevel 299) [x86_64-linux]
Vikrant Chaudhary
19
Sur Ruby 1.9, m.__file__et m.__line__ont été remplacés par m.source_location.
Jonathan Tran
Et Ruby 2.1?
Lokesh
38

J'arrive tard sur ce sujet et je suis surpris que personne ne l'ait mentionné Method#owner.

class A; def hello; puts "hello"; end end
class B < A; end
b = B.new
b.method(:hello).owner
=> A
Alex D
la source
2
Je suis surpris que vous soyez le premier à référencer explicitement la classe Method . Un autre trésor moins connu introduit 1,9: Method#parameters.
fny
13

Copie de ma réponse à partir d'une question similaire plus récente qui ajoute de nouvelles informations à ce problème.

Ruby 1.9 a une méthode appelée source_location :

Renvoie le nom de fichier source Ruby et le numéro de ligne contenant cette méthode ou nil si cette méthode n'était pas définie dans Ruby (c'est-à-dire native)

Cela a été rétroporté à 1.8.7 par ce joyau:

Vous pouvez donc demander la méthode:

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

Et puis demandez le source_locationde cette méthode:

m.source_location

Cela renverra un tableau avec le nom de fichier et le numéro de ligne. Par exemple, pour ActiveRecord::Base#validatesce retour:

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]

Pour les classes et les modules, Ruby n'offre pas de support intégré, mais il existe un excellent Gist qui s'appuie sur source_locationpour renvoyer le fichier pour une méthode donnée ou le premier fichier pour une classe si aucune méthode n'a été spécifiée:

En action:

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]

Sur les Mac avec TextMate installé, cela fait également apparaître l'éditeur à l'emplacement spécifié.

Laas
la source
7

Cela peut aider, mais vous devrez le coder vous-même. Collé du blog:

Ruby fournit un rappel method_added () qui est appelé chaque fois qu'une méthode est ajoutée ou redéfinie dans une classe. Cela fait partie de la classe Module, et chaque classe est un module. Il existe également deux rappels associés appelés method_removed () et method_undefined ().

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

Ken
la source
6

Si vous pouvez planter la méthode, vous obtiendrez une trace qui vous dira exactement où elle se trouve.

Malheureusement, si vous ne pouvez pas le bloquer, vous ne pouvez pas savoir où il a été défini. Si vous tentez de simuler la méthode en la remplaçant ou en la remplaçant, tout plantage proviendra de votre méthode remplacée ou remplacée et ne sera d'aucune utilité.

Méthodes utiles pour planter les méthodes:

  1. Passez nillà où cela vous l'interdit - la plupart du temps, la méthode soulèvera un ArgumentErrorou le toujours présent NoMethodErrorsur une classe nulle.
  2. Si vous avez une connaissance interne de la méthode et que vous savez que la méthode appelle à son tour une autre méthode, vous pouvez remplacer l'autre méthode et augmenter à l'intérieur.
Orion Edwards
la source
Si vous avez accès au code, vous pouvez tout aussi bien insérer require 'ruby-debug'; debugger dans votre code où vous voulez vous y rendre.
The Tin Man
"Malheureusement, si vous ne pouvez pas le planter, vous ne pouvez pas savoir où il a été défini." Ce n'est pas vrai. Voir d'autres réponses.
Martin T.
6

Peut-être que cela #source_locationpeut aider à trouver d'où vient la méthode.

ex:

ModelName.method(:has_one).source_location

Revenir

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

OU

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

Revenir

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

Réponse très tardive :) Mais les réponses précédentes ne m'ont pas aidé

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
la source
Pourquoi tu passes nil?
Arup Rakshit
@ArupRakshit de la documentation: «Établit proc comme gestionnaire pour le traçage, ou désactive le traçage si le paramètre est nil
tig
3

Vous pourriez peut-être faire quelque chose comme ceci:

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

Assurez-vous ensuite que foo_finder est chargé en premier avec quelque chose comme

ruby -r foo_finder.rb railsapp

(Je n'ai fait que gâcher les rails, donc je ne sais pas exactement, mais j'imagine qu'il y a un moyen de le démarrer un peu comme ça.)

Cela vous montrera toutes les redéfinitions de String # foo. Avec un peu de méta-programmation, vous pouvez le généraliser pour la fonction que vous souhaitez. Mais il doit être chargé AVANT le fichier qui effectue réellement la redéfinition.

AShelly
la source
3

Vous pouvez toujours obtenir une trace de l'endroit où vous vous trouvez en utilisant caller().

l'homme d'étain
la source
1
C'est utile pour trouver ce qui vous a appelé, mais pas bon lorsque vous essayez de trouver où quelque chose a été défini.
le Tin Man du