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


58

Comment transformer une chaîne en un nom de classe, mais seulement si cette classe existe déjà?

Si Ambre est déjà une classe, je peux obtenir à partir d'une chaîne à la classe via:

Object.const_get("Amber") 

ou (Rails)

"Amber".constantize 

Mais l'une de ces échouera avec NameError: uninitialized constant Amber si Amber n'est pas déjà une classe.

Ma première pensée est d'utiliser la méthode defined?, mais il ne fait pas de discrimination entre les classes qui existent déjà et ceux qui ne le font pas:

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

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

  0

'' défini dans l'exemple est en train de faire exactement ce qu'il est censé faire: Il vérifie si la méthode 'constantize' sur un objet String est défini. Il ne se soucie pas si la chaîne contient "Object" ou "AClassNameThatCouldNotPossiblyExist". 08 mai. 172017-05-08 15:34:40

105

Que diriez-vous de const_defined??

Rappelez-vous dans Rails, il est auto-chargement en mode de développement, il peut être difficile quand vous le tester:

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

parfait - merci. Quant au chargeur automatique, il existe un moyen de trouver ce qu'il y a dans la liste de l'autochargeur. Je vais creuser ça si ça s'avère être un problème. 22 avril. 112011-04-22 18:36:46

  0

merci, juste ce dont j'ai besoin = D 17 avril. 152015-04-17 04:55:35

+3

Cela correspond également à des choses qui ne sont pas des cours. 03 sept.. 152015-09-03 17:47:26


10

Inspirée par @ réponse de ctcherry ci-dessus, voici un « envoi en toute sécurité de méthode de classe ', où class_name est une chaîne. Si class_name ne nomme pas une classe, elle renvoie zéro.

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

p.s.: Si vous aimez cette réponse, s'il vous plaît, votez pour la réponse de ctcherry, puisque c'est ce qui m'a indiqué la bonne direction. 22 avril. 112011-04-22 18:54:49


0

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

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

Une autre approche, au cas où vous voudriez obtenir la classe aussi. Renviendra à zéro si la classe n'est pas définie, donc vous n'avez pas besoin d'attraper 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 

11

En rails, il est vraiment facile:

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

Le 'rescue' était utile parce que parfois les constantes peuvent être déchargées et vérifier avec' const_defined? 'Sera faux. 17 juin. 162016-06-17 18:52:29

  0

Merci beaucoup! 11 févr.. 172017-02-11 12:16:40

  0

Suppression des exceptions n'est pas recommandé, lisez plus ici: https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions 11 sept.. 172017-09-11 19:23:25

  0

@AndrewK: dans le sauvetage Ruby est utilisé très souvent - ofc je suis d'accord qu'il est pas bon; Dans le monde Elixir, nous essayons de ne pas le faire s'il n'y a pas besoin de le faire, mais j'ai vu que beaucoup de gens utilisent le sauvetage dans Ruby 12 sept.. 172017-09-12 19:43:44

  0

@Eiji, D'accord. Je voulais juste le mentionner car les nouveaux venus chez Ruby ne savent pas que c'est un anti-pattern et qu'il faut éviter. 12 sept.. 172017-09-12 20:56:38


2

Il semblerait que toutes les réponses en utilisant la méthode Object.const_defined? sont imparfaites. 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 est définitivement comme ceci:?

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