Dada uma classe, veja se a instância possui o método (Ruby)


227

Eu sei no Ruby que posso usar respond_to?para verificar se um objeto tem um determinado método.

Mas, dada a classe, como posso verificar se a instância tem um determinado método?

ou seja, algo como

Foo.new.respond_to?(:bar)

Mas sinto que deve haver uma maneira melhor do que instanciar uma nova instância.

Respostas:


364

Não sei por que todo mundo está sugerindo que você deveria usar instance_methodse include?quando method_defined?faz o trabalho.

class Test
  def hello; end
end

Test.method_defined? :hello #=> true

NOTA

Caso você esteja vindo para Ruby de outra linguagem OO OU você acha que method_definedsignifica APENAS métodos que você definiu explicitamente com:

def my_method
end

então leia isto:

No Ruby, uma propriedade (atributo) no seu modelo também é basicamente um método. Então method_defined?, também retornará true para propriedades, não apenas métodos.

Por exemplo:

Dada uma instância de uma classe que possui um atributo String first_name:

<instance>.first_name.class #=> String

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

uma vez que first_nameé um atributo e um método (e uma sequência do tipo String).


9
method_defined? é a melhor solução porque instance_methods mudou entre Ruby 1.8 e 1.9. 1.8 retorna uma matriz de seqüências de caracteres e 1.9 retorna uma matriz de símbolos. method_defined? recebe um símbolo em 1.8 e 1.9.
Jason

2
Sem mencionar que: instance_methods criará uma nova matriz a cada chamada e que: include? terá que percorrer a matriz para contar. Por outro lado,: method_defined? consultará internamente as tabelas de pesquisa de métodos (uma pesquisa de hash em C) e informará você sem criar novos objetos.
Asher

4
Embora exista pelo menos uma vantagem quando você o usa dessa maneira String.instance_methods(false).include? :freeze, o argumento false pode dizer exatamente se o freezemétodo está definido na classe String ou não. Nesse caso, ele retornará false porque o freezemétodo é herdado do módulo Kernel por String, mas String.method_defined? :freezesempre retornará true, o que não pode ajudar muito com esse objetivo.
ugoa

1
@Bane, você está confundindo métodos na classe com métodos disponíveis na instância. method_defined?deve ser utilizado na classe (por exemplo Array), mas você está tentando usá-lo em casos (por exemplo [])
horseyguy

@banister Absolutamente correto. Obrigado pelo esclarecimento, faz todo o sentido em lê-lo. :) Eu removi meu comentário para não confundir.
Bane

45

Você pode usar da method_defined?seguinte maneira:

String.method_defined? :upcase # => true

Muito mais fácil, portátil e eficiente do que instance_methods.include?todo mundo parece sugerir.

Lembre-se de que você não saberá se uma classe responde dinamicamente a algumas chamadas com method_missing, por exemplo, redefinindo respond_to?ou desde o Ruby 1.9.2, definindo respond_to_missing?.


2
Note que se você tem uma instância na mão e você não sabe sua classe, você pode fazerinstance.class.method_defined? :upcase
jaydel

25

Na verdade, isso não funciona para objetos e classes.

Isto faz:

class TestClass
  def methodName
  end
end

Então, com a resposta dada, isso funciona:

TestClass.method_defined? :methodName # => TRUE

Mas isso NÃO funciona:

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

Então, eu uso isso para classes e objetos:

Aulas:

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

Objetos:

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

3
Para instâncias, você também pode usar `instance.class.method_defined? '
Luksurious

Depende da sua versão do ruby. Este método acima funciona para todas as versões, incluindo 1.8.7.
Alex V

10

A resposta para " Dada uma classe, veja se a instância possui o método (Ruby) " é melhor. Aparentemente, Ruby tem isso embutido, e de alguma forma eu perdi. Minha resposta é deixada para referência, independentemente.

As classes Ruby respondem aos métodos instance_methodse public_instance_methods. No Ruby 1.8, o primeiro lista todos os nomes de métodos de instância em uma matriz de seqüências de caracteres, e o segundo o restringe a métodos públicos. O segundo comportamento é o que você provavelmente desejaria, uma vez que também se respond_to?restringe a métodos públicos.

Foo.public_instance_methods.include?('bar')

No Ruby 1.9, no entanto, esses métodos retornam matrizes de símbolos.

Foo.public_instance_methods.include?(:bar)

Se você planeja fazer isso com frequência, convém estender Modulepara incluir um método de atalho. (Pode parecer estranho atribuir isso ao Moduleinvés de Class, mas como é onde os instance_methodsmétodos vivem, é melhor manter-se alinhado com esse padrão.)

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

Se você deseja dar suporte ao Ruby 1.8 e ao Ruby 1.9, esse seria um local conveniente para adicionar a lógica para procurar por seqüências de caracteres e símbolos.


1
method_defined?é melhor :) #
3110 horseyguy

@banister - oh, que coisa, e eu de alguma forma perdi! xD jogou um +1 trás @ resposta de Marc, e adicionou um link para se certificar de que não está perdida :)
Matchu


3

Não tenho certeza se essa é a melhor maneira, mas você sempre pode fazer isso:

Foo.instance_methods.include? 'bar'


3

Se você estiver verificando se um objeto pode responder a uma série de métodos, faça algo como:

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

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

o methods & something.methodsunirá as duas matrizes em seus elementos comuns / correspondentes. something.methods inclui todos os métodos que você está verificando, serão iguais. Por exemplo:

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

tão

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

Nessa situação, você desejaria usar símbolos, porque quando você chama .methods, ele retorna uma matriz de símbolos e, se você o usou ["my", "methods"], retornaria false.


2

klass.instance_methods.include :method_nameou "method_name", dependendo da versão do Ruby, eu acho.


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 isso ajude você!


1

No meu caso, trabalhando com o ruby ​​2.5.3, as seguintes frases funcionaram perfeitamente:

value = "hello world"

value.methods.include? :upcase

Ele retornará um valor booleano verdadeiro ou falso.


1

Embora respond_to?retorne true somente para métodos públicos, a verificação de "definição de método" em uma classe também pode pertencer a métodos privados.

No Ruby v2.0 +, a verificação de conjuntos públicos e privados pode ser obtida com

Foo.private_instance_methods.include?(:bar) || Foo.instance_methods.include?(:bar)
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.