Come posso verificare se una classe è definita?


58

Come si trasforma una stringa in un nome di classe, ma solo se tale classe esiste già?

Se ambra è già una classe, posso ottenere da una stringa alla classe tramite:

Object.const_get("Amber") 

o (in Rails)

"Amber".constantize 

Ma uno di questi avrà esito negativo con NameError: uninitialized constant Amber se Ambra non è già una classe.

Il mio primo pensiero è quello di utilizzare il metodo defined?, ma non discrimina tra le classi già esistenti e quelli che non lo fanno:

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

Allora, come faccio a testare se un nomi di stringa una classe prima che provi a convertirlo? (Ok, che ne dici di un blocco begin/rescue per catturare errori NameError? Troppo brutto? Sono d'accordo ...)

  0

'' definito nell'esempio è esattamente facendo quello che deve fare: Controlla se è definito il metodo 'constantize' su un oggetto String. Non importa se la stringa contiene "Object" o "AClassNameThatCouldNotPossiblyExist". 08 mag. 172017-05-08 15:34:40

105

Che ne dici di const_defined??

Ricorda in Rails, non c'è auto-caricamento in modalità di sviluppo, in modo che possa essere difficile quando si esegue il test it out:

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

perfetto - grazie. come per il caricatore automatico, IIRC c'è un modo per scoprire cosa c'è nella lista dei caricatori automatici. Lo scaverò se si rivelasse un problema. 22 apr. 112011-04-22 18:36:46

  0

grazie, proprio quello che mi serve = D 17 apr. 152015-04-17 04:55:35

+3

Questo corrisponde anche a cose che non sono classi però. 03 set. 152015-09-03 17:47:26


10

Ispirato @ di ctcherry risposta di cui sopra, ecco un 'sicuro metodo di classe send ', dove class_name è una stringa. Se class_name non assegna un nome a una classe, restituisce zero.

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

Una versione ancora più sicuro che invoca method solo se class_name risponde ad esso:

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.: se ti piace questa risposta, per favore rispondi alla risposta di ctcherry, poiché è quello che mi ha indirizzato nella giusta direzione. 22 apr. 112011-04-22 18:54:49


0

ho creato un validatore per verificare se una stringa è un nome di classe valido (o un elenco separato da virgole di nomi di classe validi):

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

Un altro approccio, nel caso in cui si desideri ottenere anche la lezione. Restituirà nil se la classe non è definita, quindi non devi prendere un'eccezione.

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

In rotaie è davvero facile:

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

Il 'rescue' è stato utile perché a volte le costanti possono essere scaricate e il controllo con' const_defined? 'Sarà falso. 17 giu. 162016-06-17 18:52:29

  0

Bello grazie! 11 feb. 172017-02-11 12:16:40

  0

La soppressione delle eccezioni non è raccomandata, leggi qui: https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions 11 set. 172017-09-11 19:23:25

  0

@AndrewK: in Ruby il salvataggio viene usato molto spesso - ofc Sono d'accordo che è non bene; nel mondo degli elisir cerchiamo di non farlo se non c'è bisogno di farlo, ma ho visto che molte persone usano il salvataggio in Ruby 12 set. 172017-09-12 19:43:44

  0

@Eiji, concordato. Volevo solo menzionarlo perché le persone nuove a Ruby non sanno che è un anti-modello e dovrebbero essere evitati. 12 set. 172017-09-12 20:56:38


2

Sembrerebbe che tutte le risposte con il metodo Object.const_defined? sono irregolari. Se la classe in questione non è stata ancora caricata, a causa del caricamento lento, l'asserzione fallirà. L'unico modo per raggiungere questo obiettivo è definitivamente in questo modo:?

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