Почему открытый в Ruby open-uri возвращает StringIO в моем модульном тесте, но FileIO в моем контроллере? - PullRequest
27 голосов
/ 29 марта 2009

Я унаследовал приложение 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, , а не a FileIO.

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

Ответы [ 2 ]

64 голосов
/ 09 июля 2011

Библиотека 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

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

26 голосов
/ 29 марта 2009

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

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

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