Active Storage: How to retain uploaded files on form resubmission?
Tags: tutorial, rails
Published: 2021-10-09 13:22:58

---
title: Active Storage: How to retain uploaded files on form resubmission?
published: true
description: Hands on tutorial about Active Storage to keep your files after form resubmission.
tags: tutorial, rails
//cover_image: https://direct_url_to_image.jpg
---

This article is also available on [Medium](https://medium.com/@TETRA2000/active-storage-how-to-retain-uploaded-files-on-form-resubmission-91b57be78d53).

---

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?](https://stackoverflow.com/a/58351326)

## 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](https://api.rubyonrails.org/classes/ActiveStorage/Blob.html). This is a model to store file's metadata. And another is [ActiveStorage::Attachment](https://api.rubyonrails.org/classes/ActiveStorage/Attachment.html). This is an intermediate table to combine `ActiveStorage::Blog` to your model.

The`has_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](https://github.com/rails/rails/blob/v6.1.3/activestorage/lib/active_storage/attached/model.rb#L50-L87). Take a look at these 2 lines.

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

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

If you made changes to your model's attachment, those actions are stored as object in `attachment_changes` . For example, [ActiveStorage::Attached::Changes::CreateOne](https://github.com/rails/rails/blob/v6.1.3/activestorage/lib/active_storage/attached/changes/create_one.rb), [ActiveStorage::Attached::Changes::DeleteOne](https://github.com/rails/rails/blob/v6.1.3/activestorage/lib/active_storage/attached/changes/delete_one.rb), 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::Attachment`will be [created and referenced to your model](https://github.com/rails/rails/blob/5aaaa1630ae9a71b3c3ecc4dc46074d678c08d67/activestorage/lib/active_storage/attached/changes/create_one.rb#L32-L35) on the `save` method.

And attached files will be [uploaded](https://github.com/rails/rails/blob/5aaaa1630ae9a71b3c3ecc4dc46074d678c08d67/activestorage/lib/active_storage/attached/changes/create_one.rb#L23-L30) 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](https://api.rubyonrails.org/classes/ActiveRecord/SignedId.html) to sent on form resubmission.

```haml
= f.file_field :doc
= f.hidden_field :doc, value: f.object.doc.signed_id if f.object.doc.attached?
```

I used this page as a reference.

[When using activestorage in Rails 6, how do I retain a file when redisplaying a form?](https://stackoverflow.com/a/58351326)

### Step.2

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

```ruby
# 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
```

That's it!