ActiveStorage 能否创建一个用于直接上传的 blob 并在上传完成之前将其附加到模型?

Can ActiveStorage create a blob for direct upload and attach it to a model before the upload is finished?

我正在创建一个 api,其中包含一个文件上传服务,其中上传的文件可以包含用户输入的额外信息,这些信息存储在项目模型中。当客户端请求从服务器直接上传时,我还想创建一个新项目并在客户端上传到 S3 时附加 blob。但是,如果 Blob 在 S3 中尚不存在,ActiveStorage 似乎不允许附加该 Blob。

我考虑了何时附加 blob 的几个不同选项,但我认为在创建 blob 和直接上传凭据时执行此操作是满足我需求的最佳选择。由于我需要一些 ActiveStorage 提供的默认控制器未涵盖的额外逻辑,因此我编写了自己的逻辑。在其中,我创建了用于直接上传的 blob、创建项目、将 blob 附加到项目,以及 return 签名上传 url、headers 和项目 ID。但是,当我尝试将 blob 附加到项目时,它给了我一个 Aws::S3::Errors::NoSuchKey 错误。

在我的控制器中:

Project.transaction do
    # Create a project with default attributes
    project = create_draft_project(project_title)

    # Create a blob before direct upload to generate a signed url
    blob = ActiveStorage::Blob.create_before_direct_upload!(
        filename: filename,
        byte_size: byte_size,
        checksum: checksum,
        content_type: content_type
    )

    # Attach the blob to the project. This is where it errors.
    project.file.attach(blob.signed_id)
end

# Render the success response
success_response(project, blob)

在我的项目模型中:

has_one_attached :file

我希望 ActiveStorage 可以让我附加 blob,即使它还没有上传,但它实际上会在执行之前检查文件是否存在于 S3 中。这样做是有道理的,但似乎没有办法跳过检查。

我认为我在上传之前附加 blob 的原因实际上并没有我想象的那么深思熟虑。我不再试图绕过 S3 检查,但我在调查时确实找到了解决原始问题的方法。

ActiveStorage::BlobActiveStorage::Attachment实际上都是ActiveRecord::Base的子类。这意味着您基本上可以将它们视为普通模型。 ActiveStorage::Attachment 被设置为多态关联,以在 ActiveStorage::Blob 和任何其他记录之间创建 many-to-many 关系。您需要做的就是使用正确的参数调用 ActiveStorage::Attachment.create 来设置新创建的 blob 和记录之间的关系。

问题中的代码将被重写为:

Project.transaction do
    # Create a project with default attributes
    project = create_draft_project(project_title)

    # Create a blob before direct upload to generate a signed url
    blob = ActiveStorage::Blob.create_before_direct_upload!(
        filename: filename,
        byte_size: byte_size,
        checksum: checksum,
        content_type: content_type
    )

    # Attach the blob to the project by creating the association in the database directly.
    ActiveStorage::Attachment.create(
        name: 'file',
        record_type: 'Project',
        record_id: project.id,
        blob_id: blob.id
    )
end

# Render the success response
success_response(project, blob)