Как проверить, определен ли класс?
Как превратить строку в имя класса, но только если этот класс уже существует?
Если 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