将一个Active Storage blob附加到多个文件



在Rails中,当将文件附加到对象时,是否有方法检查它是否已经附加到另一个对象?如果是这样,我可以在第二个文件上使用这个现有的附件吗?这里的原因是为了防止多次将相同的文件上传到存储后端。

具体来说,这源于使用file_fixture_upload在开发环境中附加的文件。

您可以将现有blob附加到对象上,就像#attachrails文档:https://api.rubyonrails.org/classes/ActiveStorage/Attached/One.html#method-i-attach中的person.avatar.attach(avatar_blob).

但关键问题是如何知道文件已经上传,对应的blob在哪里?

有两种可能的思考方式:

  1. 尝试使用active_storage_blobs表中存储的数据查找blob
  2. 创建另一个模型来跟踪和维护文件上传状态。

Sol 1:尝试使用active_storage_blobs表中存储的数据查找blob

active_storage_blobs方案为:

create_table "active_storage_blobs", force: :cascade do |t|
t.string "key", null: false
t.string "filename", null: false
t.string "content_type"
t.text "metadata"
t.bigint "byte_size", null: false
t.string "checksum"
t.datetime "created_at", precision: nil, null: false
t.string "service_name"
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end

可以通过使用filenamechecksum来查找blob,但是您需要自己实现查询。有一些问题需要注意:

  1. filenamechecksum默认没有索引,当blob增加时可能会出现效率问题。如果需要,您可以考虑将它们编入索引。
  2. 需要知道filenamechecksum是如何在活动存储中生成的,并在进行查询时将其应用于文件。

Sol 2:创建另一个模型来跟踪和维护文件上传状态

第一个解决方案是一个小hack,因为active_storage_blobs应该是内部的,我们不应该在rails应用程序中直接访问它们。

一个更好的方法是创建一个模型来跟踪和维护上传的文件,并决定为查询文件存储什么信息。例如,我们可以用checksum字段创建一个UploadedFile模型:

db模式
create_table "uploaded_files", force: :cascade do |t|
t.string "checksum", null: false
t.index ["checksum"], name: "index_uploaded_files_on_checksum", unique: true
end

并实现处理上传的相关方法如下:

app/模型/uploaded_file.rb

class UploadedFile < ApplicationRecord
has_one_attached(:uploaded_file)
class << self
def find_or_create_file_blob(file)
blob = find_blob_by_given_file(file)
if blob.blank?
blob = create_uploaded_file_and_do_upload(file)
end
blob
end
private
def find_blob_by_given_file(file)
checksum = calculate_checksum(file)
existing_uf = UploadedFile.where(checksum:).take
existing_uf.present? ? existing_uf.uploaded_file.blob : nil
end
def create_uploaded_file_and_do_upload(file)
checksum = calculate_checksum(file)
uf = UploadedFile.create(checksum:)
uf.uploaded_file.attach(file)
if uf.uploaded_file.attched?
return uf.uploaded_file.blob
else
# do error handling when upload failed
end
end
def calculate_checksum(file)
# implement how to calculate checksum with a given file
end
end
# ...
end

最后,当上传文件到persion.avatar时,不是直接更新persion.avatar,而是先使用UploadedFile.find_or_create_file_blob找到blob,然后将其附加到persion.avatar

blob = UploadedFile.find_or_create_file_blob(params[:file])
persion.avatar.attach(blob)

最新更新