DEV Community

Takahiko Inayama
Takahiko Inayama

Posted on

Active Storage: How to retain uploaded files on form resubmission?

This article is also available on Medium.


If you've ever used Active Storage, I bet you must have encountered the same problem I was confused by.

Imagine if your app has a simple form with file upload tags along with other fields. Then one of the fields has unmet with validation rules on submission. So your app displays the form again. Finally, the user corrected the error and submit again. But files that should have saved were simply disappeared...

This kind of story isn't uncommon if you're using Active Storage. I had faced this too. But I found a solution after all.

Note:

If you can use direct_upload in your application, you don't need to read this article. You can simply use this solution.

When using activestorage in Rails 6, how do I retain a file when redisplaying a form?

TL;DR

  • You need to upload files and save ActiveStorage::Blob objects manually before form reappearance.
  • You need to submit blob's key using hidden fields.

Background

Active Storage uses 2 models to store attachment information to database. First one is ActiveStorage::Blob. This is a model to store file's metadata. And another is ActiveStorage::Attachment. This is an intermediate table to combine ActiveStorage::Blog to your model.

Thehas_one_attached method makes your model to manage these 2 models automatically. I'll talk about has-one case but most logics are same as has-many one.

Source code of has_one_attached is here. Take a look at these 2 lines.

after_save { attachment_changes[name.to_s]&.save }

after_commit(on: %i[ create update ]) { attachment_changes.delete(name.to_s).try(:upload) }
Enter fullscreen mode Exit fullscreen mode

If you made changes to your model's attachment, those actions are stored as object in attachment_changes . For example, ActiveStorage::Attached::Changes::CreateOne, ActiveStorage::Attached::Changes::DeleteOne, and so on.

Your model will be configured to call these objects's save method right before database access. (after_save hook will be called before actual changes to the database.) Objects of ActiveStorage::Blob and ActiveStorage::Attachmentwill be created and referenced to your model on the save method.

And attached files will be uploaded on after_commit hook.

What is the root cause of problem?

Attachment persistence is handled when you save your model, as I mentioned in the previous section. This is a problem because your model won't be saved before form resubmission.

Solution

Problems are identified. What should we do?

Step.1

You need to add this hidden fields to your form. This makes file's signed_id to sent on form resubmission.

= f.file_field :doc
= f.hidden_field :doc, value: f.object.doc.signed_id if f.object.doc.attached?
Enter fullscreen mode Exit fullscreen mode

I used this page as a reference.

When using activestorage in Rails 6, how do I retain a file when redisplaying a form?

Step.2

You need to write these codes on your app. This will let you to upload files and save ActiveStorage::Blob object manually.

# In your app's controller
def update
  ...
  # `obj` is your model that using `has_one_attached`.
  if(obj.update)
    redirect_to ...
  else
    obj.attachment_changes.each do |_, change|
      if change.is_a?(ActiveStorage::Attached::Changes::CreateOne)
        change.upload
        change.blob.save
      end
      # TODO: ActiveStorage::Attached::Changes::CreateMany
    end
    ...
  end
end
Enter fullscreen mode Exit fullscreen mode

That's it!

Top comments (1)

Collapse
 
flp profile image
flp

anyone an idea how to achieve that with the current active storage changes in github.com/rails/rails/commit/d092...?