Überprüfen Sie bei einer gegebenen Klasse, ob die Instanz eine Methode hat (Ruby).

227

Ich weiß in Ruby, dass ich verwenden kann respond_to? damit überprüfen , ob ein Objekt eine bestimmte Methode hat.

Aber wie kann ich angesichts der Klasse überprüfen, ob die Instanz eine bestimmte Methode hat?

dh so etwas wie

Foo.new.respond_to?(:bar)

Aber ich denke, es muss einen besseren Weg geben, als eine neue Instanz zu instanziieren.

user94154
quelle

Antworten:

364

Ich weiß nicht, warum alle vorschlagen, dass Sie verwenden sollten instance_methodsund include?wann method_defined?der Job erledigt wird.

class Test
  def hello; end
end

Test.method_defined? :hello #=> true

HINWEIS

Wenn Sie aus einer anderen OO-Sprache zu Ruby kommen oder der Meinung sind, dass method_defineddies NUR Methoden bedeutet, die Sie explizit definiert haben mit:

def my_method
end

dann lies das:

In Ruby ist eine Eigenschaft (ein Attribut) in Ihrem Modell im Grunde auch eine Methode. Dies method_defined?gilt auch für Eigenschaften, nicht nur für Methoden.

Beispielsweise:

Bei einer Instanz einer Klasse mit einem String-Attribut first_name:

<instance>.first_name.class #=> String

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

da first_nameist sowohl ein Attribut als auch eine Methode (und eine Zeichenfolge vom Typ Zeichenfolge).

horseyguy
quelle
9
method_defined? ist die beste Lösung, da instance_methods zwischen Ruby 1.8 und 1.9 geändert wurde. 1.8 gibt ein Array von Zeichenfolgen zurück und 1.9 gibt ein Array von Symbolen zurück. method_defined? nimmt ein Symbol in 1.8 und 1.9.
Jason
2
Ganz zu schweigen davon, dass: instance_methods bei jedem Aufruf ein neues Array erstellt und dass: include? muss dann das Array gehen, um zu sagen. Im Gegensatz dazu: method_defined? fragt intern die Methoden-Nachschlagetabellen ab (eine Hash-Suche in C) und informiert Sie, ohne neue Objekte zu erstellen.
Asher
4
Während es mindestens einen Vorteil gibt, wenn Sie es so verwenden, String.instance_methods(false).include? :freezekann das falsche Argument genau sagen, ob die freezeMethode in der String-Klasse definiert ist oder nicht. In diesem Fall wird false zurückgegeben, da die freezeMethode von String vom Kernel-Modul geerbt wird, aber String.method_defined? :freezeimmer true zurückgegeben, was bei einem solchen Zweck nicht viel helfen kann.
Ugoa
1
@Bane Sie verwechseln Methoden in der Klasse mit Methoden, die in der Instanz verfügbar sind. method_defined?muss für die Klasse verwendet werden (z. B. Array), aber Sie versuchen, es für Instanzen zu verwenden (z. B. [])
horseyguy
@banister Absolut richtig. Vielen Dank für die Klarstellung, es macht durchaus Sinn, es zu lesen. :) Ich habe meinen Kommentar entfernt, um nicht zu verwirren.
Bane
45

Sie können method_defined?wie folgt verwenden:

String.method_defined? :upcase # => true

Viel einfacher, tragbarer und effizienter als die instance_methods.include? alle anderen vermuten.

Denken Sie daran, dass Sie nicht wissen, ob eine Klasse dynamisch auf einige Aufrufe reagiert method_missing, z. B. durch Neudefinition respond_to?oder seit Ruby 1.9.2 durch Definieren respond_to_missing?.

Marc-André Lafortune
quelle
2
Beachten Sie, dass Sie, wenn Sie eine Instanz in der Hand haben und nicht wissen, dass es sich um eine Klasse handelt, dies tun könneninstance.class.method_defined? :upcase
jaydel
25

Tatsächlich funktioniert dies nicht für Objekte und Klassen.

Das macht:

class TestClass
  def methodName
  end
end

Mit der gegebenen Antwort funktioniert dies also:

