¿Cómo verifico si una clase está definida?


58

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

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". 08 may. 172017-05-08 15:34:40

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. 22 abr. 112011-04-22 18:36:46

  0

gracias, justo lo que necesito = D 17 abr. 152015-04-17 04:55:35

+3

Esto también coincide con cosas que no son clases. 03 sep. 152015-09-03 17:47:26


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. 22 abr. 112011-04-22 18:54:49


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. 17 jun. 162016-06-17 18:52:29

  0

¡Gracias! 11 feb. 172017-02-11 12:16:40

  0

Suprimir excepciones no es recomendable, lea más aquí: https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions 11 sep. 172017-09-11 19:23:25

  0

@AndrewK: en Ruby rescue se usa muy a menudo, estoy de acuerdo en que es no está bien; en el mundo de Elixir tratamos de no hacer esto si no hay necesidad de hacerlo, pero vi que mucha gente usa el rescate en Ruby 12 sep. 172017-09-12 19:43:44

  0

@Eiji, de acuerdo. Solo quería mencionarlo ya que las personas nuevas en Ruby no saben que es un antipatrón y debe evitarse. 12 sep. 172017-09-12 20:56:38


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