【问题标题】:Why does Ruby open-uri's open return a StringIO in my unit test, but a FileIO in my controller?为什么Ruby open-uri open 在我的单元测试中返回一个StringIO,但在我的控制器中返回一个FileIO?
【发布时间】:2010-10-16 04:46:34
【问题描述】:

我继承了一个 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

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

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) 返回一个绑定到名为/tmp/open-uri7378-0 的文件的FileIO 实例,并且该文件包含图像数据。

想必是测试和开发环境的一些差异,我在开发环境下启动了控制台。但就像在单元测试中一样,open('http://...') 返回了 StringIO,不是 FileIO。

我已经通过 open-uri 和所有相关的特定于应用程序的代码进行了跟踪,找不到任何差异的原因。

【问题讨论】:

    标签: ruby-on-rails ruby amazon-s3 imagemagick open-uri


    【解决方案1】:

    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-uri'。我更新了上面的代码以反映这一变化。

    【讨论】:

    • Micah,这看起来很棒,但是当我在 Rails 3 应用程序中尝试时遇到错误。看起来还没有创建 Buffer 对象。未初始化的常量 OpenURI (NameError)
    • 谢谢,保罗,我更新了答案中的代码。谢谢,迈克尔,我也爱你。
    • 在初始化程序中更改此应用程序范围内的性能影响?
    • 感谢您的解决方案。来自 CSV 错误 no implicit conversion of StringIO into String
    • 这已经让我发疯了几个星期了。感谢您的回答!
    【解决方案2】:

    负责这个的代码在 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(self.public_filename).read,你不知道手柄什么时候会关闭。请改用open(self.public_filename, &:read),它使用块形式并在完成后显式关闭。而且它实际上并没有更多的代码。
    • 仅供参考,from_blob 现在已弃用,取而代之的是 read。见github.com/probablycorey/mini_magick/blob/…
    【解决方案3】:

    现在OpenURI::Buffer::StringMax可以直接设置了:

    require 'open-uri'
    OpenURI::Buffer::StringMax = 0
    

    但有警告:

    warning: already initialized constant OpenURI::Buffer::StringMax
    

    【讨论】:

      猜你喜欢
      • 2013-09-18
      • 1970-01-01
      • 1970-01-01
      • 2015-07-24
      • 1970-01-01
      • 2014-11-09
      • 2015-02-19
      • 1970-01-01
      • 2016-03-27
      相关资源
      最近更新 更多