How to update attachment in ActiveStorage (Rails 5.2)

I have the same problem the with image saving. I hope this will help

class User < ApplicationRecord
  has_one_attached :avatar
end

let look at the form and controller

= simple_form_for(@user) do |f|
  = f.error_notification
  .form-inputs
    = f.input :name
    = f.input :email
    = f.input :avatar, as: :file

  .form-actions
    = f.button :submit

controllers/posts_controller.rb

def create
    @user = User.new(post_params)
    @user.avatar.attach(params[:post][:avatar])
    respond_to do |format|
      if @user.save
        format.html { redirect_to @user, notice: 'Post was successfully created.' }
        format.json { render :show, status: :created, location: @user }
      else
        format.html { render :new }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

The cause of the error

This error is being raised by the has_one association between your model and the attachment record. It occurs because trying to replace the original attachment with a new one will orphan the original and cause it to fail the foreign key constraint for belongs_to associations. This is the behavior for all ActiveRecord has_one relationships (i.e. it’s not specific to ActiveStorage).

An analogous example

class User < ActiveRecord::Base
   has_one :profile
end
class Profile < ActiveRecord::Base
   belongs_to :user
end

# create a new user record
user = User.create!

# create a new associated profile record (has_one)
original_profile = user.create_profile!

# attempt to replace the original profile with a new one
user.create_profile! 
 => ActiveRecord::RecordNotSaved: Failed to remove the existing associated profile. The record failed to save after its foreign key was set to nil.

In attempting to create a new profile, ActiveRecord tries to set the user_id of the original profile to nil, which fails the foreign key constraint for belongs_to records. I believe this is essentially what is happening when you try and attach a new file to your model using ActiveStorage... doing so tries to nullify the foreign key of the original attachment record, which will fail.

The solution

The solution for has_one relationships is to destroy the associated record before trying to create a new one (i.e. purging the attachment before trying to attach another one).

user.avatar.purge # or user.avatar.purge_later
user.avatar.attach(params[:file])

Is this desired behavior?

Whether or not ActiveStorage should automatically purge the original record when trying to attach a new one for has_one relationships is a different question best posed to the core team...

IMO having it work consistently with all other has_one relationships makes sense, and it may be preferable to leave it up to the developer to be explicit about purging an original record before attaching a new one rather than doing it automatically (which may be a bit presumptuous).

Resources:

  • Source code for has_one error raising behavior
  • Test case for has_one error raising behavior

You can call purge_later before attach when using has_one_attached:

user.avatar.purge_later
user.avatar.attach(params[:file])

Update

Rails now purges previous attachment automatically (since Aug 29th).