为什么Ruby open-uri打开在我的单元测试中返回一个StringIO,但在我的控制器中是一个FileIO?

Sea*_*try 30 ruby open-uri ruby-on-rails imagemagick amazon-s3

我继承了一个Rails 2.2.2应用程序,用于在Amazon S3上存储用户上传的图像.基于attachment_fu的Photo模型提供了一种rotate方法,用于open-uri从S3和MiniMagick中检索图像以执行旋转.

rotate方法包含此行以检索用于MiniMagick的图像:

temp_image = MiniMagick::Image.from_file(open(self.public_filename).path)
Run Code Online (Sandbox Code Playgroud)

self.public_filename 返回类似的东西

http://s3.amazonaws.com/bucketname/photos/98/photo.jpg
Run Code Online (Sandbox Code Playgroud)

检索图像并旋转它在生产和开发中正在运行的应用程序中工作正常.但是,单元测试失败了

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'
Run Code Online (Sandbox Code Playgroud)

原因是当在单元测试的上下文中调用模型方法时,open(self.public_filename)返回StringIO包含图像数据的对象.path此对象上的方法返回nilMiniMagick::Image.from_file爆炸.

当从该调用这个相同的模型方法时PhotosController,open(self.public_filename)返回FileIO绑定到例如名为的文件的实例,/tmp/open-uri7378-0并且该文件包含图像数据.

考虑原因必须是测试和开发之间的一些环境差异,我在开发环境下启动了控制台.但正如在单元测试中,open('http://...')返回一个StringIO,而不是一个FileIO.

我已经通过open-uri和所有相关的应用程序特定代码进行了跟踪,并且没有找到差异的理由.

Mic*_*cht 70

open-uri库使用常量来设置StringIO对象的10KB大小限制.

> OpenURI::Buffer::StringMax
=> 10240 
Run Code Online (Sandbox Code Playgroud)

您可以将此设置更改为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
Run Code Online (Sandbox Code Playgroud)

你不能直接设置常量.您需要实际删除常量然后再次设置它(如上所述),否则您将收到警告:

warning: already initialized constant StringMax
Run Code Online (Sandbox Code Playgroud)

更新时间:2012年12月18日:Rails 3默认情况下不需要OpenURI,因此您需要require 'open-uri'在初始化程序的顶部添加.我更新了上面的代码以反映这一变化.

  • 我想我爱你. (13认同)

Jef*_*ien 27

负责此操作的代码位于open-uri的Buffer类中.它首先创建一个StringIO对象,并在数据超过一定大小(10 KB)时仅在本地文件系统中创建一个实际的临时文件.

我假设您的测试加载的数据足够小,可以保存在StringIO中,而您在实际应用程序中使用的图像足够大,可以保证TempFile.解决方案是使用两个类共有的方法,特别是read方法,使用MiniMagick :: Image#from_blob:

temp_image = MiniMagick::Image.from_blob(open(self.public_filename, &:read))
Run Code Online (Sandbox Code Playgroud)

  • 不要做`open(self.public_filename).read`,你不知道何时关闭句柄.使用`open(self.public_filename,&:read)`代替它,它使用块形式并在完成时显式关闭.而且它不是更多的代码. (3认同)