TestClass.method_defined? :methodName # => TRUE

Das funktioniert aber NICHT:

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

Also benutze ich dies sowohl für Klassen als auch für Objekte:

Klassen:

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

Objekte:

t = TestClass.new
t.methods.include? 'methodName'  # => TRUE
Alex V.
quelle
3
Für Instanzen können Sie auch "instance.class.method_defined?"
Luksurious
Es hängt von Ihrer Version von Ruby ab. Diese Methode funktioniert für alle Versionen einschließlich 1.8.7.
Alex V
10

Die Antwort auf " Bei einer gegebenen Klasse prüfen, ob die Instanz eine Methode (Ruby) hat " ist besser. Anscheinend hat Ruby dieses eingebaut und ich habe es irgendwie verpasst. Meine Antwort bleibt unabhängig davon als Referenz.

Ruby-Klassen reagieren auf die Methoden instance_methodsund public_instance_methods. In Ruby 1.8 listet der erste alle Instanzmethodennamen in einem Array von Zeichenfolgen auf und der zweite beschränkt ihn auf öffentliche Methoden. Das zweite Verhalten ist das, was Sie höchstwahrscheinlich wollen, da es respond_to?sich standardmäßig auch auf öffentliche Methoden beschränkt.

Foo.public_instance_methods.include?('bar')

In Ruby 1.9 geben diese Methoden jedoch Arrays von Symbolen zurück.

Foo.public_instance_methods.include?(:bar)

Wenn Sie dies häufig planen, möchten Sie möglicherweise Moduleeine Verknüpfungsmethode hinzufügen. (Es mag seltsam erscheinen, dies Modulestattdessen zuzuweisen Class, aber da dort die instance_methodsMethoden leben, ist es am besten, sich an dieses Muster zu halten.)

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

Wenn Sie sowohl Ruby 1.8 als auch Ruby 1.9 unterstützen möchten, ist dies ein praktischer Ort, um die Logik für die Suche nach Zeichenfolgen und Symbolen hinzuzufügen.

Matchu
quelle
1
method_defined?ist besser :)
horseyguy
@banister - oh mein Gott, und ich habe es irgendwie verpasst! xD warf eine +1 hinter @ Marc Antwort und fügte einen Link hinzu, um sicherzustellen, dass es nicht verpasst wird :)
Matchu
5

Versuchen Foo.instance_methods.include? :bar

imightbeinatree bei Cloudspace
quelle
3

Ich bin mir nicht sicher, ob dies der beste Weg ist, aber Sie können dies immer tun:

Foo.instance_methods.include? 'bar'
dbyrne
quelle
3

Wenn Sie überprüfen, ob ein Objekt auf eine Reihe von Methoden reagieren kann, können Sie Folgendes tun:

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

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

Sie methods & something.methodsverbinden die beiden Arrays mit ihren gemeinsamen / übereinstimmenden Elementen. Something.methods enthält alle Methoden, nach denen Sie suchen. Es entspricht den Methoden. Beispielsweise:

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

so

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

In dieser Situation möchten Sie Symbole verwenden, da beim Aufrufen von .methods ein Array von Symbolen zurückgegeben wird und wenn Sie diese verwendet haben ["my", "methods"] false zurückgegeben wird.

TalkativeTree
quelle
2

klass.instance_methods.include :method_nameoder "method_name", abhängig von der Ruby-Version, denke ich.

yxhuvud
quelle
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] 

Hoffe das hilft dir!

Rajeev Kannav Sharma
quelle
1

In meinem Fall mit Ruby 2.5.3 haben die folgenden Sätze perfekt funktioniert:

value = "hello world"

value.methods.include? :upcase

Es wird ein boolescher Wert true oder false zurückgegeben.

Manuel Lazo
quelle
1

Während respond_to? nur für öffentliche Methoden true zurückgegeben wird, kann die Überprüfung auf "Methodendefinition" für eine Klasse auch private Methoden betreffen.

Unter Ruby v2.0 + können sowohl öffentliche als auch private Sets überprüft werden

Foo.private_instance_methods.include?(:bar) || Foo.instance_methods.include?(:bar)
Epigen
quelle