Étant donné une classe, voir si l'instance a une méthode (Ruby)

227

Je sais dans Ruby que je peux utiliser respond_to?pour vérifier si un objet a une certaine méthode.

Mais, compte tenu de la classe, comment puis-je vérifier si l'instance a une certaine méthode?

c'est-à-dire quelque chose comme

Foo.new.respond_to?(:bar)

Mais j'ai l'impression qu'il doit y avoir un meilleur moyen que d'instancier une nouvelle instance.

user94154
la source

Réponses:

364

Je ne sais pas pourquoi tout le monde suggère que vous devriez utiliser instance_methodset include?quand method_defined?fait le travail.

class Test
  def hello; end
end

Test.method_defined? :hello #=> true

REMARQUE

Dans le cas où vous venez vers Ruby à partir d'un autre langage OO OU vous pensez que cela method_definedsignifie UNIQUEMENT des méthodes que vous avez définies explicitement avec:

def my_method
end

puis lisez ceci:

Dans Ruby, une propriété (attribut) sur votre modèle est également une méthode. Donc, method_defined?cela retournera également true pour les propriétés, pas seulement pour les méthodes.

Par exemple:

Étant donné une instance d'une classe qui a un attribut String first_name:

<instance>.first_name.class #=> String

<instance>.class.method_defined?(:first_name) #=> true

puisqu'il first_names'agit à la fois d'un attribut et d'une méthode (et d'une chaîne de type String).

horseyguy
la source
9
défini par méthode? est la meilleure solution car instance_methods a changé entre Ruby 1.8 et 1.9. 1.8 renvoie un tableau de chaînes et 1.9 renvoie un tableau de symboles. défini par méthode? prend un symbole à la fois 1.8 et 1.9.
Jason
2
Sans oublier que: instance_methods créera un nouveau tableau à chaque appel et que: include? devra alors parcourir le tableau pour le dire. En revanche,: method_defined? interrogera en interne les tables de recherche de méthode (une recherche de hachage en C) et vous le fera savoir sans créer de nouveaux objets.
Asher
4
Bien qu'il y ait au moins un avantage lorsque vous l'utilisez comme ceci, String.instance_methods(false).include? :freezele faux argument peut indiquer exactement si la freezeméthode est définie dans la classe String ou non. Dans ce cas, il retournera false car la freezeméthode est héritée du module du noyau par String, mais String.method_defined? :freezeretournera toujours true, ce qui ne peut pas aider beaucoup à cet effet.
ugoa
1
@Bane vous confondez les méthodes de la classe avec les méthodes disponibles sur l'instance. method_defined?doit être utilisé sur la classe (par exemple Array), mais vous essayez de l'utiliser sur des instances (par exemple [])
horseyguy
@banister Absolument correct. Merci pour la clarification, il est parfaitement logique de le lire. :) J'ai supprimé mon commentaire afin de ne pas confondre.
Bane
45

Vous pouvez utiliser method_defined?comme suit:

String.method_defined? :upcase # => true

Beaucoup plus facile, portable et efficace que instance_methods.include?tout le monde semble le suggérer.

Gardez à l'esprit que vous ne saurez pas si une classe répond dynamiquement à certains appels avec method_missing, par exemple en redéfinissant respond_to?, ou depuis Ruby 1.9.2 en définissant respond_to_missing?.

Marc-André Lafortune
la source
2
Notez que si vous avez une instance en main et que vous ne savez pas que c'est une classe, vous pouvez le faireinstance.class.method_defined? :upcase
jaydel
25

En fait, cela ne fonctionne pas pour les objets et les classes.

Cela fait:

class TestClass
  def methodName
  end
end

Donc, avec la réponse donnée, cela fonctionne:

TestClass.method_defined? :methodName # => TRUE

Mais cela ne fonctionne PAS:

t = TestClass.new
t.method_defined? : methodName  # => ERROR!

J'utilise donc ceci pour les classes et les objets:

Des classes:

TestClass.methods.include? 'methodName'  # => TRUE

Objets:

