SmartHRプロダクトエンジニアのgongo(@gongoZ)です。 年末調整機能を担当しています。 最近の週末の過ごし方は醤油ラーメン(中華そば)を食べることです。
今回は Ruby on Rails が提供する ActiveStorage に関連した「こういうことをやりたいけど困ったこと」と、「それをどのように解消したのか」を紹介します。
本記事で紹介する現象
再現させるための最小コードです。Ruby on Rails 6 以降であれば再現できます。
# # 全文 https://gist.github.com/gongo/f3bcff91a1773b1640ca97f9320600f4 # class User < ActiveRecord::Base has_one_attached :profile end user = User.create! ActiveRecord::Base.transaction do user.profile.attach( content_type: 'text/plain', filename: 'dummy.txt', io: ::StringIO.new('dummy'), ) user.profile.download # => raise ActiveStorage::FileNotFoundError end user.profile.download # => "dummy"
- トランザクション内で ActiveStorage の attach を実行する
- 同トランザクション内で 1 で attach したファイルの download を試みる
ActiveStorage::FileNotFoundError
が発生してしまう(downloadに成功してほしかった)- トランザクション完了後は問題なく download できる
想定するユースケースとしては「attach したファイルをすぐダウンロードして、別の何かに使いたい」という場面です。無さそうだけどあると思います。
なぜ本現象が起きるのか
前項の再現コードと比較しながら動きを見ていきましょう。
- モデルを作成
- 作成したモデルに attach すると
active_storage_attachments
やactive_storage_blobs
レコードが作成される - しかし トランザクション中はストレージサービスにアップロードされていないため download に失敗する
- トランザクション完了後にアップロードされる
- それ以降は download に成功する
そうです。attach したファイルがストレージサービスにアップロードされるのはトランザクションを完了したあとなのです。
( after_commit
で upload している様子 )
本現象の対応策
トランザクション完了してから download する と解決します!!
しかし「どうしても……どうしても同トランザクション内で download したい……」という方は次の方法をお試しください。 ずばり attach 前にファイルをアップロードしておく です。
具体的には ActiveStorage::Blob.create_and_upload!
を使います。
user = User.create! ActiveRecord::Base.transaction do profile_blob = ActiveStorage::Blob.create_and_upload!( content_type: "text/plain", filename: "dummy.txt", io: ::StringIO.new("dummy"), ) user.profile.attach(profile_blob) user.profile.download # => "dummy" end user.profile.download # => "dummy"
- モデルを作成
ActiveStorage::Blob.create_and_upload!
を実行したタイミングでストレージサービスへのアップロードが行なわれ、同時にactive_storage_blobs
が作成されるactive_storage_blobs
レコードを attach することでactive_storage_attachments
レコードが作成される- トランザクション中だが、すでにストレージサービスにアップロード済みなので、download に成功する
- トランザクション完了後も、問題なく download に成功する
#attach
は ActiveStorage::Blob も渡せる ので、今回の対応が可能となりました。
この方法の注意点
先にアップロードを行なう関係上、ロールバックによって active_storage_blobs
レコードが消滅してしまうと どことも紐付かないストレージオブジェクト が生まれてしまいます。
その状況に備えて「ロールバックを検出して削除する仕組み」や「浮いたストレージオブジェクトを掃除する仕組み」を用意しておくと安心です。
まとめ
私が前回書いた記事も、ActiveRecord と ActiveStorage を仲良くする方法を紹介するものでした。
ActiveRecord トランザクションと ActiveStorage をちょっとだけ仲良くさせる方法 - SmartHR Tech Blog
あれから5年以上経過し、ActiveStorage もどんどん進化を続けていますが、まだまだ躓くことも多いなと感じる今日この頃です。でも便利なので ActiveStorage 大好き!!
We Are Hiring!
SmartHR では一緒に SmartHR を作りあげていく仲間を募集中です! 少しでも興味を持っていただけたら、カジュアル面談でざっくばらんにお話ししましょう!