Для данного класса посмотрите, есть ли у экземпляра метод (Ruby)

227

В Ruby я знаю, что могу использовать, respond_to?чтобы проверить, есть ли у объекта определенный метод.

Но, учитывая класс, как я могу проверить, есть ли у экземпляра определенный метод?

то есть что-то вроде

Foo.new.respond_to?(:bar)

Но я чувствую, что должен быть лучший способ, чем создание нового экземпляра.

user94154
источник

Ответы:

364

Я не знаю, почему все предлагают, чтобы вы использовали instance_methodsи include?когда выполняли method_defined?работу.

class Test
  def hello; end
end

Test.method_defined? :hello #=> true

НОТА

В случае, если вы переходите на Ruby с другого языка OO или вы думаете, что это method_definedозначает ТОЛЬКО методы, которые вы явно определили с помощью:

def my_method
end

тогда прочитайте это:

В Ruby свойство (атрибут) вашей модели также является в основном методом. Так же method_defined?будет возвращать true для свойств, а не только для методов.

Например:

Учитывая экземпляр класса, который имеет атрибут String first_name:

<instance>.first_name.class #=> String

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

поскольку first_nameявляется как атрибутом, так и методом (и строкой типа String).

horseyguy
источник
9
method_defined? является лучшим решением, потому что instance_methods изменился между Ruby 1.8 и 1.9. 1.8 возвращает массив строк, а 1.9 возвращает массив символов. method_defined? принимает символ как в 1,8, так и в 1,9.
Джейсон
2
Не говоря уже о том, что: instance_methods будет создавать новый массив при каждом вызове и что: include? Затем придется пройти массив, чтобы сказать. Для сравнения: method_defined? внутренне запросит таблицы поиска методов (один поиск хэша в C) и сообщит вам, не создавая никаких новых объектов.
Ашер
4
Хотя при его использовании, по крайней мере, есть одно преимущество String.instance_methods(false).include? :freeze, ложный аргумент может точно сказать, определен ли freezeметод в классе String или нет. В этом случае он вернет false, потому что freezeметод унаследован от модуля Kernel от String, но String.method_defined? :freezeвсегда вернет true, что не может сильно помочь с такой целью.
Угоа
1
@Bane, вы путаете методы класса с методами, доступными в экземпляре. method_defined?должен быть использован в классе (например Array), но вы пытаетесь использовать его в экземплярах (например [])
horseyguy
@banister Абсолютно правильно. Спасибо за разъяснения, это имеет смысл читать. :) Я удалил свой комментарий, чтобы не путать.
Бэйн
45

Вы можете использовать method_defined?следующим образом:

String.method_defined? :upcase # => true

Гораздо проще, портативнее и эффективнее, чем instance_methods.include?кажется всем остальным.

Имейте в виду, что вы не будете знать, реагирует ли класс динамически на некоторые вызовы method_missing, например, путем переопределения respond_to?, или начиная с Ruby 1.9.2, путем определения respond_to_missing?.

Марк-Андре Лафортун
источник
2
Обратите внимание, что если у вас есть экземпляр в руках, и вы не знаете, его класс, вы можете сделатьinstance.class.method_defined? :upcase
jaydel
25

На самом деле это не работает как для объектов, так и для классов.

Это делает:

class TestClass
  def methodName
  end
end

Итак, с данным ответом это работает:

TestClass.method_defined? :methodName # => TRUE

Но это НЕ работает:

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

Поэтому я использую это для классов и объектов:

Классы:

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

Объекты:

t = TestClass.new
t.methods.include? 'methodName'  # => TRUE
Алекс V
источник
3
Для экземпляров вы также можете использовать `instance.class.method_defined? '
Luksurious
Это зависит от вашей версии ruby. Этот метод выше работает для всех версий, включая 1.8.7.
Алекс V
10

Ответ на « Данный класс, посмотреть, если экземпляр имеет метод (Ruby) » лучше. Очевидно, в Ruby есть эта встроенная система, и я почему-то пропустил ее. Мой ответ оставлен для справки, вне зависимости.

Классы Ruby отвечают на методы instance_methodsи public_instance_methods. В Ruby 1.8 первый перечисляет все имена методов экземпляра в массиве строк, а второй ограничивает его открытыми методами. Второе поведение - это то, что вы, скорее всего, захотите, так respond_to?как по умолчанию ограничивается публичными методами.

Foo.public_instance_methods.include?('bar')

В Ruby 1.9 эти методы возвращают массивы символов.

Foo.public_instance_methods.include?(:bar)

Если вы планируете делать это часто, возможно, вы захотите расширить Moduleего, добавив ярлык. (Может показаться странным назначать это Moduleвместо Class, но, поскольку именно там instance_methodsживут методы, лучше придерживаться этого шаблона.)

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

Если вы хотите поддерживать как Ruby 1.8, так и Ruby 1.9, было бы удобно добавить логику для поиска как строк, так и символов.

Matchu
источник
1
method_defined?лучше :)
скакун
@banister - о, мой, и я как-то пропустил это! xD бросил +1 за ответ @ Marc и добавил ссылку, чтобы убедиться, что он не пропущен :)
Matchu
5

Пытаться Foo.instance_methods.include? :bar

imightbeinatree в облачном пространстве
источник
3

Не уверен, что это лучший способ, но вы всегда можете сделать это:

Foo.instance_methods.include? 'bar'
dbyrne
источник
3

Я думаю, что с method_defined?Rails что-то не так . Это может быть непоследовательным или что-то, поэтому, если вы используете Rails, лучше использовать что-то изattribute_method?(attribute) .

« Проверка для method_defined?» в классах ActiveRecord не работает до момента создания экземпляра »- вопрос о несоответствии.

NoDisplayName
источник
3

Если вы проверяете, может ли объект отвечать на ряд методов, вы можете сделать что-то вроде:

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

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

the methods & something.methodsприсоединится к двум массивам их общих / совпадающих элементов. Что-то содержит методы, которые вы проверяете, это равнозначно методам. Например:

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

так

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

В этой ситуации вы захотите использовать символы, потому что когда вы вызываете .methods, он возвращает массив символов, и если вы использовали ["my", "methods"] , он возвращает false.

TalkativeTree
источник
2

klass.instance_methods.include :method_nameили "method_name", в зависимости от версии Ruby, я думаю.

yxhuvud
источник
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] 

Надеюсь, это поможет вам!

Раджив Каннав Шарма
источник
1

В моем случае, работающем с ruby ​​2.5.3, следующие предложения сработали отлично:

value = "hello world"

value.methods.include? :upcase

Он вернет логическое значение true или false.

Мануэль Лазо
источник
1

Пока respond_to? вернет true только для открытых методов, проверка «определения метода» в классе может также относиться к закрытым методам.

На Ruby v2.0 + можно проверять как публичные, так и частные наборы с помощью

Foo.private_instance_methods.include?(:bar) || Foo.instance_methods.include?(:bar)
Epigene
источник