はじめに
ActiveStrageの使い方を知ろうと思い簡単なアプリケーションを作っていたところimage_tagで発生したエラーについて調べてみました。
エラー発生時のコード
画像挿入実装の前提
- Mac OS(imagemagicインストール済み)
- image_processing 1.12.2利用
- ActiveStorageで保存した画像を表示させる
- 画像の表示は存在する時のみ表示 ⇦ 結局ここで引っかかった
実装内容
理想の画面イメージはこんな感じ。
ところが、ActiveStrageで保存した画像を表示させるテンプレートで下記のようなimage_tagを利用した表記を試したところいくつかエラーが発生しました。
Can’t resolve image into URL: undefined method `persisted?’ for nil:NilClass
下記のようにテンプレートを実装したところ<%= image_tag user.avatar %>
で発生したエラーです。
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= user.name %></td>
<td><%= user.email %></td>
<td><%= user.code %></td>
<td><%= user.address %></td>
<td><%= image_tag user.avatar %></td> <%# ⇦ココで発生している %>
<td><%= link_to t('views.common.show'), user %></td>
<td><%= link_to t('views.common.destroy'), user, method: :delete, data: { confirm: t('views.common.delete_confirm') } %></td>
</tr>
<% end %>
</tbody>
user.avatorはActiveStorageのクラスであるActiveStorage::Attached::Oneを持っています。
画像が存在しない場合、そのActiveStorage::Attached::OneクラスがNull値としてuser.avatarに代入されることが根本原因です。
image_tagのエラーというより「Nilクラスはpersisited?メソッドを持たないよ」というエラーです。
この文言をimage_tagでURIとして解釈しようとしてしまい今回のようなエラーが発生してしまいました。
Nil location provided. Can’t build URI.
同様に<%= image_tag user.avatar %>
で発生したエラーです。
一つ目のエラーとは違い、user.avator.variant() メソッドを利用しています。
<tbody>
<% @users.each do |user| %>
<tr>
<td><%= user.name %></td>
<td><%= user.email %></td>
<td><%= user.code %></td>
<td><%= user.address %></td>
<td><%= image_tag user.avatar.variant( resize: "64x64^", gravity: "center", crop: "100x100+0+0" ) if user.avatar.attached?%></td> <%# ⇦ココで発生している %>
<td><%= link_to t('views.common.show'), user %></td>
<td><%= link_to t('views.common.destroy'), user, method: :delete, data: { confirm: t('views.common.delete_confirm') } %></td>
</tr>
<% end %>
</tbody>
variantメソッドでは画像をサーバサイドでレンダリングする際に加工をしています。
その中で加工された画像のURLが返却値をしてimage_tagに渡されるのですが、画像が存在しない場合にはNull値になります。
そのためこのエラーが発生することになります。
証拠として、image_tagにあえてnilを渡すと同様のエラーが得られます。
<td><%= image_tag nil %></td>
エラーの回避
通常、DBにデータが保存されていなければNullとして返され、それがString型やInt型であればテンプレート上は特に何も指定しなくても良いです。
しかし、今回のようにメソッドに値を渡すなどの処理が走る場合はNull値に気をつけなければいけません。
対処法としては単純にuser.avatarに画像がアタッチされているかどうかを確認すれば良いのです。
<% image_tag <値> if user.avatar.attached?%>
上記を付け加えると先ほどまでのエラーを起こしていたコードが下記のようになります。
<% @users.each do |user| %>
<tr>
<td><%= user.name %></td>
<td><%= user.email %></td>
<td><%= user.code %></td>
<td><%= user.address %></td>
<td><%= image_tag user.avatar.variant( resize: "64x64^", gravity: "center", crop: "100x100+0+0" ) if user.avatar.attached? %></td>
<td><%= link_to t('views.common.show'), user %></td>
<td><%= link_to t('views.common.destroy'), user, method: :delete, data: { confirm: t('views.common.delete_confirm') } %></td>
</tr>
<% end %>
</tbody>
まとめ
StringやIntではNull値はあまり気にしなくても良いですが、image_tagなどのヘルパーメソッドなどを使うときは少し気をつけてあげないといけません。
不明点などはこちらに。