Почему open-uri в Ruby возвращает StringIO в моем модульном тесте, но FileIO в моем контроллере?

Я унаследовал приложение Rails 2.2.2, которое хранит загруженные пользователем изображения на Amazon S3. Модель Photo на основе attachment_fu предлагает метод rotate, который использует open-uri для извлечения изображения из S3 и MiniMagick для выполнения поворота.

Метод rotate содержит эту строку для получения изображения для использования с MiniMagick:

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

self.public_filename возвращает что-то вроде

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

Извлечение изображения и его поворот прекрасно работают в работающем приложении в производстве и разработке. Однако модульный тест терпит неудачу с

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'

Причина в том, что когда метод модели вызывается в контексте модульного теста, open(self.public_filename) возвращает объект StringIO, содержащий данные изображения. Метод path для этого объекта возвращает nil, а MiniMagick::Image.from_file взрывается.

Когда тот же самый метод модели вызывается из PhotosController, open(self.public_filename) возвращает экземпляр FileIO, связанный с файлом с именем, например, /tmp/open-uri7378-0, и этот файл содержит данные изображения.

Подумав, что причиной должна быть какая-то разница в среде между тестированием и разработкой, я запустил консоль в среде разработки. Но, как и в модульном тесте, open('http://...') вернул StringIO, а не FileIO.

Я проследил свой путь через open-uri и весь соответствующий код для конкретного приложения и не могу найти причину разницы.


person Sean Santry    schedule 29.03.2009    source источник


Ответы (3)


Код, отвечающий за это, находится в классе Buffer в open-uri. Он начинается с создания объекта StringIO и создает фактический временный файл в локальной файловой системе только тогда, когда данные превышают определенный размер (10 КБ).

Я предполагаю, что любые данные, загружаемые вашим тестом, достаточно малы, чтобы храниться в StringIO, а изображения, которые вы используете в реальном приложении, достаточно велики, чтобы гарантировать TempFile. Решение состоит в использовании методов, общих для обоих классов, в частности метода чтения, с MiniMagick::Image#from_blob:

temp_image = MiniMagick::Image.from_blob(open(self.public_filename, &:read))
person Jeff Dallien    schedule 29.03.2009
comment
Не делайте open(self.public_filename).read, вы не знаете, когда ручка будет закрыта. Вместо этого используйте open(self.public_filename, &:read), который использует блочную форму и явно закрывается по завершении. И это не совсем код. - person apeiros; 22.02.2013
comment
К вашему сведению, from_blob теперь устарел в пользу read. См. github.com/proballycorey/mini_magick/blob/ - person Jessie A. Young; 06.09.2017

Библиотека open-uri использует константу для установки ограничения размера 10 КБ для объектов StringIO.

> OpenURI::Buffer::StringMax
=> 10240 

Вы можете изменить этот параметр на 0, чтобы open-uri никогда не создавал объект StringIO. Вместо этого это заставит его всегда создавать временный файл.

Просто добавьте это в инициализатор:

# 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

Вы не можете просто установить константу напрямую. Вам нужно фактически удалить константу, а затем установить ее снова (как указано выше), иначе вы получите предупреждение:

warning: already initialized constant StringMax

ОБНОВЛЕНО 18 декабря 2012 г.: Rails 3 не требует OpenURI по умолчанию, поэтому вам нужно добавить require 'open-uri' в начало инициализатора. Я обновил приведенный выше код, чтобы отразить это изменение.

person Micah Winkelspecht    schedule 09.07.2011
comment
Мика, это выглядит великолепно, но я получаю ошибки, когда пробую это в приложении Rails 3. Похоже, объект Buffer еще не создан. неинициализированная константа OpenURI (NameError) - person Paul; 25.10.2012
comment
Спасибо, Пол, я обновил код в ответе. Спасибо, Майкл, я тоже тебя люблю. - person Micah Winkelspecht; 19.12.2012
comment
Изменение этого приложения в инициализаторе, какие последствия для производительности это имеет? - person Marius Butuc; 08.09.2015
comment
Спасибо за это решение. Пришел сюда из-за ошибки CSV no implicit conversion of StringIO into String - person goodmanship; 06.04.2016
comment
Это сводит меня с ума уже пару недель. Спасибо за этот ответ! - person Phatjam98; 13.06.2017
comment
падаме любит тебя братан - person Pacolotero; 20.06.2019

Теперь OpenURI::Buffer::StringMax можно установить напрямую:

require 'open-uri'
OpenURI::Buffer::StringMax = 0

но с предупреждением:

warning: already initialized constant OpenURI::Buffer::StringMax
person Jinbo    schedule 09.07.2020