Как проверить, определен ли класс? - PullRequest
69 голосов
/ 22 апреля 2011

Как мне превратить строку в имя класса, но только если этот класс уже существует?

Если Amber * уже класс, я могу получить строку из классачерез:

Object.const_get("Amber")

или (в Rails)

"Amber".constantize

Но любой из них завершится с NameError: uninitialized constant Amber, если Amber еще не является классом.

MyПервой мыслью является использование метода defined?, но он не делает различий между классами, которые уже существуют, и классами, которые не существуют:

>> defined?("Object".constantize)
=> "method"
>> defined?("AClassNameThatCouldNotPossiblyExist".constantize)
=> "method"

Итак, как я могу проверить, если строка именует класс раньше?Я пытаюсь преобразовать это?(Хорошо, а как насчет блока begin / rescue для отлова ошибок NameError? Слишком некрасиво? Я согласен ...)

Ответы [ 6 ]

121 голосов
/ 22 апреля 2011

Как насчет const_defined??

Помните, что в Rails есть автоматическая загрузка в режиме разработки, поэтому при тестировании может возникнуть сложность:

17 голосов
/ 30 сентября 2015

В рельсах это действительно легко:

amber = "Amber".constantize rescue nil
if amber # nil result in false
    # your code here
end
13 голосов
/ 22 апреля 2011

Вдохновленный ответом @ ctcherry выше, вот «безопасный метод отправки класса», где class_name - строка.Если class_name не называет класс, он возвращает ноль.

def class_send(class_name, method, *args)
  Object.const_defined?(class_name) ? Object.const_get(class_name).send(method, *args) : nil
end

Еще более безопасная версия, которая вызывает method, только если class_name отвечает на нее:

def class_send(class_name, method, *args)
  return nil unless Object.const_defined?(class_name)
  c = Object.const_get(class_name)
  c.respond_to?(method) ? c.send(method, *args) : nil
end
4 голосов
/ 21 марта 2016

Может показаться, что все ответы, использующие метод Object.const_defined?, ошибочны. Если рассматриваемый класс еще не был загружен из-за отложенной загрузки, то утверждение не будет выполнено. Единственный способ достичь этого окончательно таков:

  validate :adapter_exists

  def adapter_exists
    # cannot use const_defined because of lazy loading it seems
    Object.const_get("Irs::#{adapter_name}")
  rescue NameError => e
    errors.add(:adapter_name, 'does not have an IrsAdapter')
  end
2 голосов
/ 22 августа 2014

Я создал валидатор для проверки, является ли строка допустимым именем класса (или список допустимых имен классов, разделенных запятыми):

class ClassValidator < ActiveModel::EachValidator
  def validate_each(record,attribute,value)
    unless value.split(',').map { |s| s.strip.constantize.is_a?(Class) rescue false }.all?
      record.errors.add attribute, 'must be a valid Ruby class name (comma-separated list allowed)'
    end
  end
end
1 голос
/ 03 сентября 2015

Другой подход, если вы хотите получить класс тоже.Вернет ноль, если класс не определен, поэтому вам не нужно перехватывать исключение.

class String
  def to_class(class_name)
    begin
      class_name = class_name.classify (optional bonus feature if using Rails)
      Object.const_get(class_name)
    rescue
      # swallow as we want to return nil
    end
  end
end

> 'Article'.to_class
class Article

> 'NoSuchThing'.to_class
nil

# use it to check if defined
> puts 'Hello yes this is class' if 'Article'.to_class
Hello yes this is class
...