今回はバリデーション失敗後でも画像情報を保持することができる機能、「Cache(キャッシュ)」について調べてたので、簡単にまとめたいと思います。画像のアップロード機能にCarrierwaveを使用している場合は、バリデーション後の実装は楽できるはず?です。
Cache(キャッシュ)とは
Cache(キャッシュ)とは、バリデーションの失敗後でもアップロードしたファイルを記憶・保持してくれる機能のことです。
Often you’ll notice that uploaded files disappear when a validation fails. CarrierWave has a feature that makes it easy to remember the uploaded file even in that case.
参考:Github
実装まとめ
「carrierwave cache」で検索すると色々な記事が出てきますが、結論の以下の2点を実装するだけでCacheを使うことができます。
- 画像アップロード時に「(カラム名)_cache」という名前のhidden_fieldを追加する
- ストロングパラメーターに「(カラム名)_cache」の記述を追記する
Cacheを実装してみる
では実際に、ミニアプリにCacheを実装してみたいと思います。
事前準備
まずはアプリケーションの立ち上げです。
以下のコマンドで新規アプリケーションを作成しましょう。「bundle install」とデータベースの作成も行っておきます。
% rails new cache-demo -d mysql
% cd cache-demo
% bundle install
% rails db:create
% rails db:migrate
今回開発するアプリケーションの環境は以下のようになります。
- アプリケーション名:cache-demo
- Rails:6.0.3.2
- Ruby:2.6.5
- DB:MySQL
ファイルアップロード機能の作成
次にファイルアップロード機能を実装します。ファイルアップロードに必要な「Carrierwave」に加え、画像加工処理をしてくれる「MiniMagick」もインストールしておきましょう。
% brew install imagemagick
# Gemfileに以下を追記
gem 'carrierwave'
gem 'mini_magick'
% bundle install
ファイルアップローダーを作成するため、以下のコマンドを打ち込みます。
% rails g uploader image
すると「app/uploaders/image_uploader.rb」が作成されるので、画像のリサイズが行えるように以下を記述しましょう。
# app/uploaders/image_uploader.rb
class ImageUploader < CarrierWave::Uploader::Base
include CarrierWave::MiniMagick
process resize_to_fit: [400, 200]
# 省略

モデルの作成
続いてモデルの作成に移ります。今回モデル名は「Product_image」、カラムは2つ用意し、「name」と「image」とします。加えて、これらはScaffoldの機能を用いて作成していきます。
以下のコマンドを入力しましょう。
% rails generate scaffold Product_image name:string image:string
% rails db:migrate
ファイルアップローダーをProduct_imageモデルと関連付けさせます。
# app/models/product_image.rb
class ProductImage < ApplicationRecord
mount_uploader :image, ImageUploader
end
ビューの編集
次にビューの編集に取り掛かりましょう。
画像をアップロードするため、以下のように編集しましょう。
<%# app/views/product_images/_form.html.erb %>
<div class="field">
<%= form.label :image %>
<%= form.text_field :image %>
</div>
↓ 以下のように変更
<div class="field">
<%= form.label :image %>
<%= form.file_field :image %>
</div>
アップロードした画像も表示させられるようにコードを編集します。
<%# app/views/product_images/show.html.erb %>
<p>
<strong>Image:</strong>
<%= @product_image.image %>
</p>
↓ 以下のように編集
<p>
<strong>Image:</strong>
<%= @product_image.image %>
</p>
<p>
<%= image_tag @product_image.image.url %>
</p>
バリデーションの付与
Cacheはバリデーション後の機能のため、前提としてバリデーションが付与されていなければなりません。nameカラムとimageカラムにそれぞれバリデーションを追記しましょう。
# app/models/product_image.rb
class ProductImage < ApplicationRecord
validates :name, presence: true
validates :image, presence: true
mount_uploader :image, ImageUploader
end
途中経過
まだ実装途中ですが、ここまでで上手く画像アップロードができるかどうか確かめてみます。「rails server」でサーバーを立ち上げ、「http://localhost:3000/product_images」にアクセスしてみましょう。
以下のような挙動になっていれば、今のところは成功です。
Cacheの実装
ではここから本題のCacheの実装に移っていきます。
冒頭でも説明しましたが、Cacheの実装には以下の2点を記述するだけで実装できます。
- 画像アップロード時に「(カラム名)_cache」という名前のhidden_fieldを追加する
- ストロングパラメーターに「(カラム名)_cache」の記述を追記する
以下のファイルを編集しましょう。
<%# app/views/product_images/_form.html.erb %>
<div class="field">
<%= form.label :image %>
<%= form.file_field :image %>
</div>
↓ 以下のように編集
<div class="field">
<%= form.label :image %>
<%= form.file_field :image %>
<%= form.hidden_field :image_cache %>
</div>
# app/controllers/product_images_controller.rb
def product_image_params
params.require(:product_image).permit(:name, :image)
end
↓ 以下のように編集
def product_image_params
params.require(:product_image).permit(:name, :image, :image_cache)
end
このように記述することで、hidden_fieldの中に画像データが保存され、バリデーションに引っ掛かった後でも、データを引き継ぐことができます。

これにより、画像データをもう一度入力せずとも、nameのみ入力するだけで投稿ができてしまいます。
サムネイルの表示
しかし今のままではやや不便です。なぜならバリデーションに引っ掛かった後、画像データはすでに入っているのにも関わらず、「選択されていません」と表示されてしまうからです。

この場合の対処方法としては、Githubに以下のようなことが書かれています。
It might be a good idea to show the user that a file has been uploaded, in the case of images, a small thumbnail would be a good indicator:
ファイルがアップロードされたことを示すには、小さなサムネイルを表示するのが良いでしょう。
ということで、小さなサムネイルを表示させます。画像サイズはMiniMagickを使い、すでに400×200pxに加工されています。
「app/views/product_images/_form.html.erb」を編集しましょう。
<%# app/views/product_images/_form.html.erb %>
<div class="field">
<%= form.label :image %>
<%= form.file_field :image %>
<%= form.hidden_field :image_cache %>
</div>
↓ 以下のように編集
<div class="field">
<%= image_tag(@product_image.image.url) if @product_image.image? %>
<%= form.label :image %>
<%= form.file_field :image %>
<%= form.hidden_field :image_cache %>
</div>
参考文献
Github:https://github.com/carrierwaveuploader/carrierwave#making-uploads-work-across-form-redisplays
teratail:https://teratail.com/questions/213320