ActiveStorageを使ったアプリケーションで、複数ファイルを添付できるhas_many_attached
にdependent: :destroy
を設定したい場合があります。特にS3などの外部ストレージを使っていると、不要なファイルや関連情報が残らないように管理することが重要です。
この記事では、dependent: :destroy
を設定できない理由と、それを補完する方法を紹介します。
問題: has_many_attached
ではdependent: :destroy
を設定できない
has_many_attached
は、ActiveStorageが内部でactive_storage_attachments
テーブルを通じてモデルとファイルを関連付けています。この仕組みは通常のActiveRecordの関連付けとは異なるため、dependent: :destroy
を直接設定することはできません。
また、S3のようなリモートストレージを使っている場合、ストレージ上の実ファイルも適切に削除する必要があります。
解決策: コールバックを使う
dependent: :destroy
が使えない代わりに、モデルのbefore_destroy
やafter_destroy
コールバックを利用して、関連付けられたファイルとそのメタデータ(active_storage_blobs
やactive_storage_attachments
)を削除できます。
1. コールバックを使ったファイル削除
以下のように、before_destroy
コールバックでファイルを削除します。
class Document < ApplicationRecord
has_many_attached :files
before_destroy :purge_attached_files
private
# ファイルとその関連情報を削除
def purge_attached_files
files.each(&:purge_later) # 非同期で削除
end
end
ポイント
-
purge_later
: ファイルを非同期で削除します(推奨)。S3などのリモートストレージを使用している場合でもパフォーマンスに優れています。 -
purge
: 同期的に削除しますが、大量のファイル削除時には時間がかかる可能性があります。
削除される内容
- ストレージ上の実ファイル。
-
active_storage_attachments
の関連情報。 -
active_storage_blobs
のメタデータ。
2. ActiveStorageの関連情報だけを削除
ファイル自体を削除せず、ActiveStorageの関連情報(active_storage_attachments
)だけを削除したい場合は、以下のように実装します。
class Document < ApplicationRecord
has_many_attached :files
before_destroy :remove_active_storage_attachments
private
# ActiveStorageの関連情報を削除
def remove_active_storage_attachments
files.attachments.each(&:destroy) # attachments情報のみ削除
end
end
この方法では、active_storage_blobs
や実際のファイルは削除されないため、手動でクリーニングが必要です。
3. ActiveStorageの残データを定期的に削除
万が一、削除漏れが発生した場合に備えて、ActiveStorageが提供する以下のタスクでデータをクリーンアップできます。
rails active_storage:clean # 未使用のファイルを削除
rails active_storage:purge_unattached # 関連付けられていないBlobsを削除
これらをCronやスケジュールジョブ(Sidekiqなど)で定期実行すると、ストレージを効率的に管理できます。
まとめ
has_many_attached
ではdependent: :destroy
を直接使用できませんが、以下の方法で同様の動作を実現できます。
-
コールバックで
purge_later
を使う(推奨)- ファイル本体と関連データを削除。
-
attachments.each(&:destroy)
で関連データのみ削除- ファイル本体は削除せず、柔軟なカスタマイズが可能。
-
定期的にクリーンアップタスクを実行
- 削除漏れやストレージの膨張を防止。
用途や要件に応じて、適切な方法を選択してください。特にS3のような外部ストレージを使用している場合は、purge_later
を活用するのがおすすめです。