Почему Ruby open-uri open возвращает StringIO в моем unit test, но 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
Извлечение изображения и его вращение отлично работают в запущенном приложении в процессе производства и разработки. Однако unit test не работает с
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'
Причина в том, что когда метод модели вызывается в контексте unit test, open(self.public_filename)
возвращает объект StringIO
, который содержит данные изображения. Метод path
этого объекта возвращает nil
и MiniMagick::Image.from_file
.
Когда этот самый метод модели вызывается из PhotosController
, open(self.public_filename)
возвращает экземпляр FileIO
, связанный с файлом с именем, например, /tmp/open-uri7378-0
, и файл содержит данные изображения.
Мысль о причинах должна быть некоторой экологической разницей между тестированием и разработкой, я запускал консоль в среде разработки. Но так же, как в unit test, open('http://...')
вернул a StringIO
, а не a FileIO
.
Я проследил свой путь через open-uri и весь соответствующий код приложения и не нашел причин для разницы.
Ответы
Ответ 1
Код, отвечающий за это, находится в классе Buffer в open-uri. Он начинается с создания объекта StringIO и создает фактический временный файл в локальной файловой системе, когда данные превышают определенный размер (10 КБ).
Я предполагаю, что любые данные, которые загружает ваш тест, достаточно малы, чтобы их можно было удерживать в StringIO, а изображения, которые вы используете в реальном приложении, достаточно велики, чтобы гарантировать TempFile. Решением является использование методов, которые являются общими для обоих классов, в частности метода чтения, с помощью MiniMagick:: Image # from_blob:
temp_image = MiniMagick::Image.from_blob(open(self.public_filename, &:read))
Ответ 2
В библиотеке open-uri используется константа для установки ограничения размера 10KB для объектов 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'
в начало инициализатора. Я обновил код выше, чтобы отразить это изменение.