Comment vérifier si une classe est définie?

Comment puis-je transformer une chaîne en nom de classe, mais uniquement si cette classe existe déjà?

Si Amber est déjà une classe, je peux passer d’une chaîne à la classe via:

Object.const_get("Amber") 

ou (en Rails)

 "Amber".constantize 

Mais l’un ou l’autre échouera avec NameError: uninitialized constant Amber si Amber n’est pas déjà une classe.

Ma première pensée est d’utiliser le defined? méthode, mais elle ne fait pas de distinction entre les classes qui existent déjà et celles qui ne le font pas:

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

Alors, comment tester si une chaîne désigne une classe avant d’essayer de la convertir? (Ok, que diriez-vous d’un bloc begin / rescue pour détecter les erreurs NameError? Trop moche? Je suis d’accord …)

Qu’en est-il de const_defined? ?

Rappelez-vous dans Rails, il y a un chargement automatique en mode développement, il peut donc être difficile de le tester:

 >> Object.const_defined?('Account') => false >> Account => Account(id: integer, username: ssortingng, google_api_key: ssortingng, created_at: datetime, updated_at: datetime, is_active: boolean, randomize_search_results: boolean, contact_url: ssortingng, hide_featured_results: boolean, paginate_search_results: boolean) >> Object.const_defined?('Account') => true 

Dans les rails, c’est vraiment facile:

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

Inspiré par la réponse de @ctcherry ci-dessus, voici une «méthode de classe sécurisée send», où class_name est une chaîne. Si class_name ne class_name pas une classe, il renvoie nil.

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

Une version encore plus sûre qui invoque la method uniquement si class_name répond:

 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 

Il semblerait que toutes les réponses utilisant Object.const_defined? la méthode est imparfaite. Si la classe en question n’a pas encore été chargée en raison d’un chargement différé, l’assertion échoue. La seule façon d’y parvenir définitivement est la suivante:

  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 

Une autre approche, au cas où vous voudriez obtenir la classe aussi. Renvoie nil si la classe n’est pas définie, vous n’avez donc pas à intercepter une exception.

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

J’ai créé un validateur pour tester si une chaîne est un nom de classe valide (ou une liste de noms de classes valides séparés par des virgules):

 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