March 19, 2019; in Ruby on Rails

Purging ActiveStorage attachments with a familiar API

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.