【Rails】Active Storageの画像加工時に出たActiveStorage::FileNotFoundErrorについて
はじめに
Rails学習時に調べたことや、遭遇したエラーなどについて書いております。
間違いなどありましたら、優しくご指摘いただけましたら幸いです。
対象読者
- Rails初学者の方
- ActiveStorage::FileNotFoundErrorが出てしまって困っている方
- ActiveStorage::Variantのprocessedメソッドについて知りたい方
まずはActive Storageについて概要を知りたい!という方は、よろしければ以下をご覧ください。
実行環境
Rails 7.0.4
ruby 3.1.4p223
エラーの発生経緯
実装していたのは、「タイトル」、「内容」、「画像」を投稿する簡単なフォームです。
下書き画面で投稿内容を編集し、「更新する」ボタンを押すと再度下書き画面に戻り、「公開する」ボタンを押すことで公開ページに遷移するような仕様になっています。
Postモデルは以下のように設定しています。Postモデルには1つ画像がアタッチできるようになっています。タイトルに presence: true
のバリデーションをつけています。
# == Schema Information
#
# Table name: posts
#
# id :bigint not null, primary key
# title :string(255) not null
# content :text(65535)
# created_at :datetime not null
# updated_at :datetime not null
#
# Indexes
# index_posts_on_user_id
#
class Post < ApplicationRecord
belongs_to :user
has_one_attached :image
validates :title, presence: true
end
画像ファイルの表示には、デコレーターに定義しているActive StorageのVariantを使用しています。この例のように複数バージョンの画像を用意したいときには、 processed
をつけることで効率的な処理になります。processed
メソッドは、対象のvariantがすでに保存されていればそのvariantを返すので、2回目以降のアクセスでは高速になるというメリットがあります。
(※ デコレーターにはDraperではなく、ActiveDecoratorを使用しています。)
= simple_form_for @post, url: post_path(@post) do |f|
= f.input :title
= f.input :content
= f.input :image, as: :file
- if @post.image.attached?
= image_tag @post.image_url(:thumb)
= f.button :submit, class: %w[btn btn-primary]
= link_to '公開する', post_path(@post), class: %w[btn btn-default]
module PostDecorator
def image_url(version = :origin)
command = case version
when :thumb
{ resize_to_limit: [320, 240] }
when :lg
{ resize_to_limit: [1024, 768] }
when :ogp
{ resize_to_limit: [120, 630] }
else
false
end
command ? image.variant(command).processed : image
end
end
コントローラーの更新関連の箇所の抜粋は以下です。
before_action :set_post, only: %i[edit update destroy]
def edit; end
def update
@post.assign_attributes(post_params)
if @post.save
flash[:notice] = '更新しました'
redirect_to edit_post_url(@post)
else
render :edit
end
end
private
def post_params
params.require(:post).permit(:title, :content, :image)
end
def set_post
@post = Post.find(params[:id])
end
エラーが発生するのは、以下の操作を行った時です。
- 下書き画面で、画像をアップロードする
- 必須入力の「タイトル」を空欄にして、バリデーションエラーを発生させ、データが更新されない状態にする
- 「更新する」ボタンを押下する
post_deorator.rbの image.variant(command).processed
の部分でActiveStorage::FileNotFoundError
が発生します。
なぜこのエラーが発生するのか?
ActiveStorage::FileNotFoundError
が発生する原因を調べるために、 まずFramework Traceを見てみます。
activestorage (7.0.4) lib/active_storage/service/disk_service.rb:150:in `rescue in stream'
activestorage (7.0.4) lib/active_storage/service/disk_service.rb:143:in `stream'
activestorage (7.0.4) lib/active_storage/service/disk_service.rb:29:in `block in download'
activesupport (7.0.4) lib/active_support/notifications.rb:206:in `block in instrument'
activesupport (7.0.4) lib/active_support/notifications/instrumenter.rb:24:in `instrument'
activesupport (7.0.4) lib/active_support/notifications.rb:206:in `instrument'
activestorage (7.0.4) lib/active_storage/service.rb:163:in `instrument'
activestorage (7.0.4) lib/active_storage/service/disk_service.rb:28:in `download'
activestorage (7.0.4) lib/active_storage/downloader.rb:32:in `download'
activestorage (7.0.4) lib/active_storage/downloader.rb:13:in `block in open'
activestorage (7.0.4) lib/active_storage/downloader.rb:24:in `open_tempfile'
activestorage (7.0.4) lib/active_storage/downloader.rb:12:in `open'
activestorage (7.0.4) lib/active_storage/service.rb:90:in `open'
activestorage (7.0.4) app/models/active_storage/blob.rb:301:in `open'
activestorage (7.0.4) app/models/active_storage/variant.rb:109:in `process'
activestorage (7.0.4) app/models/active_storage/variant.rb:64:in `processed'
以下略
processed
がトレースに表示されていますね。この部分のコードを確認したいと思います。
トレースをもう一度見ると、 processed
メソッドの次に process
メソッドが呼ばれていることがわかります。 process
メソッドも確認してみましょう。
次にトレースに記載されているのは、blob.rbの open
なので、上記 process
メソッド内の blob.open
を次に注目します。
blob.open
のところでは、実際にファイルを開き、変更を適用しようとしています。
ここで、エラーが発生した経緯を思い出すと、バリデーションエラーが発生し、画面の表示を行おうとした際に、 ActiveStorage::FileNotFoundError
が発生してしまっていました。なので、この時点ではDBのBlobのデータは保存されていません。にも関わらず blob.open
を実行しているため、エラーが発生してしまっていたということだったんですね。
エラーを解消するには?
現状自分の調べた限りでは、解決方法として2つあるのかなと思っています。
1つ目は、processed
を削除することです。削除すると、 processed
をつけることによるアクセス効率化のメリットはなくなってしまいますが、エラーは解消されます。
2つ目は、 blob.open
が実行される前に、BlobがDBに保存されているかチェックする方法です。
変更点は以下のようになります。
良い方法かはわかりませんが、こちらの方法でもエラーは解消できました。
- - if @post.image.attached?
= image_tag @post.image_url(:thumb)
+ - if @post.image.attached? && @post.image_exists?
= image_tag @post.image_url(:thumb)
+ def image_exists?
+ image.blob.persisted?
+ end
Discussion