Как проверить, определен ли класс?

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

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

Object.const_get("Amber")

или (в Rails)

"Amber".constantize

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

Моя первая мысль - использовать метод defined?, но он не различает уже существующие классы и те, которые этого не делают:

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

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

Ответ 1

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

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

>> Object.const_defined?('Account')
=> false
>> Account
=> Account(id: integer, username: string, google_api_key: string, created_at: datetime, updated_at: datetime, is_active: boolean, randomize_search_results: boolean, contact_url: string, hide_featured_results: boolean, paginate_search_results: boolean)
>> Object.const_defined?('Account')
=> true

Ответ 2

В рельсах это очень просто:

amber = "Amber".constantize rescue nil
if amber # nil result in false
    # your code here
end

Ответ 3

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

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

Похоже, что все ответы с использованием метода 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

Ответ 5

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

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

Ответ 6

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

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