はじめに
Rails6の新機能の1つのActionText(リッチテキストコンテンツと編集をかんたんに導入できる)の関連付けされてない画像データをsidekiqでバックグラウンド削除しようという内容です。
削除jobを実行するPostモデル(ポスト)と実行しないArticleモデル(記事)を使って比較していきます。
WEBページ
GitHub
環境
- Ruby 2.6.3
- Rails 6.0.2
- gem aws-sdk-s3 1.60.2
問題点
こちらがActionTextを実装したPostモデルとArticleモデルのER図になります。
active_storage_blobsにアップロードした画像が保存され、投稿を完了(save)した時にactive_storage_attachmentsにaction_text_rich_textsとの関連情報が保存されます。
しかし、画像をアップロードした時点で active_storage_blobs に新規レコードが作成されてしまい関連付けされなかった場合にも残り続けてしまいます。
以下sidekiqでjobを実行していないArticleモデルでのgifです。
↓一覧画面の上部にactive_storage_attachments, active_storage_blobsを表示しています
画像添付をやめたのにActiveStorage::Blobの数: 2に増えてしまっています。
本題
関連付けされなかったデータを削除するjobを作成します。
class DeleteUnreferencedBlobJob < ApplicationJob
sidekiq_options queue: :default, retry: 3
require 'aws-sdk-s3'
def perform(*args)
# 全Blobのidを取得
blob_ids = ActiveStorage::Blob.pluck(:id)
# 関連付けされているBlobの取得
_blob_ids = ActiveStorage::Attachment.pluck(:blob_id).uniq
# 関連付けされていないBlobの割り出し
unreferenced_blob_ids = blob_ids - _blob_ids
# 関連付けされていないBlobの画像ファイルを削除
if Rails.env.production?
s3 = Aws::S3::Resource.new(
region: 'ap-northeast-1',
credentials: Aws::Credentials.new(
Rails.application.credentials.dig(:aws, :access_key_id), # S3用アクセスキー
Rails.application.credentials.dig(:aws, :secret_access_key) # S3用シークレットアクセスキー
)
)
bucket = s3.bucket('aws-on-rails6')
unreferenced_blob_ids.each do |id|
s3_file_key = ActiveStorage::Blob.find(id).key
bucket.object(s3_file_key).delete
bucket.objects({prefix: "variants/#{s3_file_key}"}).batch_delete!
end
end
# 関連付けされていないBlobの削除
ActiveStorage::Blob.where(id: unreferenced_blob_ids).delete_all
end
end
本番環境のみs3の画像ファイルも削除しています。
これをPostモデルのsave, update, delete時に実行します。
def create
if @post.save
DeleteUnreferencedBlobJob.perform_later
.
.
def update
if @post.update
DeleteUnreferencedBlobJob.perform_later
.
.
def destroy
DeleteUndreferencedBlobJob.perform_later if @post.destroy
.
.
ActiveStorage::Blobの数: 1となり削除されているのが確認できました!
注意点
この実装だとユーザーAが画像添付ポストを作成時、ユーザーBが別のポストをsaveやupdateした場合でもユーザーAのsave前の関連付けされていない画像データが消えてしまうので、複数ユーザーが利用するサービスではメンテナンス時などに実行するのが望ましいでしょう。