que - ruby on rails tutorial
¿Cómo puedo verificar si una clase está definida? (6)
¿Qué tal const_defined?
?
Recuerde en Rails, hay carga automática en modo de desarrollo, por lo que puede ser complicado cuando lo está probando:
>> 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
¿Cómo convierto una cadena en un nombre de clase, pero solo si esa clase ya existe?
Si Amber ya es una clase, puedo pasar de una cadena a la clase a través de:
Object.const_get("Amber")
o (en Rails)
"Amber".constantize
Pero cualquiera de estos fallará con NameError: uninitialized constant Amber
si Amber aún no es una clase.
Mi primer pensamiento es usar el defined?
método, pero no discrimina entre las clases que ya existen y las que no:
>> defined?("Object".constantize)
=> "method"
>> defined?("AClassNameThatCouldNotPossiblyExist".constantize)
=> "method"
Entonces, ¿cómo pruebo si una cadena nombra una clase antes de intentar convertirla? (Bien, ¿qué tal un bloque de begin
/ rescue
para detectar errores de NameError? ¿Demasiado feo? Estoy de acuerdo ...)
Creé un validador para probar si una cadena es un nombre de clase válido (o una lista de nombres de clase válidos separados por comas):
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
En los rieles es realmente fácil:
amber = "Amber".constantize rescue nil
if amber # nil result in false
# your code here
end
Inspirado por la respuesta de @ ctcherry anterior, aquí hay un ''envío de método de clase segura'', 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 segura que invoca el method
solo 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
Otro enfoque, en caso de que quiera 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
Parecería que todas las respuestas usando el Object.const_defined?
método son defectuosos. Si la clase en cuestión no se ha cargado todavía, debido a la carga diferida, la aserción fallará. La única forma 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