2009-03-29 2 views
24

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

Метод 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.

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

ответ

22

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

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

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

Не делайте 'open (self.public_filename) .read', вы не знаете, когда дескриптор будет закрыт. Вместо этого используйте 'open (self.public_filename, &: read)', который использует блочную форму и явно закрывается, когда это делается. И это не больше кода. – apeiros

+0

FYI, 'from_blob' теперь устарел в пользу' read'. См. Https://github.com/probablycorey/mini_magick/blob/f309fbf390cd21a845264bca9bec95b9bdae8029/lib/mini_magick.rb#L82 –

61

открытым библиотека использует URI константы, чтобы установить предельный размер 10KB для StringIO объекты.

> OpenURI::Buffer::StringMax 
=> 10240 

Вы можете изменить этот параметр на 0, чтобы предотвратить открывание-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' в начало инициализатора. Я обновил код выше, чтобы отразить это изменение.

+0

Micah. Это выглядит великолепно, но я получаю ошибки, когда я пытаюсь использовать его в приложении Rails 3. Похоже, что объект Buffer еще не создан. uninitialized constant OpenURI (NameError) – Paul

+8

Я думаю, что люблю тебя. –

+0

Спасибо, Пол, я обновил код в ответе. Спасибо, Майкл, я тоже тебя люблю. –