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?
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: