🖼️

CarrierWaveでファイルアップロード時に変更前のファイルを削除しない方法

2023/09/30に公開

こんにちは株式会社スペースマーケットでエンジニアをしているtchmrです。

最近ようやく暑さが落ち着いてきたので朝散歩が日課の自分にとっては最高のシーズンになってきました。
朝は脳のボーナスタイムなので考え事がまとまったり、インプットが捗ったりしやすいです。
特にオーディオブックやポッドキャストとの相性は最高で、健康増進と知識のインプット、さらに一日をいい気分で始められる、一石三鳥な習慣です。

朝散歩についてもっと語りたいところではありますが、ここはぐっと我慢して本題に移りたいと思います。笑

記事の概要

Railsのファイルアップロードでよく使われるライブラリの一つとしてCarrierWaveがあるかと思います。
2010年頃から2023年現在でもメンテナンスされておりスター数も多いため一定の信頼性があるgemと言えるかと思います。

便利なgemなのですがアップロードファイルの更新時の挙動が更新前のファイルを削除 + 新規ファイルをアップロードとなるため、以下の要件を実現するためには設定を少しいじってやる必要がありました。

要件

画像ファイルがアップロードされた際に変更前のファイルを残しつつ新しいファイルを追加したい。
また、リソースを削除する場合もファイルは残したい。
背景: 監査対応などを想定して特定の時点にアップロードされていたファイル後から特定することができるようにしたいという場合
※ アップロードするファイルはユーザーのプロフィール画像とします。
※ アップロードするストレージはS3とし、ディレクトリはuploads/users/*とします。

デフォルトの挙動

class User
  mount_uploader :avatar, AvatarUploader
  ...
end

登録時

ユーザーにプロフィール画像(first_image.png)を設定した上で登録します。
これにより、指定のディレクトリに画像ファイルがアップロードされます。

uploads/users/1
- first_image.png

更新時

ユーザーに別のプロフィール画像(second_image.png)を設定した上で更新します。
これにより、変更前の画像ファイルが削除され、変更後の画像ファイルがディレクトリにアップロードされます。

uploads/users/1
- second_image.png

削除時

ユーザーを削除します。
これにより、アップロードしたファイルが削除されます。

uploads/users/1
- ファイルなし

解決方法

READMEにズバリ書いてありました!笑

mount_uploaderの記述により以下のコールバックが暗黙的に定義されます。
そのため、実行したくない処理をスキップする定義を追加することで解決できます。

before_save :write_avatar_identifier
after_save :store_previous_changes_for_avatar
# ↓を実行したくない
after_commit :remove_avatar!, on: :destroy
after_commit :mark_remove_avatar_false, on: :update
# ↓を実行したくない
after_commit :remove_previously_stored_avatar, on: :update
after_commit :store_avatar!, on: [:create, :update]
class User
  mount_uploader :avatar, AvatarUploader
  skip_callback :commit, :after, :remove_avatar!
  skip_callback :commit, :after, :remove_previously_stored_avatar
  ...
end

対応後の挙動

登録時

ユーザーにプロフィール画像(first_image.png)を設定した上で登録します。
after_commit :store_avatar!, on: [:create, :update]のコールバックによりファイルがアップロードされます。

uploads/users/1
- first_image.png

更新時

ユーザーに別のプロフィール画像(second_image.png)を設定した上で更新します。
after_commit :remove_previously_stored_avatar, on: :updateによる変更前ファイルの削除処理をスキップします。
after_commit :store_avatar!, on: [:create, :update]によりファイルがアップロードされます。

uploads/users/1
- first_image.png (更新前ファイルが残る!)
- second_image.png

削除時

ユーザーを削除します。
after_commit :remove_avatar!, on: :destroyによるアップロードファイルの削除処理をスキップします。

uploads/users/1
- first_image.png (削除後もファイルは残る!)
- second_image.png (削除後もファイルは残る!)

感想

上記のコールバックが設定されることを知ることでCarrierWaveの挙動の理解がクリアになりました。
CarrierWaveに限らず、ライブラリの処理自体はブラックボックスになりがちですが、READMEやドキュメント、コードを追うなどしてなぜそのような挙動となるのかを理解しておくことが重要だと改めて感じました。
こうしておくことで、少し捻った要件やバグの対応も適切かつ迅速に行えるので胸に刻んでおきたいと思います。

スペースマーケット Engineer Blog

Discussion