t = TestClass.new
t.methods.include? 'methodName'  # => TRUE
Alex V
la source
3
Pour les instances, vous pouvez également utiliser `instance.class.method_defined? '
Luksurious
Cela dépend de votre version de ruby. Cette méthode ci-dessus fonctionne pour toutes les versions, y compris 1.8.7.
Alex V
10

La réponse à " Étant donné une classe, voyez si l'instance a une méthode (Ruby) " est meilleure. Apparemment, Ruby a cette fonction intégrée, et je l'ai ratée. Ma réponse est laissée pour référence, peu importe.

Les classes Ruby répondent aux méthodes instance_methodset public_instance_methods. Dans Ruby 1.8, le premier répertorie tous les noms de méthode d'instance dans un tableau de chaînes et le second le restreint aux méthodes publiques. Le deuxième comportement est celui que vous souhaiteriez le plus, car il se respond_to?limite également aux méthodes publiques par défaut.

Foo.public_instance_methods.include?('bar')

Dans Ruby 1.9, cependant, ces méthodes renvoient des tableaux de symboles.

Foo.public_instance_methods.include?(:bar)

Si vous prévoyez de le faire souvent, vous souhaiterez peut-être étendre Modulepour inclure une méthode de raccourci. (Il peut sembler étrange d'affecter cela à Moduleau lieu de Class, mais puisque c'est là que vivent les instance_methodsméthodes, il est préférable de rester en ligne avec ce modèle.)

class Module
  def instance_respond_to?(method_name)
    public_instance_methods.include?(method_name)
  end
end

Si vous souhaitez prendre en charge Ruby 1.8 et Ruby 1.9, ce serait un endroit pratique pour ajouter la logique de recherche de chaînes et de symboles.

Matchu
la source
1
method_defined?c'est mieux :)
horseyguy
@banister - oh, mon, et je l'ai manqué en quelque sorte! xD Jette un +1 derrière la réponse de @ Marc et ajoute un lien pour s'assurer qu'il ne soit pas manqué :)
Matchu
5

Essayer Foo.instance_methods.include? :bar

imightbeinatree chez Cloudspace
la source
3

Je ne sais pas si c'est la meilleure façon, mais vous pouvez toujours le faire:

Foo.instance_methods.include? 'bar'
dbyrne
la source
3

Si vous vérifiez si un objet peut répondre à une série de méthodes, vous pouvez faire quelque chose comme:

methods = [:valid?, :chase, :test]

def has_methods?(something, methods)
  methods & something.methods == methods
end

le methods & something.methodsjoindra les deux tableaux sur leurs éléments communs / correspondants. Quelque chose.methods inclut toutes les méthodes que vous recherchez, cela égalera les méthodes. Par exemple:

[1,2] & [1,2,3,4,5]
==> [1,2]

alors

[1,2] & [1,2,3,4,5] == [1,2]
==> true

Dans cette situation, vous souhaitez utiliser des symboles, car lorsque vous appelez .methods, il renvoie un tableau de symboles et si vous l'utilisez ["my", "methods"], il renvoie false.

TalkativeTree
la source
2

klass.instance_methods.include :method_nameou "method_name", selon la version Ruby je pense.

yxhuvud
la source
2
class Foo
 def self.fclass_method
 end
 def finstance_method
 end
end

foo_obj = Foo.new
foo_obj.class.methods(false)
=> [:fclass_method] 

foo_obj.class.instance_methods(false)
=> [:fclass_method] 

J'espère que cela vous aide!

Rajeev Kannav Sharma
la source
1

Sur mon cas de travail avec ruby ​​2.5.3, les phrases suivantes ont parfaitement fonctionné:

value = "hello world"

value.methods.include? :upcase

Il renverra une valeur booléenne true ou false.

Manuel Lazo
la source
1

Bien que respond_to?renvoie true uniquement pour les méthodes publiques, la vérification de la "définition de méthode" sur une classe peut également concerner des méthodes privées.

Sur Ruby v2.0 +, la vérification des ensembles publics et privés peut être réalisée avec

Foo.private_instance_methods.include?(:bar) || Foo.instance_methods.include?(:bar)
Epigene
la source