Rails 6 ActiveStorage在文件上载失败时恢复事务事务、文件、Rails、ActiveStorage

2023-09-03 09:16:54 作者:月亮邮递员

我有一个非常简单的场景,我正在创建一条记录,然后附加一个文件(在save!之后,因为我需要记录上的id来生成附件的finename),所有这些都包装在一个事务中。

类似:

def create
  ActiveRecord::Base.transaction do
    record = A.create!(a_params)
    pdf = generate_pdf
    record.file.attach(
      io: StringIO.new(pdf),
      filename: "PO##{record.id}.pdf",
      content_type: 'application/pdf'
    )
  rescue
    # here it should rollback transaction on all kind of errors, if it fails upload or whatever, but it does not
    raise ActiveRecord::Rollback
  end
end
我的三星U盘格式化了忘了备份文件怎么办

但是激活存储只有在提交之后才会上传文件,所以这个内部救援永远不会起作用,起作用的是:

def create
  record = nil
  ActiveRecord::Base.transaction do
    record = A.create!(a_params)
    pdf = generate_pdf
    record.file.attach(
      io: StringIO.new(pdf),
      filename: "PO##{record.id}.pdf",
      content_type: 'application/pdf'
    )
  end
rescue
  record&.destroy!
end

我在这里简化了这个场景,实际上我有一个场景,其中在一个循环中创建了许多记录,并且我不想保存其中的任何记录,以防出现任何错误。

我发现一些问题,如: https://github.com/rails/rails/issues/32449 https://github.com/rails/rails/issues/31985

我如何才能以最好的方式修复此问题,我看到活动存储在后台上传文件,以避免事务持续时间过长。因为如果不这样做,就会产生问题。这是有道理的,我认为我还应该将pdf_生成逻辑从事务中删除。

但是我想知道有没有更好的方法,只有在正确生成和上传pdf的情况下才创建记录。而不是手动销毁它们并在发生错误时将任何其他更新恢复到数据库。

推荐答案

我找到了一个替代方案,该方案仍然可以优雅地验证上载。

首先使用ActiveStorage::Blob.create_after_upload!方法上传文件,传入一些文件参数作为io、文件名和Content_type: https://apidock.com/rails/v6.0.0/ActiveStorage/Blob/create_after_upload%21/class

然后,设置并保存上载到附件列的blob

在您的情况下,可能如下所示:

blob = ActiveStorage::Blob.create_after_upload!(
  io: StringIO.new(pdf), 
  filename: "PO##{record.id}.pdf",
  content_type: 'application/pdf')

record.file = blob
record.save!

在这种情况下,如果在文件上传到服务的过程中出现任何错误,则会像最初预期的那样,在当前块之前和内部引发错误。

注意:它是在Rails版本6.0.x上测试的,所以我不知道它在其他版本上是否工作得很好。