ActiveStorage × CallbackでActiveStorage::FileNotFoundError

engineer

課題

(Rails 6.0.0)下記のようなUserモデルを考える. has_one_attachedでimageを持たせておく.

class User < ApplicationRecord
  has_one_attached :image
end

このモデルにコールバックを設定する。

class User < ApplicationRecord
  has_one_attached :image

  before_save do
    filename = ActiveStorage::Blob.service.path_for(image.key)
    puts "#{filename} "+(File.exist?(filename) ? "exists": "doesn't exist")
    f = File.open(ActiveStorage::Blob.service.path_for(image.key))
  end

  after_save do
    filename = ActiveStorage::Blob.service.path_for(image.key)
    puts "#{filename} "+(File.exist?(filename) ? "exists": "doesn't exist")
    f = File.open(ActiveStorage::Blob.service.path_for(image.key))
  end

  after_commit do
    filename = ActiveStorage::Blob.service.path_for(image.key)
    puts "#{filename} "+(File.exist?(filename) ? "exists": "doesn't exist")
    f = File.open(ActiveStorage::Blob.service.path_for(image.key))
  end
end

それぞれbefore_save, after_save, after_commitのタイミングでFile.exist?を行い、ファイルの存在確認をしています.

結果

// before_saveのタイミング
> "/test/storage/ay/9y/ay9yt1l70jmqw1mw20yykkcndb01 doesn't exist"

// after_saveのタイミング
> "/test/storage/ay/9y/ay9yt1l70jmqw1mw20yykkcndb01 doesn't exist"

// after_commitのタイミング
> "/test/storage/ay/9y/ay9yt1l70jmqw1mw20yykkcndb01 doesn't exist"

説明

has_one_attached内で下記のコールバックが設定されている.

after_commit(on: %i[ create update ]) { attachment_changes.delete(name.to_s).try(:upload) }

after_commit系のコールバックは逆順(reverse order)で呼び出されるよう.
上記の場合 after_commitを呼び出す -> uploadの順となるためファイルが見つからないらしい.

解決策

①コールバックとhas_one_attachedを逆にする

class User < ApplicationRecord
  before_save do
    filename = ActiveStorage::Blob.service.path_for(image.key)
    puts "#{filename} "+(File.exist?(filename) ? "exists": "doesn't exist")
    f = File.open(ActiveStorage::Blob.service.path_for(image.key))
  end

  after_save do
    filename = ActiveStorage::Blob.service.path_for(image.key)
    puts "#{filename} "+(File.exist?(filename) ? "exists": "doesn't exist")
    f = File.open(ActiveStorage::Blob.service.path_for(image.key))
  end

  after_commit do
    filename = ActiveStorage::Blob.service.path_for(image.key)
    puts "#{filename} "+(File.exist?(filename) ? "exists": "doesn't exist")
    f = File.open(ActiveStorage::Blob.service.path_for(image.key))
  end

  has_one_attached :image
end

// before_saveのタイミング
> "/test/storage/ay/9y/ay9yt1l70jmqw1mw20yykkcndb01 doesn't exist"

// after_saveのタイミング
> "/test/storage/ay/9y/ay9yt1l70jmqw1mw20yykkcndb01 doesn't exist"

// after_commitのタイミング
> "/test/storage/ay/9y/ay9yt1l70jmqw1mw20yykkcndb01 exist"

※commitされるまで実ファイルが存在しないのは正しい挙動(Rails 6.0.0~)

参考: https://github.com/rails/rails/pull/33303

②prepend: trueをつける

※prepend_trueについて: https://railsguides.jp/active_record_callbacks.html

class User < ApplicationRecord

  has_one_attached :image

  after_commit :file_exist_check, prepend: true

  private
  def file_exist_check
    filename = ActiveStorage::Blob.service.path_for(image.key)
    puts "#{filename}"+(File.exist?(filename) ? "exists": "doesn't exist")
  end
end
// before_saveのタイミング
> "/test/storage/ay/9y/ay9yt1l70jmqw1mw20yykkcndb01 doesn't exist"

// after_saveのタイミング
> "/test/storage/ay/9y/ay9yt1l70jmqw1mw20yykkcndb01 doesn't exist"

// after_commitのタイミング
> "/test/storage/ay/9y/ay9yt1l70jmqw1mw20yykkcndb01 exist"

参考

ActiveStorage::FileNotFoundError in before_save · Issue #36994 · rails/rails
Steps to reproduce Example Repo Background I'm using Active Storage with Disk storage. I want users to upload a json file attached to a model called Test_Model....

コメント

タイトルとURLをコピーしました