SmartHR Tech Blog

SmartHR 開発者ブログ

ActiveStorage の attach → download を同トランザクション内で行なう時に起こりうる現象と対応

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"
  1. トランザクション内で ActiveStorage の attach を実行する
  2. 同トランザクション内で 1 で attach したファイルの download を試みる
  3. ActiveStorage::FileNotFoundError が発生してしまう(downloadに成功してほしかった)
  4. トランザクション完了後は問題なく download できる

想定するユースケースとしては「attach したファイルをすぐダウンロードして、別の何かに使いたい」という場面です。無さそうだけどあると思います。

なぜ本現象が起きるのか

前項の再現コードと比較しながら動きを見ていきましょう。

再現する過程

  1. モデルを作成
  2. 作成したモデルに attach すると active_storage_attachmentsactive_storage_blobs レコードが作成される
  3. しかし トランザクション中はストレージサービスにアップロードされていないため download に失敗する
  4. トランザクション完了後にアップロードされる
  5. それ以降は 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"

トランザクション完了前でも問題なく動く様子

  1. モデルを作成
  2. ActiveStorage::Blob.create_and_upload! を実行したタイミングでストレージサービスへのアップロードが行なわれ、同時に active_storage_blobs が作成される
  3. active_storage_blobs レコードを attach することで active_storage_attachments レコードが作成される
  4. トランザクション中だが、すでにストレージサービスにアップロード済みなので、download に成功する
  5. トランザクション完了後も、問題なく download に成功する

#attachActiveStorage::Blob も渡せる ので、今回の対応が可能となりました。

この方法の注意点

先にアップロードを行なう関係上、ロールバックによって active_storage_blobs レコードが消滅してしまうと どことも紐付かないストレージオブジェクト が生まれてしまいます。 その状況に備えて「ロールバックを検出して削除する仕組み」や「浮いたストレージオブジェクトを掃除する仕組み」を用意しておくと安心です。

まとめ

私が前回書いた記事も、ActiveRecord と ActiveStorage を仲良くする方法を紹介するものでした。

ActiveRecord トランザクションと ActiveStorage をちょっとだけ仲良くさせる方法 - SmartHR Tech Blog

あれから5年以上経過し、ActiveStorage もどんどん進化を続けていますが、まだまだ躓くことも多いなと感じる今日この頃です。でも便利なので ActiveStorage 大好き!!

We Are Hiring!

SmartHR では一緒に SmartHR を作りあげていく仲間を募集中です! 少しでも興味を持っていただけたら、カジュアル面談でざっくばらんにお話ししましょう!

hello-world.smarthr.co.jp