最近、CarrierWaveでものすごくハマったので、仕様確認がてら触ってみることにした。ハマった内容は、CarrierWave側のバグかもしれない。それはともかく、基本的なところから試していく。
準備
RailsにCarrierWaveをインストールする
Gemfileにgemを追記する。
# Gemfile
gem 'carrierwave', '~> 2.0'
ターミナルからインストールする
$ bundle install
Uploaderを作成する
アップローダーを作成する。このアップローダーはいろんな設定ができるもの。用途に応じて利用するのが一般的。運用中のアプリケーションの場合は、すでにあるだろう。
$ rails g uploader Image
Running via Spring preloader in process 10393
create app/uploaders/image_uploader.rb
画像を管理するカラムの追加
今回は1からアプリを作っているので、scaffoldでUserモデルを作る。profile_imageで画像を管理する。
$ rails g scaffold user name:string profile_image:string
すでにモデルがある場合は、通常のmigrationでstringでカラムを作る
$ rails g migration add_profile_image_to_users profile_image:string
追加した場合は、users_controllerにストロングパラメータを設定するの忘れないように。
マイグレートする
$ rails db:migrate
Uploaderをマウントする
# /app/models/user.rb
class User < ApplicationRecord
mount_uploader :profile_image, ImageUploader
end
View編
入力フォームの編集
# /app/views/users/_form.html.erb
<div class="field">
<%= form.label :profile_image %>
<%= form.file_field :profile_image %>
</div>
画像が表示されない
画像を表示する
下記のように変更する
# /app/views/users/index.html.erb
# scaffoldの場合は、<td><%= user.profile_image %></td>となっているところを下記に変更する
<td><%= image_tag(user.profile_image_url, width: 100) if user.profile_image? %></td>
Edit画面で表示させたい
index.html.erbと同様の記述をする
# /app/views/users/_form.html.erb
<div class="field">
<%= image_tag(@user.profile_image_url, width: 100) if @user.profile_image? %>
<%= form.label :profile_image %>
<%= form.file_field :profile_image %>
</div>
表示ができた。このままだと、ファイルを選択したあとバリデーションで再度editページを表示したとき、ファイル選択が解除されてしまう。
UserモデルでNameを入力必須のバリデーションで設定し、下記のように実行する。なお、保存されている背景がグレーっぽい写真はmy-image.jpg。新たに利用している背景が緑色の写真はdemo-image.jpg。
バリデーションなどの時に再度画像を選択しなくても保存できるようにする。
hiddenに :[カラム名]_cache を渡す
# /app/views/users/_form.html.erb
<div class="field">
<%= image_tag(@user.profile_image_url, width: 100) if @user.profile_image? %>
<%= form.label :profile_image %>
<%= form.file_field :profile_image %>
<%= form.hidden_field :profile_image_cache %>
</div>
ストロングパラメータで :[カラム名]_cache を許可する
# /app/controllers/users_controller.rb
def user_params
params.require(:user).permit(:name, :profile_image, :profile_image_cache)
end
実行結果は下記のようになる。
demo-image.jpgがアップロードされている
コントローラー等で、profile_image.cache! のように知人に言われたし、見かけるが、このぐらい単純な処理だと不要。hiddenにprofile_image_cacheを渡すのと、ストロングパラメータで設定してあげれば大丈夫。
画像を削除したい
・viewに削除用のチェックボックス(パラメータ名 :[カラム名]_image)を用意
・ストロングパラメーターに:[カラム名]_imageを追加する
# /app/views/users/_form.html.erb
<div class="field">
<%= image_tag(@user.profile_image_url, width: 100) if @user.profile_image? %>
<%= form.label :profile_image %>
<%= form.file_field :profile_image %>
<%= form.hidden_field :profile_image_cache %>
<label>
<%= form.check_box :remove_profile_image %>
画像を削除する
</label>
</div>
# /app/controllers/users_controller.rb
def user_params
params.require(:user).permit(:name, :profile_image, :profile_image_cache, :remove_profile_image)
end
実際に動作を試してみる。
ちなみに、デフォルトだとファイルを選択した状態で削除にチェックを入れて、Updateをしても削除が優先されるため、同様の結果になる。
設定編
ファイルの形式を設定したい
画像を想定しているところでは画像だけ、文書だけを想定しているところでは文書だけをアップロードさせるようにしたいという要望がある。
今回は画像のケース。
ジェネレーターでアップローダーを作成した場合は、下記の内容のコメントアウトがあるので外すだけ。例えば、CsvUploaderのようなものを作った場合は、 %w(csv tsv) などを指定してあげる。
# /app/uploaders/image_uploader.rb
def extension_whitelist
%w(jpg jpeg gif png)
end
実行結果
エラーメッセージが英語。。。
バリデーションなどのCarrierWaveが出力するエラーの日本語化
localeファイルを追加もしくはlocaleファイルに追記する。
localeファイルを新しく追加した場合は、サーバーを再起動しないと反映されないので注意。
# /config/locales/ja.yml
ja:
errors:
messages:
carrierwave_processing_error: 処理できませんでした
carrierwave_integrity_error: は許可されていないファイルタイプです
carrierwave_download_error: はダウンロードできません
extension_whitelist_error: "%{extension}ファイルのアップロードは許可されていません。アップロードできるファイルタイプ: %{allowed_types}"
extension_blacklist_error: "%{extension}ファイルのアップロードは許可されていません。アップロードできないファイルタイプ: %{prohibited_types}"
content_type_whitelist_error: "%{content_type}ファイルのアップロードは許可されていません。アップロードできるファイルタイプ: %{allowed_types}"
content_type_blacklist_error: "%{content_type}ファイルのアップロードは許可されていません"
rmagick_processing_error: "rmagickがファイルを処理できませんでした。画像を確認してください。エラーメッセージ: %{e}"
mini_magick_processing_error: "MiniMagickがファイルを処理できませんでした。画像を確認してください。エラーメッセージ: %{e}"
min_size_error: "を%{min_size}以上のサイズにしてください"
max_size_error: "を%{max_size}以下のサイズにしてください"
参考: https://github.com/carrierwaveuploader/carrierwave-i18n/blob/master/rails/locales/ja.yml
コントローラーで使用言語を指定する。言語の指定方法はいくつかある。application.rbやapplication_controller.rbで絶対にlocaleをセットするようにするなど。以前多言語化した経験があるので、また記事で書きたいと思う。今回は、CarrierWaveの仕様確認が目的なので、さくっとできる方法で記述。
# /app/controllers/users_controller.rb
class UsersController < ApplicationController
# 省略
before_action :set_locale
# 省略
private
def set_locale
I18n.locale = :ja
end
end
実行してみる
英語は、動作からも分かるように設定しなくてもデフォルトでCarrierWaveのライブラリから読み取るが、変更したい場合は、en.ymlに同じように書けば良い。コピペして、変更すれば使える。
# /config/locales/en.yml
en:
errors:
messages:
carrierwave_processing_error: failed to be processed
carrierwave_integrity_error: is not of an allowed file type
carrierwave_download_error: could not be downloaded
extension_whitelist_error: "You are not allowed to upload %{extension} files, allowed types: %{allowed_types}"
extension_blacklist_error: "You are not allowed to upload %{extension} files, prohibited types: %{prohibited_types}"
content_type_whitelist_error: "You are not allowed to upload %{content_type} files, allowed types: %{allowed_types}"
content_type_blacklist_error: "You are not allowed to upload %{content_type} files"
rmagick_processing_error: "Failed to manipulate with rmagick, maybe it is not an image?"
mini_magick_processing_error: "Failed to manipulate with MiniMagick, maybe it is not an image? Original Error: %{e}"
min_size_error: "File size should be greater than %{min_size}"
max_size_error: "File size should be less than %{max_size}"
ファイル容量を制限したい
サービスを運営する側としては、ストレージのコストの問題は尽きない。ファイルのアップロードというのは、本当にストレージの容量を食ってしまう。そんな時の対象方法。
用途によってファイルの上限と下限を決めるといい。今回のプロフィールなども含め画像は2MBだとぐらい適切かと思う。もっと少なくてもいいが、もっと少なくする場合は、アップロード時にファイルを圧縮する処理を入れないと、UX的に実用に耐えないと思う。
# /app/uploaders/image_uploader.rb
def size_range
0..2.megabytes
end
2.5MBのファイルをアップロードしてみる
大きさをリサイズして保存したい
MiniMagicを使用する。RMagicというライブラリも存在していて利用できるが、CarrierWave公式リポジトリにもMiniMagicを推奨すると書いている。
サーバー(アプリじゃなくてサーバー本体)にImageMagick(MiniMagickのコアロジックが入っている)をインストールする。
少なくとも今回のバージョンではmini_magickのgemのインストールは不要。Gemfile.lockを見ればわかるが、mini_magickがcarrierwaveと一緒にインストールされている。
AWS Linuxの場合
$ sudo yum install ImageMagick
Mac(OSX)でhomebrewを使っている場合
$ brew install imagemagick
アップローダーにリサイズの記述をする。下記のように記述することでアップロードする画像のサイズを常に200×200に設定できる。
2020/05/14追記
uploaderにMiniMagickをincludeする記述する旨を追記。これがないと、resize_to_fitというメソッドがありませんというエラーが出る。
# /app/uploaders/image_uploader.rb
include CarrierWave::MiniMagick
process resize_to_fit: [200, 200]
アップロードの前後の比較。Macのプレビュー画面で確認している。アップロード後は引き伸ばして大きく表示しているので、画像が荒いこともわかる。
ImageMagicがサーバーにインストールされていない状態だと、DBに更新をかけるタイミングで下記のようなエラーが表示される
リサイズの機能を使えば、ファイル容量を制限する時に更なる容量の制限もできると思う。
リサイズはバージョン保存と組み合わせると、サイズ違いの同じ画像が保存できる。先ほどの例に加えてversion指定でリサイズを記述する。
# /app/uploaders/image_uploader.rb
process resize_to_fit: [200, 200]
version :thumb do
process resize_to_fit: [50, 50]
end
同じようにアップロードをするだけで2枚の画像が保存される。デフォルトの状態では、[version名]_[元のファイル名]で保存される。
利用用途としては、例えば、プロフィール画像を、プロフィール情報の閲覧とメッセージ画面で利用する場合、プロフィール情報を表示する場合は、表示する枚数は1枚でサイズも大きめなので、通常の画像を表示する。一方、メッセージ画面は表示される回数も多く、画面も小さいので、読み込み速度を考慮すると、画像の大きさは必要最低限でいい。こんな場合に利用できる。
今回のケースでは、resize_to_fitというメソッドだけの記述だが、用途に応じたリサイズをする場面が実際には多いと思う。縦横比率を維持してリサイズしたいなど。。。
それは別途記事を書きたいと思う。
まとめ
基本的なところは上記のようなところだと思う。バリデーションの時にファイルを保持したまま更新とか必要最低限の記述を改めて確認できたのは非常に良かった。
エラーメッセージの多言語化も別gemでテンプレートがあったりして非常に良かった。
だが、業務レベルだと、S3にアップロードしたいとか複数枚のアップロードに対応したいとかある。その辺も試して別記事で残そうと思う。
コメントを残す