2009-03-29 7 views
24

Ho ereditato un'app Rails 2.2.2 che memorizza le immagini caricate dall'utente su Amazon S3. Il modello attachment_fu basata Photo offre un metodo che utilizza rotateopen-uri per recuperare l'immagine da S3 e MiniMagick per effettuare la rotazione.Perché Ruby open-uri è aperto restituisce un StringIO nel mio test dell'unità, ma un FileIO nel mio controller?

Il metodo rotate contiene questa linea per recuperare l'immagine da utilizzare con MiniMagick:

temp_image = MiniMagick::Image.from_file(open(self.public_filename).path) 

self.public_filename restituisce qualcosa come

http://s3.amazonaws.com/bucketname/photos/98/photo.jpg 

Recupero l'immagine e ruotandola funzionano bene in applicazione in esecuzione in produzione e sviluppo. Tuttavia, il test fallisce con unità

TypeError: can't convert nil into String 
    /Users/santry/Development/totspot/vendor/gems/mini_magick-1.2.3/lib/mini_magick.rb:34:in `initialize' 
    /Users/santry/Development/totspot/vendor/gems/mini_magick-1.2.3/lib/mini_magick.rb:34:in `open' 
    /Users/santry/Development/totspot/vendor/gems/mini_magick-1.2.3/lib/mini_magick.rb:34:in `from_file' 

La ragione è che quando il metodo modello viene chiamata nel contesto del test di unità, open(self.public_filename) restituisce un oggetto StringIO che contiene i dati di immagine. Il metodo path su questo oggetto restituisce nil e MiniMagick::Image.from_file colpi in su.

Quando questo stesso metodo di modello si chiama dal PhotosController, open(self.public_filename) restituisce un'istanza FileIO legato ad un file chiamato, per esempio, /tmp/open-uri7378-0 e il file contiene i dati di immagine.

Pensando la causa deve essere una certa differenza tra l'ambiente di test e sviluppo, ho sparato la console sotto l'ambiente di sviluppo. Ma proprio come nel test di unità, open('http://...') restituito un StringIO, non un FileIO.

Ho rintracciato la mia strada attraverso tutto il codice specifico dell'applicazione rilevanti open-uri e e riesco a trovare alcun motivo per la differenza.

risposta

22

Il codice responsabile di questo è nella classe Buffer in open-uri. Inizia creando un oggetto StringIO e crea solo un file temporaneo effettivo nel filesystem locale quando i dati superano una determinata dimensione (10 KB).

Suppongo che i dati caricati dal test siano abbastanza piccoli da essere conservati in un StringIO e che le immagini che si stanno utilizzando nell'applicazione effettiva siano sufficientemente grandi da giustificare un TempFile. La soluzione è quella di utilizzare metodi che sono comuni a entrambe le classi, in particolare il metodo di lettura, con MiniMagick :: Immagine # from_blob:

temp_image = MiniMagick::Image.from_blob(open(self.public_filename, &:read)) 
+3

Non fare 'open (self.public_filename) .read', non si sa quando l'handle verrà chiuso. Usa invece 'open (self.public_filename, &: read)', che usa il modulo di blocco e si chiude esplicitamente al termine. E non è più un codice. – apeiros

+0

FYI, 'from_blob' è ora deprecato a favore di' read'. Vedi https://github.com/probablycorey/mini_magick/blob/f309fbf390cd21a845264bca9bec95b9bdae8029/lib/mini_magick.rb#L82 –

61

La libreria open-uri usa una costante per impostare il limite di dimensione 10KB per StringIO oggetti.

> OpenURI::Buffer::StringMax 
=> 10240 

È possibile modificare questa impostazione a 0 per impedire open-uri da sempre la creazione di un oggetto StringIO. Invece, questo lo costringerà a generare sempre un file temporaneo.

Basta buttare questo in un inizializzatore:

# Don't allow downloaded files to be created as StringIO. Force a tempfile to be created. 
require 'open-uri' 
OpenURI::Buffer.send :remove_const, 'StringMax' if OpenURI::Buffer.const_defined?('StringMax') 
OpenURI::Buffer.const_set 'StringMax', 0 

Non si può semplicemente impostare direttamente la costante.È necessario rimuovere in realtà il costante e quindi impostare di nuovo (come sopra), altrimenti si otterrà un avvertimento:

warning: already initialized constant StringMax 

AGGIORNATO 2012/12/18: Rails 3 non richiede OpenURI di default , quindi è necessario aggiungere require 'open-uri' nella parte superiore dell'inizializzatore. Ho aggiornato il codice sopra per riflettere tale modifica.

+0

Micah, sembra ottimo ma sto ricevendo errori quando lo provo in un'app Rails 3. Sembra che un oggetto Buffer non sia stato ancora creato. costante non inizializzata OpenURI (NameError) – Paul

+8

Penso di amarti. –

+0

Grazie, Paul, ho aggiornato il codice nella risposta. Grazie, Michael, anch'io ti amo. –