ActiveStorageを試し、image_tagのエラー文などについて少し調べた
最初に
Qiita: ActiveStorageを使ってお手軽にファイルアップロードを試す
この記事で失敗するという話を見て、久しくActiveStorageを使ってなかったので試してみた。
結論からいうと、画像なしでユーザー登録すると、画像が見つからずview側でエラーが起こる。
ただ、このエラー文がわかりにくく(image_tag
によるもの)、それについて少し調べた。
あと、解決方法のメソッドattached?
, present?
, blank?
について気になった点があったので、少し調べた。
Qiita記事と異なった些細な点について、いくつか
Qiita: ActiveStorageを使ってお手軽にファイルアップロードを試す
記事通りにやるだけだが、いくつか記事と異なる点があった。
バージョン違い
mini_magick -> image_processing
また、Rails 6からmini_magick
ではなくimage_processing
をデフォルトで使うことになっているらしく、
Gemfileにはimage_processing
が書かれていて、その代わりに記事通りにmini_magick
を使った。
今回の実験では、mini_magick
で特に問題はなかった。
rails db:create
rails db:migrate
の前に1回rails db:create
を挟む必要があった。
sqlight3では不要ぽいが、PostgreSQLでは必要。
Viewの画面でのエラー
<% @users.each do |user| %> <li> <p><%= user.name %></p> <%= image_tag user.avatar %> </li> <% end %>
の<%= image_tag user.avatar %>
で、ユーザーがavatar画像を持たないとエラーになる。
画像を持っていれば、別にエラーにはならない。
image_tag内で起こっているエラーだが、これがわかりにくかった。
なお、このエラーはS3を使う前でも確かめられる。
ArgumentError in Users#index Can't resolve image into URL: undefined method `persisted?' for nil:NilClass
また、あとで少し書くが、user.avatar
はActiveStorage::Attached::One
クラスのインスタンスで、
画像がなくても直接nil
になっているわけではないようだ。画像のないActiveStorage::Attached::One
クラスのインスタンスが、
image_tag
ヘルパーメソッドに渡されるとエラーになる。
ArgumentError
が起こっているらしいが、エラーの説明文の中にundefined method
の文字が見える。
undefined method
ならNoMethodError
なのではないかと最初は思った。
どうも調べてみると、Railsのimage_tag
ヘルパーメソッド内部でnil.persisted?
が呼ばれNoMethodError
が起こり、
image(画像)をURL(文字列)に変換できなかったため、
そういうエラーが起きるような引数があったということでArgumentError
となっているらしい。
nil.persisted?
が呼ばれたという情報はRails内部のことで、出来ればもっと隠蔽して、もっとわかりやすいエラーにして欲しいなと思った。
image_tag(1)
image_tag
に1
を与えてみると、同じ「Can't resolve image into URL: 」というエラー説明文が表示された。
ArgumentError in Users#index Can't resolve image into URL: undefined method `to_model' for 1:Integer Did you mean? to_yaml
image_tag(nil)
nilを与えると、次のようなエラーになった。nilは、場所となる画像URLを生成できないから、エラーになるらしい。
ArgumentError in Users#index Nil location provided. Can't build URI.
image_tag(適当な文字列)
適当な文字列を与えると、次のようなエラーとなった。
Sprockets::Rails::Helper::AssetNotFound in Users#index The asset "foo" is not present in the asset pipeline.
fooという場所が存在しないから、エラーになるらしい。 引数の種類としては変ではないせいか、ArgumentErrorにはならなかった。
エラー回避方法
<%= image_tag user.avatar %>
を以下のどれかに変更して、
画像が存在するときにだけ、image_tag
にuser.avatar
を渡してあげれば解決する。
<%= image_tag user.avatar if user.avatar.present? %> <%= image_tag user.avatar if user.avatar.attached? %> <%= image_tag user.avatar unless user.avatar.blank? %>
いずれも画像を持つかどうかを調べるメソッドだが、
この3つのpresent?
, attached?
, blank?
の関係性について見ていく。
present?メソッドなどの詳細
ここでuser.avatar
というのはActiveStorage::Attached::One
クラスのインスタンスである。
メソッドの中身を紐解くと、present?
とattached?
は機能的に同じメソッドである。
present?
は、ActiveSupportによるRails拡張のメソッドで、
Object
クラスに定義しているため、それを継承しているActiveStorage::Attached::One
でも使えている。
present?
は、内部で単にblank?
メソッドを呼んで否定しているだけである。
def present? !blank? end
そして、blank?
とattached?
メソッドは、ActiveStorage::Attached::One
に定義されている。
Rails API: ActiveStorage::Attached::Oneのドキュメントから確かめることができる。
def blank? !attached? end
ActiveStorage::Attached::One#blank?
は、単にattached?
を呼んで否定するように定義されている。
つまり、present?
メソッドは、attached?
の否定の否定なので、機能的には結局attached?
と等しかった。
attached?
の方がきっと僅かに速いだけなので、この2つはエイリアスと呼んでいいと思う。
最後に
Railsのエラー文はもっとわかりやすくなって欲しいなと思った。終わり。