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

我inheritance了一个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) 

self.public_filename返回类似的内容

 http://s3.amazonaws.com/bucketname/photos/98/photo.jpg 

检索图像并旋转它在生产和开发中运行的应用程序中工作得很好。 但是,unit testing失败了

 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 testing的上下文中调用模型方法时, open(self.public_filename)返回包含图像数据的StringIO对象。 此对象上的path方法返回nil并且MiniMagick::Image.from_file爆炸。

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

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

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

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

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

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

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

 > 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 

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