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

я унаследовал приложение Rails 2.2.2, которое хранит загруженные пользователем изображения на Amazon S3. Attachment_fu-based Photo модель 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 объект, содержащий данные изображения. The path метод на этом объекте возвращает nil и MiniMagick::Image.from_file взрывается.

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

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

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

2 ответов


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

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

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

библиотека 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.12.2012: Rails 3 по умолчанию не требует OpenURI, поэтому вам нужно добавить require 'open-uri' в верхней части инициализатор. Я обновил код выше, чтобы отразить это изменение.