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

Zia*_*hal 5 ruby-on-rails rails-activerecord rails-activestorage

我有一个非常简单的场景,我正在创建一个记录,而不是附加一个文件(之后save!,因为我需要记录上的 id 来生成附件的精细名称),所有这些都包含在一个事务中。

就像是:

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
Run Code Online (Sandbox Code Playgroud)

但是 activestorage 仅在提交后才上传文件,因此这种内部救援永远不会起作用,有效的是:

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
Run Code Online (Sandbox Code Playgroud)

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

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

我该如何以最佳方式解决此问题,我读到活动存储会在后台上传文件,以免使交易持续很长时间。因为否则会产生问题。这是有道理的,我认为我也应该将 pdf_generation 逻辑排除在交易之外。

但是我想知道是否有更好的方法可以仅在正确生成 pdf 时创建记录。而不是手动销毁它们并在发生错误时将任何其他更新恢复到数据库。

小智 6

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

首先,使用该方法上传文件ActiveStorage::Blob.create_after_upload!,传递一些文件参数作为iofilenamecontent_typehttps://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!
Run Code Online (Sandbox Code Playgroud)

这样,如果在文件上传到服务期间发生任何错误,它将在当前块之前和内部引发,正如最初预期的那样。

注意:它在 Rails 版本 6.0.x 上进行了测试,所以我不知道它在其他版本上是否运行良好。