Rails

【Rails】CarrierwaveのCache機能を使用し、バリデーション後の画像データを保持する方法

Rails

 

今回はバリデーション失敗後でも画像情報を保持することができる機能、「Cache(キャッシュ)」について調べてたので、簡単にまとめたいと思います。画像のアップロード機能にCarrierwaveを使用している場合は、バリデーション後の実装は楽できるはず?です。

ファイルアップローダーに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」の記述を追記する
検索上位に出てきた記事だと上手く実装できませんでした。一次ソースであるGithubを参考にするのが無難です。

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]

  # 省略
Rails
【Rails】CarrierwaveとMiniMagickを使って画像を投稿する方法CarrierwaveとMiniMagickを使って画像を投稿できる機能を実装する方法を紹介しています。インストールの仕方から使い方まで解説しているので、ぜひこの機会に画像のアップロードをマスターしましょう。...

モデルの作成

続いてモデルの作成に移ります。今回モデル名は「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」にアクセスしてみましょう。

以下のような挙動になっていれば、今のところは成功です。

Image from Gyazo

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の中に画像データが保存され、バリデーションに引っ掛かった後でも、データを引き継ぐことができます。

hidden_fieldの中身

 

これにより、画像データをもう一度入力せずとも、nameのみ入力するだけで投稿ができてしまいます。

Image from Gyazo
上記のGIFの場合、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>

 

Image from Gyazo

参考文献

Github:https://github.com/carrierwaveuploader/carrierwave#making-uploads-work-across-form-redisplays

teratail:https://teratail.com/questions/213320