Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I'm creating an api that includes a file upload service where the uploaded file can have extra information inputted by the user, stored in a Project model. When the client requests a direct upload from the server, I want to also create a new project and attach the blob while the client is uploading to S3. However, ActiveStorage does not appear to allow attaching the blob if it does not exist in S3 yet.

I considered a few different options for when to attach the blob, but I thought that doing it as the blob and direct upload credentials were created was the best option for my needs. Since I needed some extra logic not covered by the default controllers provided by ActiveStorage, I wrote my own. In it, I create the blob for a direct upload, create the project, attach the blob to the project, and return the signed upload url, headers, and project id. However, it gives me an Aws::S3::Errors::NoSuchKey error when I try to attach the blob to the Project.

In my controller:

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)

In my project model:

has_one_attached :file

I was hoping that ActiveStorage would just let me attach the blob even though it isn't uploaded yet, but it actually does check that the file exists in S3 before doing it. It makes sense that it would do so, but there doesn't appear to be a way to skip the check.

like image 961
Blargel Avatar asked Jul 16 '19 12:07

Blargel


1 Answers

I decided that my reasons to attach the blob before it was uploaded were actually not as well thought out as I thought they were. I am no longer attempting to bypass the S3 check, but I did find a solution to the original problem while investigating it.

ActiveStorage::Blob and ActiveStorage::Attachment are actually both subclasses of ActiveRecord::Base. This means you can basically treat them as normal models. ActiveStorage::Attachment is set up as a polymorphic association to create a many-to-many relationship between ActiveStorage::Blob and any other record. All you need to do is call ActiveStorage::Attachment.create with the correct params to set up the relationship between the newly created blob and record.

The code in the question would be rewritten as:

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)
like image 105
Blargel Avatar answered Sep 22 '22 02:09

Blargel