ActiveStorage: How to update an attachment?

1,259 views
Skip to first unread message

hennin...@hausgold.de

unread,
Aug 25, 2017, 7:51:17 AM8/25/17
to Ruby on Rails: Talk
We currently switched to Rails 5.2 to get the awesome functionalities of ActiveStorage.
Up until now I was able to upload, delete (purge), and show files as expected - but I am not quite sure on how to update an attachment the right way.

Here are the steps to reproduce:

user.avatar.attached?
=> false

user.avatar.attach(io: File.open("~/avatar.png"), content_type: "image/png", filename: "avatar")
=> #<ActiveStorage::Attachment id: 2, name: "avatar", record_type: "User", ...

user.avatar.attached?
=> true

user.avatar.attach(io: File.open("~/avatar2.png"), content_type: "image/png", filename: "avatar2")
=> Exception: ActiveRecord::RecordNotSaved: Failed to remove the existing associated avatar_attachment. The record failed to save after its foreign key was set to nil.

user.avatar.purge
=> nil

user.avatar.attached?
=> false

user.avatar.attach(io: File.open("~/avatar2.png"), content_type: "image/png", filename: "avatar2")
=> #<ActiveStorage::Attachment id: 2, name: "avatar2", record_type: "User", ...


I don't think it should be necessary to purge the first attachment to attach a new one. So how can this be achieved properly?

Carlos Ramirez

unread,
Aug 29, 2017, 9:43:19 AM8/29/17
to Ruby on Rails: Talk
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

user
= User.create!
original_profile
= user.create_profile!
user
.create_profile! # attempt to replace the original profile with a new one
=> 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 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). 

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:
Reply all
Reply to author
Forward
0 new messages