Rails事务中使用dry-monads触发ActiveStorage::FileNotFoundError问题
问题产生原因
- 根本原因是ActiveStorage的附件写入与数据库事务的时序不匹配:
file.attach执行时仅会在数据库中写入附件关联记录,实际的文件写入操作(本地磁盘或云存储)默认会延迟到数据库事务正式提交之后才触发。 - 你在事务块内部调用了
myobject.file.download,此时事务尚未提交,文件还没有被真正写入存储介质,直接读取自然会抛出ActiveStorage::FileNotFoundError。 - 叠加dry-monads的执行逻辑:事务块内部的
yield会在返回失败结果时直接中断执行、触发事务回滚,进一步导致附件写入操作永远不会被执行。
解决方法
方案1:拆分事务与文件操作(最推荐)
将核心业务数据的原子操作放到事务块内,文件读取、解析等非数据库操作移到事务提交之后执行:
# 第一步先创建核心业务记录 process = yield create_process # 事务块仅保障数据库操作的原子性 Process.transaction do yield attach_file_to_process(process) end # 事务提交后再执行文件解析逻辑 yield parse_process_file(process) # 拆分原create_file方法 def attach_file_to_process(process) process.file.attach(io: file['tempfile'], filename: file['filename'], content_type: file['type']) Success(process) end def parse_process_file(process) csv_content = CSV.parse(process.file.download.force_encoding('UTF-8'), headers: true) Success(csv_content) end
方案2:预上传文件生成Blob
如果必须在事务内部校验文件内容,可以提前将文件上传生成ActiveStorage Blob,再在事务内关联到业务对象,此时可以直接从Blob读取内容,不需要等待事务提交:
# 提前上传文件生成Blob,不涉及事务操作 file_blob = ActiveStorage::Blob.create_and_upload!( io: file['tempfile'], filename: file['filename'], content_type: file['type'] ) Process.transaction do process = yield create_process process.file.attach(file_blob) # 直接从预生成的Blob读取内容 csv_content = CSV.parse(file_blob.download.force_encoding('UTF-8'), headers: true) # 后续CSV业务校验逻辑 yield validate_csv_content(csv_content) end
方案3:使用事务回调执行文件操作
如果逻辑不允许拆分代码结构,可以通过after_commit回调在事务提交完成后再触发文件解析逻辑,需额外适配dry-monads的异常捕获和结果返回逻辑即可。
内容的提问来源于stack exchange,提问作者Petros Kalafatidis
相关产品推荐
相关产品推荐

