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

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

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

Object.const_get("Amber")

или (в рельсах)

"Amber".constantize

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

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

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

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


person fearless_fool    schedule 22.04.2011    source источник
comment
defined? в этом примере делает именно то, что должен делать: он проверяет, определен ли метод constantize для объекта String. Неважно, содержит ли строка Object или AClassNameThatCouldNotPossilyExist.   -  person ToniTornado    schedule 08.05.2017


Ответы (6)


Как насчет 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
person ctcherry    schedule 22.04.2011
comment
идеально -- спасибо. что касается автозагрузчика, у IIRC есть способ узнать, что находится в списке автозагрузчика. Я откопаю это, если окажется, что это проблема. - person fearless_fool; 22.04.2011
comment
Это также соответствует вещам, которые не являются классами. - person mahemoff; 03.09.2015

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

amber = "Amber".constantize rescue nil
if amber # nil result in false
    # your code here
end
person Eiji    schedule 30.09.2015
comment
rescue был полезен, потому что иногда константы могут быть выгружены, и проверка с помощью const_defined? будет ложной. - person Spencer; 17.06.2016
comment
Подавление исключений не рекомендуется. Подробнее читайте здесь: github.com/bbatsov /ruby-style-guide#dont-hide-exceptions - person Andrew K; 11.09.2017
comment
@AndrewK: в Ruby очень часто используется спасение - конечно, я согласен, что это нехорошо; в мире Эликсира мы стараемся не делать этого, если в этом нет необходимости, но я видел, что многие люди используют спасение в Ruby - person Eiji; 12.09.2017
comment
@ Эйдзи, согласен. Я просто хотел упомянуть об этом, поскольку люди, плохо знакомые с Ruby, не знают, что это анти-шаблон, и его следует избегать. - person Andrew K; 12.09.2017

Вдохновленный ответом @ctcherry выше, вот «отправка метода безопасного класса», где 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
person fearless_fool    schedule 22.04.2011
comment
p.s.: если вам нравится этот ответ, пожалуйста, проголосуйте за ответ ctcherry, поскольку именно он указал мне правильное направление. - person fearless_fool; 22.04.2011

Казалось бы, все ответы с использованием метода 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
person TomDunning    schedule 21.03.2016

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

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
person Fred Willmore    schedule 22.08.2014

Другой подход, если вы тоже хотите получить класс. Возвращает 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
person mahemoff    schedule 03.09.2015