2009-03-29 5 views
24

J'ai hérité d'une application Rails 2.2.2 qui stocke les images téléchargées par l'utilisateur sur Amazon S3. Le modèle Photo basé sur attachment_fu propose une méthode rotate qui utilise open-uri pour récupérer l'image à partir de S3 et MiniMagick pour effectuer la rotation.Pourquoi Ruby open-uri ouvre-t-il un StringIO dans mon test unitaire, mais un FileIO dans mon contrôleur?

La méthode rotate contient cette ligne pour récupérer l'image pour une utilisation avec MiniMagick:

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

self.public_filename retours quelque chose comme

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

Récupération de l'image et en le tournant fonctionne très bien dans l'application en cours d'exécution dans la production et le développement. Cependant, le test unitaire échoue avec

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 raison en est que, lorsque la méthode de modèle est appelé dans le cadre de l'essai de l'unité, open(self.public_filename) renvoie un objet StringIO qui contient les données d'image. La méthode path sur cet objet renvoie nil et MiniMagick::Image.from_file explose.

Lorsque cette même méthode de modèle est appelé à partir du PhotosController, open(self.public_filename) retourne une instance FileIO liée à un fichier nommé, par exemple, /tmp/open-uri7378-0 et le fichier contient les données d'image. En pensant que la cause doit être une différence d'environnement entre le test et le développement, j'ai allumé la console dans l'environnement de développement. Mais tout comme dans le test unitaire, open('http://...') a retourné un StringIO, pas un FileIO.

J'ai tracé mon chemin à travers open-uri et tout le code spécifique à l'application et ne trouve aucune raison pour la différence.

Répondre

22

Le code responsable de ceci est dans la classe Buffer dans open-uri. Il commence par créer un objet StringIO et crée uniquement un fichier temporaire réel dans le système de fichiers local lorsque les données dépassent une certaine taille (10 Ko).

Je suppose que les données que votre test charge sont suffisamment petites pour être conservées dans un StringIO et que les images que vous utilisez dans l'application sont suffisamment grandes pour justifier un TempFile. La solution est d'utiliser des méthodes qui sont communs aux deux classes, en particulier la méthode de lecture, avec MiniMagick :: Image # from_blob:

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

Ne faites pas 'open (self.public_filename) .read', vous ne savez pas quand le handle sera fermé. Utilisez à la place 'open (self.public_filename, &: read)', qui utilise la forme de bloc et se ferme explicitement une fois terminé. Et ce n'est pas vraiment plus de code. – apeiros

+0

FYI, 'from_blob' est maintenant déprécié en faveur de' read'. Voir https://github.com/probablycorey/mini_magick/blob/f309fbf390cd21a845264bca9bec95b9bdae8029/lib/mini_magick.rb#L82 –

61

La bibliothèque ouverte uri utilise une constante pour définir la limite de taille de 10 Ko pour StringIO objets.

> OpenURI::Buffer::StringMax 
=> 10240 

Vous pouvez modifier ce paramètre à 0 pour empêcher open-uri de créer jamais un objet StringIO. Au lieu de cela, cela le forcera à toujours générer un fichier temporaire.

Il suffit de jeter cela dans un initialiseur:

# 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 

Vous ne pouvez pas définir simplement la constante directement.Vous devez supprimer réellement la constante puis réglez à nouveau (comme ci-dessus), sinon vous aurez un message d'avertissement:

warning: already initialized constant StringMax 

MISE À JOUR 18/12/2012: Rails 3 ne nécessite pas openURI par défaut , donc vous devez ajouter require 'open-uri' en haut de l'initialiseur. J'ai mis à jour le code ci-dessus pour refléter ce changement.

+0

Micah, Cela semble très bien, mais je reçois des erreurs lorsque je l'essaie dans une application Rails 3. Il semble qu'un objet Buffer n'a pas encore été créé. Constante non initialisée OpenURI (NameError) – Paul

+8

Je pense que je t'aime. –

+0

Merci, Paul, j'ai mis à jour le code dans la réponse. Merci, Michael, je t'aime aussi. –

Questions connexes