2012-01-29 10 views
21

I love the autoload functionality of Ruby; tuttavia, è going away in future versions of Ruby poiché non è mai stato thread-safe.Autoloading di classi in Ruby senza il suo `autoload`

Quindi ora vorrei far finta che sia già andato e scrivere il mio codice senza di esso, tramite implementando il meccanismo di caricamento lento io stesso. Mi piacerebbe implementarlo nel modo più semplice possibile (non mi interessa la sicurezza del thread in questo momento). Ruby dovrebbe permetterci di farlo. inizio

Let aumentando una classe const_missing:

class Dummy 
    def self.const_missing(const) 
    puts "const_missing(#{const.inspect})" 
    super(const) 
    end 
end 

Rubino chiamerà questo metodo speciale quando si tenta di fare riferimento a una costante in 'Dummy' che manca, ad esempio, se cerchiamo di fare riferimento a "Dummy: : Ciao ", chiamerà const_missing con il simbolo :Hello. Questo è esattamente quello che ci serve, quindi cerchiamo di prendere ulteriormente:

class Dummy 
    def self.const_missing(const) 
    if :OAuth == const 
     require 'dummy/oauth' 
     const_get(const)  # warning: possible endless loop! 
    else 
     super(const) 
    end 
    end 
end 

Ora, se facciamo riferimento "Dummy :: OAuth", sarà necessario il file "fittizio/oauth.rb" che dovrebbe definire la " Dummy :: OAuth "costante. C'è una possibilità di un ciclo infinito quando chiamiamo const_get (dal momento che può chiamare internamente) const_missing, ma guardarsi da questo è al di fuori dello scopo di questa domanda.

Il grosso problema è che l'intera soluzione si interrompe se esiste un modulo denominato "OAuth" nello spazio dei nomi di livello superiore. Il riferimento a "Dummy :: OAuth" salterà il suo const_missing e restituirà "OAuth" dal livello superiore. La maggior parte delle implementazioni di Ruby farà anche un avvertimento su questo:

warning: toplevel constant OAuth referenced by Dummy::OAuth 

This was reported as a problem way back in 2003 ma non riuscivo a trovare la prova che il core team di Ruby era mai preoccupato per questo. Oggi, le implementazioni di Ruby più popolari hanno lo stesso comportamento.

Il problema è che const_missing viene saltato automaticamente a favore di una costante nello spazio dei nomi di livello superiore. Ciò non accadrebbe se "Dummy :: OAuth" fosse dichiarato con la funzionalità autoload di Ruby. Qualche idea su come aggirare questo?

+0

Questo mi sembra un suggerimento stupido, ma si può guardare il sorgente C di 'autoload'? Sono sicuro che puoi trovarlo da qualche parte nella sorgente Ruby. Se non puoi farlo direttamente in Ruby, c'è la possibilità di creare un'estensione C (che ha accesso al lato inferiore dell'interprete). – Linuxios

+0

È un'opzione. – mislav

+0

suona come una cosa a forza bruta, ma non potresti 'remove_const' nella classe di primo livello? – phoet

risposta

5

Questo è stato sollevato in un biglietto Rails qualche tempo fa e quando l'ho esaminato sembrava non esserci intorno. Il problema è che Ruby cercherà gli antenati prima di chiamare lo const_missing e dal momento che tutte le classi hanno come antenato lo Object, allora le costanti di livello superiore verranno sempre trovate. Se potete limitarvi a solo con i moduli per namespacing allora funzionerà dal momento che non hanno Object come antenato, ad esempio:

>> class A; end 
>> class B; end 
>> B::A 
(irb):3: warning: toplevel constant A referenced by B::A 

>> B.ancestors 
=> [B, Object, Kernel, BasicObject] 

>> module C; end 
>> module D; end 
>> D::C 
NameError: uninitialized constant D::C 

>> D.ancestors 
=> [D] 
+0

Sì, è un peccato che non riusciamo a farlo funzionare con le classi, tuttavia è bello sapere che almeno questo funziona con i moduli. Grazie! – mislav

0

ottengo il problema ree 1.8.7 (non si parla di una versione specifica) se uso const_get dentro const_missing, ma non se uso ::. Non mi piace usare eval, ma funziona qui:

class Dummy 
    def self.const_missing(const) 
    if :OAuth == const 
     require 'dummy/oauth' 
     eval "self::#{const}" 
    else 
     super(const) 
    end 
    end 
end 

module Hello 
end 

Dummy.const_get :Hello # => ::Hello 
Dummy::Hello   # => Dummy::Hello 

Vorrei Module avuto un metodo :: in modo che si possa fare self.send :"::", const.

+0

Ci scusiamo per non aver menzionato versioni specifiche. Il mio problema è presente tra 1.8.7, 1.9.2, 1.9.3, Rubinius (sia stabile che edge) e altro. Proverò il tuo suggerimento, ma da quello che ho ottenuto nei miei test il metodo 'const_missing' non è stato chiamato affatto. Quindi non importa come lo realizzo. – mislav

+0

Dipende dal fatto che si usi 'Dummy.const_get: Hello' o' Dummy :: Hello' * all'esterno * del metodo 'const_missing'. Solo ':: Hello' attiverà' const_missing' - o almeno questo è vero nell'implementazione di Ruby che ho provato. –

0

pigro di caricamento è un design pattern molto comune, è possibile attuando in molti modi .come:

class Object 
    def bind(key, &block) 
    @hooks ||= Hash.new{|h,k|h[k]=[]} 
    @hooks[key.to_sym] << [self,block] 
    end 

    def trigger(key) 
    @hooks[key.to_sym].each { |context,block| block.call(context) } 
    end 
end 

Poi si può

bind :json do 
    require 'json' 
end 

begin 
    JSON.parse("[1,2]") 
rescue 
    trigger :json 
    retry 
end 
Problemi correlati