Pourquoi l'open de Ruby open-uri renvoie-t-il un StringIO dans mon test unitaire, mais un FileIO dans mon contrôleur?

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 de S3 et MiniMagick pour effectuer la rotation.

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

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

self.public_filename retourne quelque chose comme

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

Récupérer l'image et la faire tourner fonctionne très bien dans l'application en cours d'exécution en production et 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 model est appelée dans le contexte du test unitaire, open(self.public_filename) renvoie un objet StringIO contenant les données d'image. Le path méthode sur cet objet renvoie nil et MiniMagick::Image.from_file explose.

Lorsque cette même méthode de modèle est appelée à partir PhotosController, open(self.public_filename) renvoie une instance FileIO liée à un fichier nommé, par exemple /tmp/open-uri7378-0 et le fichier contient les données d'image.

Penser la cause doit être une différence environnementale entre le test et le développement, j'ai tiré la console sous l'environnement de développement. Mais, tout comme dans le test de l'unité, open('http://...') renvoyé un StringIO, pas un FileIO.

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

24
demandé sur Andrew Grimm 2009-03-29 08:48:46

2 réponses

Le code responsable de ceci est dans la classe Buffer dans open-uri. Il commence par créer un objet StringIO et ne crée un fichier temporaire réel dans le système de fichiers local que 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 réelle sont suffisamment grandes pour justifier un fichier temporaire. La solution consiste à utiliser des méthodes communes 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))
23
répondu Jeff Dallien 2014-03-19 11:56:58

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

> OpenURI::Buffer::StringMax
=> 10240 

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

Il suffit de lancer ceci 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 simplement définir la constante directement. Vous devez réellement supprimer la constante, puis la définir à nouveau (comme ci-dessus), sinon vous obtiendrez un avertissement:

warning: already initialized constant StringMax

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

62
répondu Micah Winkelspecht 2012-12-19 01:01:34