如何在工厂中不使用文件夹具定义ActiveStorage附件?遇NoMethodError
ActiveStorage搭配Rack::Test::UploadedFile使用StringIO时的NoMethodError问题
问题场景
模型中定义了ActiveStorage附件:
has_one_attached :csv
尝试在FactoryBot工厂里用StringIO生成测试用的CSV附件:
csv do Rack::Test::UploadedFile.new(StringIO.new(<<~CSV), 'text/csv', original_filename: "file.csv") Some, Columns, Here CSV end
根据Rack::Test的代码设计,这种用法应该被支持,但运行时抛出以下错误:
NoMethodError: private method `open' called for #<StringIO:0x00007f206ff88d38> tempfile.public_send(method_name, *args, &block)
错误根源
- ActiveStorage在处理附件时会调用
open方法,而Rack::Test::UploadedFile会将未实现的方法委托给内部的tempfile对象(也就是我们传入的StringIO实例)。 - StringIO类仅提供了类方法
open,但其实例没有公开的#open实例方法;而Tempfile实例自带公开的#open方法,这是两者的API差异。 - Rack::Test虽然允许传入StringIO,但没有处理这种API差异,导致ActiveStorage调用
open时触发了StringIO的私有实例方法,最终报错。
结论
这是Rack::Test的兼容性Bug:它宣称支持StringIO作为UploadedFile的输入,但未抹平StringIO与Tempfile之间的API差异,导致依赖#open方法的ActiveStorage无法正常工作。
临时修复方案
给StringIO实例添加公开的#open方法,映射到它已有的#reopen方法:
csv do file = StringIO.new(<<~CSV) Some, Columns, Here CSV # 修复StringIO与Rack::Test::UploadedFile配合的Bug file.class_eval do def open = reopen end Rack::Test::UploadedFile.new(file, "text/csv", original_filename: "enrollments_changes_report.csv") end
内容的提问来源于stack exchange,提问作者Greg
相关产品推荐
相关产品推荐

