我有一个非常简单的场景,我正在创建一条记录,然后附加一个文件(在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
但是激活存储只有在提交之后才会上传文件,所以这个内部救援永远不会起作用,起作用的是:
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上测试的,所以我不知道它在其他版本上是否工作得很好。