Solutions for Updating ActiveStorage Attachments

While updating ActiveStorage attachments, particularly those with the has_many_attached association in a Rails application, some common issues can arise. This article will discuss some such issues and their possible solutions.

First, consider we have a Post resource and the Post model contains title, body, and has_many_attached images. Our point of concern would be the images attribute.

1
2
3
class Post < ApplicationRecord
 has_many_attached :images
end

A problem can arise when updating images when images are already attached if somehow the images param comes through as an empty array. A single and generic solution to all the possible ActiveStorage image update issues would require a modification to the update method.

Suppose that a few images are already attached with a record and we now need to edit the Post to attach additional images to it. What will happen is, ActiveStorage will replace the old ones with the new ones rather than appending the new ones after the old ones. On the other hand, if the images attribute comes through as an empty array, it will delete all the already attached images. There is a workaround for both of these situations.

The images param will need to be rejected from permitted params and the images attached one by one. The loop will append the images in the case of the first problem, and will never iterate if the images attribute is an empty array.

1
2
3
4
5
if post_params[:images].present?
 post_params[:images].each do |image|
   @post.images.attach(image)
 end
end

The update method in PostsController will look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def update
 respond_to do |format|
   if @post.update(post_params.reject { |k| k["images"] })
     if post_params[:images].present?
       post_params[:images].each do |image|
         @post.images.attach(image)
       end
     end
     format.html { redirect_to @post, notice: 'Post was successfully updated.' }
     format.json { render :show, status: :ok, location: @post }
   else
     format.html { render :edit }
     format.json { render json: @post.errors, status: :unprocessable_entity }
   end
 end
end

Now multiple potential issues have been avoided. Happy attaching!