CarrierWaveとはRuby on Railsで動く画像のアップロードをサポートするgemです。
よくSNS等で使われる、ユーザー画像(アバター)のアップロードに使うことが出来ます。また、ActiveRecordをサポートしているため、ユーザー情報と紐づく形でDBに画像情報を格納することが出来ます。(画像自体はサーバー上の指定した場所に保存しており、BLOB型で保存するにはcarrierwave-blobが必要です。)
今回の要件としては、
- ユーザー情報変更画面でユーザー画像のアップロードができる。
- 画像のファイル名は[ユーザーID].jpgとする。
- 画像専用のテーブルは用意しない。
- 画像をアップロードする前にプレビュー画像の表示をする。
- 画像をアップロードする前に人物の顔写真であるかどうかを判定する。
以上の5つが挙げられます。
Viewの用意
carrierwaveは人気のgemですので、使い方の記事を日本語でもいくつか見つけることが出来ます。
Rails 超お手軽な画像アップローダー CarrierWave の使い方
Rails 4.1 で CarrierWave を使う
導入は上記の記事を参考にしてください。今回viewの部分は下のようにmodalにしてみました。
<div id="profile_image_modal" class="modal">
<div class="modal-content">
<div class="row">
<div class="col s12">
プロフィール画像を変更することが出来ます。
<%= form_for(User.new, url: {controller: 'image_upload', action: 'user_profile'}, html: {method: 'post', id: 'profile'}) do |f| %>
<div class="file-field input-field col m8 s12">
<div class="btn grey">
<i class="material-icons">add_a_photo</i>
<%= f.file_field :image %>
</div>
<div class="file-path-wrapper">
<input class="file-path validate" type="text">
</div>
</div>
<div class="file-field input-field col m4 s12 margin-bottom-1">
<input class="btn col s12" type="button" id="profile_button" value="送信">
</div>
<div class="col s12 center-align">
<div class="profile_preview"></div>
</div>
<% end %>
<div class="col s12 help-block">
(jpg, jpeg, gif, pngのみ、2MB以下)
</div>
</div>
</div>
</div>
</div>
CSSフレームワークとしてはmaterializeを使用しています。送信ボタンにsubmitを割り当ててないのはアップロード前にJavascriptで処理させたいからです。
Controller,modelの用意
画像専用のテーブルは用意したくないという需要もあるはずなのに、ググり力が3しか無いせいか日本語ではそれらしき情報を見つけることができませんでした。
マニュアルを見ながら書いたものが以下です。
def user_profile
res = image_upload({user_id: user_id, image_type: 'profile'})
if res[:result]
redirect_to teacher_url(id: user_id) and return
else
redirect_to teacher_url(id: user_id), alert: res[:message] and return
end
end
# 画像アップロード処理系(エラー処理含む)
def image_upload(image_uploader_hash)
begin
i = ImageUploader.new(image_uploader_hash)
i.store!(params[:user][:image])
rescue Exception => e
if e.message.present?
return {result: false, message: e.message}
end
end
return {result: true}
end
image_upload関数ではImageUploadオブジェクトに引数を渡してインスタンスを生成しています。viewで用意したフォーム <%= f.file_field :image %>
に入る値をparams[:user][:image]で受け取っています。
肝になる部分はi.store!(params[:user][:image])で、これだけで設定したディレクトリに画像ファイルを保存することが出来ます。
ちなみにエラー処理についてですが、config/locales/ja.ymlを適切に設定しないと日本語のエラーメッセージが表示されてくれません。それについては以下のサイトが参考になります。
Carrierwaveでlocaleファイルを追加してもTranslation missingになる
モデルの一部は以下のようになっています。
class ImageUploader < CarrierWave::Uploader::Base
# 画像が保存されるディレクトリ public/:store_dirに保存される
def store_dir
'user_profile_image'
end
# ファイル名
def filename
%Q{#{model[:user_id]}.jpg}
end
end
ImageUpload.newで{user_id: user_id, image_type: 'profile'}を渡していたのですが、ImageUploadのインスタンスメソッド内でmodel[:user_id]などとして値を取得することが出来ます。(model.user_idのように書いてある情報もありましたがそれだと何故か動きませんでした。)
jQuery.facedetectionの利用
プロフィール画像を登録する機能はもともと、ユーザーがメールで画像ファイルを添付してもらったものを、事務スタッフの目を通してペットやイラストの画像ではなく人の画像だと判断できればユーザーの情報として掲載する流れになっていました。これでは事務の手作業が入り、画像が反映されるまで時間がかかりますし、ユーザー側もメールで添付するときにいちいちお世話になっておりますなどの余計な文言が必要で面倒です。
画像を顔写真だと自動で高精度に判断できるようにしてほしいです。
そこで登場するのがjQueryで書かれたプラグインjQuery Face Detection Pluginです!
本来顔画像がどこにあるかを判定してくれるものですが、
顔と認識されなければ結果オブジェクトが返されないので、結果として顔画像かどうか判定できると考えました。
$(function(){
//画像ファイルプレビュー表示
$('form#profile').on('change', 'input[type="file"]', function(e) {
var file = e.target.files[0],
reader = new FileReader(),
$preview = $(".profile_preview");
t = this;
// 画像ファイル以外の場合は何もしない
if(file.type.indexOf("image") < 0){
alert('ファイルの種類が異なります');
}
if(file.size > 2*1000*1000){
alert('ファイルのサイズが2MB以上です');
}
reader.onload = (function(file) {
return function(e) {
$preview.empty();
$preview.append($('<img>').attr({
src: e.target.result,
width: "150px",
class: "profile_preview",
title: file.name
}));
};
})(file);
reader.readAsDataURL(file);
});
});
$('#profile_button').on('click', function(){
$('img.profile_preview').faceDetection({
complete: function (faces) {
var f = faces.length;
if(f >= 1){
$('form#profile').submit();
}else{
alert('人物の画像を投稿してください');
}
}
});
})
サーバーにアップロードされる前にサイズとファイルの種類を判定して、さらにプレビュー画像も表示しています。faceDetectionの部分は非常にシンプルに書くことが出来ました!
しかし、このプラグインでどの程度顔画像とそれ以外を弁別できるようになるのでしょうか?とりあえず幾つかの画像で試したところ特に問題はなさそうでしたが、もうちょっとパターンを増やしてみないとなんとも言えないところかもしれません。時間があればやってみたいと思っています。