2011-04-22 48 views
58

¿Cómo convierto una cadena en un nombre de clase, pero solo si esa clase ya existe?¿Cómo verifico si una clase está definida?

Si ámbar es ya una clase, que puedo conseguir de una cadena a la clase a través de:

Object.const_get("Amber") 

o (en Rails)

"Amber".constantize 

Pero ninguno de estos fallará con NameError: uninitialized constant Amber si Amber ya no es una clase.

Mi primera idea es utilizar el método defined?, pero no discrimina entre las clases que ya existen y las que no lo hacen:

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

Entonces, ¿cómo puedo probar si una cadena nombra una clase antes de intentar convertirlo? (Bien, ¿qué tal un bloque begin/rescue para detectar errores NameError? Demasiado feo? Estoy de acuerdo ...)

+0

'' definido en el ejemplo está haciendo exactamente lo que se supone que debe hacer: Se comprueba si se ha definido el método 'constantize' en un objeto String. No importa si la cadena contiene "Object" o "AClassNameThatCouldNotPossiblyExist". – ToniTornado

Respuesta

105

¿Qué tal const_defined??

Recuerde que en los carriles, no hay carga automática en el modo de desarrollo, por lo que puede ser difícil cuando se está probando hacia fuera:

>> 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

perfecto - gracias. en cuanto al cargador automático, IIRC hay una forma de averiguar qué hay en la lista del autocargador. Lo desenterraré si resulta ser un problema. –

+0

gracias, justo lo que necesito = D – Alexis

+3

Esto también coincide con cosas que no son clases. – mahemoff

10

Inspirado por la respuesta de @ ctcherry anterior, aquí hay un 'seguro método send clase ', donde class_name es una cadena. Si class_name no nombra una clase, devuelve nil.

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

Una versión aún más seguro que invoca method sólo si class_name responde a ella:

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 
+2

p.s.: si le gusta esta respuesta, por favor vote la respuesta de ctcherry, ya que eso es lo que me apuntó en la dirección correcta. –

0

He creado un validador para probar si una cadena es un nombre de clase válido (o lista separada por comas de nombres de clase válidos):

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

Otro enfoque, en caso de que desee obtener la clase también. Devolverá nil si la clase no está definida, por lo que no tiene que atrapar una excepción.

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 
11

En los carriles que es muy fácil:

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

El 'rescate 'fue útil porque a veces las constantes se pueden descargar y comprobar con' const_defined? 'Será falso. – Spencer

+0

¡Gracias! –

+0

Suprimir excepciones no es recomendable, lea más aquí: https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions –

2

Parecería que todas las respuestas utilizando el método Object.const_defined? son erróneas. Si la clase en cuestión no se ha cargado todavía, debido a la carga diferida, la aserción fallará. La única manera de lograr esto definitivamente es así:?

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 
Cuestiones relacionadas