I wanted to give a user the ability to check a box to delete an ActiveStorage Attachment, but I wanted it to feel like ActiveRecord was doing it itself.
{:.no_toc}
Naive implementation
A really naive implementation might look like this:
In our app/models/user.rb
:
class User < ApplicationRecord
has_one_attached :user
has_one_attached :company_logo
end
And in a controller app/controllers/users_controller.rb
:
class UsersController < ApplicationController
def update
if params[:delete_avatar]
current_user.avatar.purge
end
if params[:company_logo]
current_user.company_logo.purge
end
end
end
This stinks. User
shouldn’t be running this in the controller, this should be the responsibility of the model. We can
have an equally-naive solution in the model, but we can do better with a little prior preparation.
ActiveModel::Attributes
This is a great fit for ActiveModel::Attributes
. As I’ve previously discussed, ActiveModel::Attributes
gives you all the conveniences of a database-backed column without the need for the column. This means we can have boolean-y types with boolean-y callbacks and boolean-y dirty methods without the boolean-y database column.
A better solution
Our model:
class User
has_one_attached :avatar
has_one_attached :company_logo
[:avatar, :company_logo].each do |attachment|
attribute "purge_#{attachment}", :boolean, default: false
after_save if: :"purge_#{attachment}?" do
self.public_send(attachment).purge_later
end
end
end
And our controller:
class UsersController < ApplicationController
def update
current_user.update!(user_params)
# ...
end
private
def user_params
params.require(:purge_avatar, :purge_company_logo)
end
end
This gives us the form helpers, the callbacks, and everything else that we love about database-backed attributes with a familiar DSL to all developers.