web-dev-qa-db-fra.com

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

Nous avons récemment eu un problème où, après une série de commits, un processus back-end a échoué. Maintenant, nous étions de bons petits garçons et filles et nous exécutions rake test Aprè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 de Mongrel en mode production.

J'ai suivi le bogue et cela était dû à un nouveau Rails gem écrasant une méthode de la classe String de manière à rompre une utilisation étroite à l'exécution Rails = code.

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

Je ne peux pas garantir la source des données dans mon projet, donc grepping pour 'def foo' Ne me donnera pas nécessairement ce dont j'ai besoin, sans mentionner si j'ai plusieursdef foo ' s, parfois je ne sais pas jusqu'au moment de l'exécution de celle que je pourrais utiliser.

324
Matt Rogish

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 êtes sur 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é. Le Method class a aussi quelques fonctions intéressantes, comme Method # owner qui renvoie le fichier où la méthode est définie.

EDIT: Voir aussi le __file__ et __line__ et notes pour REE dans l’autre réponse, ils sont également pratiques. - wg

410
wesgarrison

Vous pouvez réellement aller un peu plus loin que la solution ci-dessus. Pour Ruby 1.8 Enterprise Edition, il y a le __file__ et __line__ méthodes sur les instances 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

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]
81
James Adam

J'arrive en retard sur ce fil, et je suis surpris que personne ne mentionne Method#owner.

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

Copier ma réponse d’un plus récent question similaire qui ajoute de nouvelles informations à ce problème.

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

Renvoie le Ruby et le numéro de ligne contenant cette méthode ou nil si cette méthode n'a pas été définie dans Ruby (c'est-à-dire natif)

Cela a été rétroporté à 1.8.7 par cette gemme:

Vous pouvez donc demander la méthode:

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

Et ensuite demander le source_location de cette méthode:

m.source_location

Cela retournera un tableau avec nom de fichier et numéro de ligne. E.g pour ActiveRecord::Base#validates ceci retourne:

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_location pour retourner le fichier d’une méthode donnée ou le premier fichier d’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 sur lesquels TextMate est installé, l’éditeur s’affiche également à l’emplacement spécifié.

12
Laas

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

6
Ken

Si vous pouvez bloquer la méthode, vous obtiendrez une trace qui vous indiquera 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 essayez de singer avec la méthode en la écrasant ou en la surchargeant, alors tout blocage proviendra de votre méthode écrasée ou surchargée et ne sera d'aucune utilisation.

Moyens utiles de planter des méthodes:

  1. Passez nil là où elle l’interdit - la plupart du temps, la méthode lève un ArgumentError ou le NoMethodError toujours présent sur une classe nil.
  2. Si vous avez une connaissance approfondie de la méthode et que vous savez que la méthode appelle à son tour une autre méthode, vous pouvez remplacer cette méthode et la relancer à l'intérieur de celle-ci.
5
Orion Edwards

Peut-être le #source_location peut 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]

OR

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]
5
Samda

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
3
tig

Vous pouvez toujours obtenir une trace de votre position en utilisant caller().

2
Garry

Vous pourriez peut-être faire quelque chose comme ça:

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

Ensuite, assurez-vous que foo_Finder est chargé en premier avec quelque chose comme

Ruby -r foo_Finder.rb railsapp

(Je me suis seulement contenté de Rails, donc je ne sais pas exactement, mais j'imagine qu'il y a un moyen de commencer 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 de votre choix. Mais il doit être chargé AVANT le fichier qui fait la redéfinition.

2
AShelly