Dada una clase, ver si la instancia tiene método (Ruby)


227

Sé en Ruby que puedo usar respond_to?para verificar si un objeto tiene un método determinado.

Pero, dada la clase, ¿cómo puedo verificar si la instancia tiene un método determinado?

es decir, algo como

Foo.new.respond_to?(:bar)

Pero siento que debe haber una mejor manera que crear una instancia nueva.

Respuestas:


364

No sé por qué todo el mundo sugiere que deberías estar usando instance_methodsy include?cuándo method_defined?hace el trabajo.

class Test
  def hello; end
end

Test.method_defined? :hello #=> true

NOTA

En caso de que venga a Ruby desde otro lenguaje OO O cree que eso method_definedsignifica SOLO métodos que definió explícitamente con:

def my_method
end

entonces lee esto:

En Ruby, una propiedad (atributo) en su modelo es básicamente un método también. Por method_defined?lo tanto , también devolverá verdadero para las propiedades, no solo los métodos.

Por ejemplo:

Dada una instancia de una clase que tiene un atributo String first_name:

<instance>.first_name.class #=> String

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

dado que first_namees tanto un atributo como un método (y una cadena de tipo String).


99
método_definido? es la mejor solución porque los métodos de instancia cambiaron entre Ruby 1.8 y 1.9. 1.8 devuelve una matriz de cadenas y 1.9 devuelve una matriz de símbolos. método_definido? toma un símbolo tanto en 1.8 como en 1.9.
Jason

2
Sin mencionar que: instance_methods creará una nueva matriz cada llamada y eso: ¿incluir? luego tendrá que recorrer la matriz para contar. En contraste, ¿método_definido? consultará internamente las tablas de búsqueda de métodos (una búsqueda hash que está en C) y le informará sin crear ningún objeto nuevo.
Asher

44
Si bien hay al menos una ventaja cuando lo usa de esta manera String.instance_methods(false).include? :freeze, el argumento falso puede decir exactamente si el freezemétodo está definido en la clase String o no. En este caso, devolverá falso porque freezeString hereda el método del módulo Kernel, pero String.method_defined? :freezesiempre devolverá verdadero, lo que no puede ayudar mucho con tal propósito.
ugoa

1
@Bane estás confundiendo métodos en la clase con métodos disponibles en la instancia. method_defined?debe usarse en la clase (p Array. ej. ), pero está intentando usarlo en instancias (p []. ej. )
horseyguy

@banister Absolutamente correcto. Gracias por la aclaración, tiene mucho sentido leerlo. :) He eliminado mi comentario para no confundir.
Bane

45

Puedes usar method_defined?lo siguiente:

String.method_defined? :upcase # => true

Mucho más fácil, portátil y eficiente de lo que instance_methods.include?todos los demás parecen estar sugiriendo.

Tenga en cuenta que no sabrá si una clase responde dinámicamente a algunas llamadas con method_missing, por ejemplo respond_to?, redefiniendo , o desde Ruby 1.9.2 definiendo respond_to_missing?.


2
Tenga en cuenta que si tiene una instancia en la mano y no sabe que es clase, puede hacerloinstance.class.method_defined? :upcase
jaydel el

25

En realidad, esto no funciona tanto para objetos como para clases.

Esto hace:

class TestClass
  def methodName
  end
end

Entonces, con la respuesta dada, esto funciona:

TestClass.method_defined? :methodName # => TRUE

Pero esto no funciona:

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

Así que uso esto para clases y objetos:

Clases

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

Objetos:

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

3
Para las instancias, también puede usar `instance.class.method_defined? '
Luksurious

Depende de tu versión de ruby. Este método anterior funciona para todas las versiones, incluida la 1.8.7.
Alex V

10

La respuesta a " Dada una clase, vea si la instancia tiene método (Ruby) " es mejor. Aparentemente, Ruby tiene esto incorporado, y de alguna manera lo extrañé. Mi respuesta se deja como referencia, independientemente.

Las clases de Ruby responden a los métodos instance_methodsy public_instance_methods. En Ruby 1.8, el primero enumera todos los nombres de métodos de instancia en una matriz de cadenas, y el segundo lo restringe a métodos públicos. El segundo comportamiento es lo que probablemente desearía, ya que también se respond_to?limita a los métodos públicos de forma predeterminada.

Foo.public_instance_methods.include?('bar')

Sin embargo, en Ruby 1.9, esos métodos devuelven matrices de símbolos.

Foo.public_instance_methods.include?(:bar)

Si planea hacer esto con frecuencia, es posible que desee ampliar Modulepara incluir un método de acceso directo. (Puede parecer extraño asignar esto a, en Modulelugar de Class, pero dado que allí es donde instance_methodsviven los métodos, es mejor mantenerse en línea con ese patrón).

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

Si desea admitir tanto Ruby 1.8 como Ruby 1.9, ese sería un lugar conveniente para agregar la lógica para buscar cadenas y símbolos también.


1
method_defined?es mejor :)
horseyguy

@banister: ¡ay, y de alguna manera lo extrañé! xD Lanzó un +1 detrás de la respuesta de @ Marc, y agregó un enlace para asegurarse de que no se pierda :)
Matchu


3

No estoy seguro si esta es la mejor manera, pero siempre puedes hacer esto:

Foo.instance_methods.include? 'bar'


3

Si está verificando si un objeto puede responder a una serie de métodos, podría hacer algo como:

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

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

Se methods & something.methodsunirán las dos matrices en sus elementos comunes / coincidentes. something.methods incluye todos los métodos que está buscando, será igual a los métodos. Por ejemplo:

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

entonces

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

En esta situación, querrá usar símbolos, porque cuando llama a .methods, devuelve una matriz de símbolos y, si lo usa ["my", "methods"], devolvería false.


2

klass.instance_methods.include :method_nameo "method_name", dependiendo de la versión de Ruby, creo.


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] 

¡Espero que esto te ayude!


1

En mi caso, trabajar con ruby ​​2.5.3 las siguientes oraciones han funcionado perfectamente:

value = "hello world"

value.methods.include? :upcase

Devolverá un valor booleano verdadero o falso.


1

Si bien respond_to?será verdadero solo para los métodos públicos, la verificación de la "definición de método" en una clase también puede pertenecer a métodos privados.

En Ruby v2.0 +, la comprobación de conjuntos públicos y privados se puede lograr con

Foo.private_instance_methods.include?(:bar) || Foo.instance_methods.include?(:bar)
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.