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