web-dev-qa-db-fra.com

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

Comment 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?, mais elle ne fait pas de distinction entre les classes qui existent déjà et celles qui n'existent pas:

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

Alors, comment puis-je tester si une chaîne nomme une classe avant d'essayer de la convertir? (D'accord, que diriez-vous d'un bloc begin/rescue pour détecter les erreurs NameError? Trop moche? Je suis d'accord ...)

69
fearless_fool

Que diriez-vous const_defined? ?

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

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

En Rails c'est très simple:

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

Inspiré par la réponse de @ ctcherry ci-dessus, voici une 'méthode de classe sûre envoyer', où class_name est une chaîne. Si class_name ne nomme pas de 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 method uniquement si class_name y 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
13
fearless_fool

Il semblerait que toutes les réponses utilisant le Object.const_defined? la méthode est défectueuse. Si la classe en question n'a pas encore été chargée, en raison d'un chargement paresseux, l'assertion échouera. 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
4
TomDunning

J'ai créé un validateur pour tester si une chaîne est un nom de classe valide (ou une liste de noms de classe 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
2
Fred Willmore

Une autre approche, au cas où vous souhaiteriez également obtenir le cours. Renvoie nil si la classe n'est pas définie, vous n'avez donc pas à intercepter une exception.

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