Дано клас, подивіться, чи має примірник метод (Ruby)


227

Я знаю в Ruby, що можу respond_to?перевірити, чи є в об'єкта певний метод.

Але, зважаючи на клас, як я можу перевірити, чи має примірник певний метод?

тобто щось подібне

Foo.new.respond_to?(:bar)

Але я відчуваю, що має бути кращий спосіб, ніж створення нової інстанції.

Відповіді:


364

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

class Test
  def hello; end
end

Test.method_defined? :hello #=> true

ПРИМІТКА

Якщо ви приїжджаєте до Ruby з іншої мови ООС, або ви думаєте, що це method_definedозначає ТІЛЬКИ методи, які ви чітко визначили:

def my_method
end

потім прочитайте це:

У Ruby властивість (атрибут) для вашої моделі також є методом. Так method_defined?само повернеться вірно для властивостей, а не лише методів.

Наприклад:

З огляду на екземпляр класу, який має атрибут String first_name:

<instance>.first_name.class #=> String

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

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


9
метод_визначено? є найкращим рішенням, оскільки instance_methods змінилися між Ruby 1.8 та 1.9. 1.8 повертає масив рядків, а 1.9 повертає масив символів. метод_визначено? приймає символ як у 1.8, так і в 1.9.
Джейсон

2
Не кажучи вже про те, що: instance_methods створить новий масив кожного виклику і що: include? Тоді доведеться пройтися масивом, щоб сказати. На відміну від: method_defined? внутрішній запит таблиць пошуку методів (один пошук хешу, який знаходиться в C), і повідомить вас, не створюючи нових об'єктів.
Ашер

4
Незважаючи на те, що при використанні цього принаймні є одна перевага String.instance_methods(false).include? :freeze, помилковий аргумент може точно вказати, freezeвизначений метод у класі String чи ні. У цьому випадку він повернеться хибним, оскільки freezeметод успадковується від модуля Kernel String, але String.method_defined? :freezeзавжди поверне true, що не може сильно допомогти з такою метою.
ugoa

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

3
Для примірників ви також можете використовувати `instance.class.method_defined? '
Luksurious

Це залежить від вашої версії рубіну. Цей спосіб працює вище для всіх версій, включаючи 1.8.7.
Олексій V

10

Відповідь на " Даний клас, подивіться, чи інстанція має метод (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, це було б зручним місцем для додавання логіки і для пошуку і рядків, і символів.


1
method_defined?краще :)
horseyguy

@banister - о, мій, і я якось пропустив це! xD Кинув +1 за відповідь @ Марка та додав посилання, щоб переконатися, що його не пропустили :)
Matchu


3

Не впевнений, що це найкращий спосіб, але ви завжди можете це зробити:

Foo.instance_methods.include? 'bar'

3

Я думаю, що method_defined?в Rails щось не так . Це може бути непослідовно чи щось таке, тому якщо ви використовуєте Rails, то краще використовувати щось із attribute_method?(attribute).

" тестування на method_defined? на класах ActiveRecord не працює, поки екземпляр " не викликає непослідовність.


3

Якщо ви перевіряєте, чи може об’єкт реагувати на низку методів, ви можете зробити щось на кшталт:

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

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

the methods & something.methodswill приєднає два масиви на їх загальних / відповідних елементах. something.methods включає всі методи, які ви перевіряєте, вони будуть однаковими методами. Наприклад:

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

так

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

У цій ситуації ви хочете використовувати символи, тому що, коли ви викликаєте .methods, він повертає масив символів, і якщо ви використовували ["my", "methods"], він повертає помилкові.


2

klass.instance_methods.include :method_nameабо "method_name", залежно від версії Ruby, яку я думаю.


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

У моєму випадку, що працює з рубіном 2.5.3, відмінно спрацювали наступні пропозиції:

value = "hello world"

value.methods.include? :upcase

Він поверне булеве значення true чи false.


1

Хоча respond_to?повернеться істинно лише для публічних методів, перевірка на "визначення методу" на класі може також стосуватися приватних методів.

На Ruby v2.0 + можна перевірити як державні, так і приватні набори за допомогою

Foo.private_instance_methods.include?(:bar) || Foo.instance_methods.include?(:bar)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.