13.4.2 画像の検証
アップローダーも悪くはありませんが、いくつかの目立つ欠点があります。例えば、アップロードされた画像に対する制限がないため、
もしユーザーが巨大なファイルを上げたり、無効なファイルを上げると問題が発生してしまいます。
この欠点を直すために、画像サイズやフォーマットに対するバリデーションを実装し、サーバー用とクライアント (ブラウザ)用の両方に追加しましょう。
画像のファイル名から有効な拡張子 (PNG/GIF/JPEGなど) を検証する
画像フォーマットのバリデーション
app/uploaders/picture_uploader.rb
class PictureUploader < CarrierWave::Uploader::Base storage :file # アップロードファイルの保存先ディレクトリは上書き可能 # 下記はデフォルトの保存先 def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end # アップロード可能な拡張子のリスト def extension_whitelist %w(jpg jpeg gif png) end end
2つ目のバリデーションでは、画像のサイズを制御します。
これはMicropostモデルに書き足していきます。
今回は手動でpicture_sizeという独自のバリデーションを定義します。
今まで使っていたvalidatesメソッドではなく、
validateメソッドを使っている点に注目してください。
app/models/micropost.rb
class Micropost < ApplicationRecord
belongs_to :user
default_scope -> { order(created_at: :desc) }
mount_uploader :picture, PictureUploader
validates :user_id, presence: true
validates :content, presence: true, length: { maximum: 140 }
validate :picture_size
private
# アップロードされた画像のサイズをバリデーションする
def picture_size
if picture.size > 5.megabytes
errors.add(:picture, "should be less than 5MB")
end
end
validateメソッドでは、引数にシンボル (:picture_size) を取り、
そのシンボル名に対応したメソッドを呼び出します。
また、呼び出されたpicture_sizeメソッドでは、5MBを上限とし、
それを超えた場合はカスタマイズした
エラーメッセージをerrorsコレクションに追加しています。
定義した画像のバリデーションをビューに組み込むために、
クライアント側に2つの処理を追加しましょう。
まずはフォーマットのバリデーションを反映するためには、
file_fieldタグにacceptパラメータを付与して使います。
<%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
このときacceptパラメータでは、リスト 13.64で許可したファイル形式を、MIMEタイプで指定するようにします。
次に、大きすぎるファイルサイズに対して警告を出すために、
ちょっとしたJavaScript (正確にはjQuery) を書き加えます。
こうすることで、長すぎるアップロード時間を防いだり、
サーバーへの負荷を抑えたりすることに繋がります。
$('#micropost_picture').bind('change', function() { var size_in_megabytes = this.files[0].size/1024/1024; if (size_in_megabytes > 5) { alert('Maximum file size is 5MB. Please choose a smaller file.'); } });
上のコードでは(ハッシュマーク#から分かるように)
CSS idのmicropost_pictureを含んだ要素を見つけ出し、
この要素を監視しています。
そしてこのidを持った要素とは、マイクロポストのフォームを指します(なお、ブラウザ上で画面を右クリックし、インスペクターで要素を調べることで確認できます)。
つまり、このCSS idを持つ要素が変化したとき、このjQueryの関数が動き出します。そして、もしファイルサイズが大きすぎた場合、
alertメソッドで警告を出すといった仕組みです。
これらの追加的なチェック機能をまとめると、↓のようになります。
ファイルサイズをjQueryでチェックする
app/views/shared/_micropost_form.html.erb
<%= form_for(@micropost) do |f| %> <%= render 'shared/error_messages', object: f.object %> <div class="field"> <%= f.text_area :content, placeholder: "Compose new micropost..." %> </div> <%= f.submit "Post", class: "btn btn-primary" %> <span class="picture"> <%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %> </span> <% end %> <script type="text/javascript"> $('#micropost_picture').bind('change', function() { var size_in_megabytes = this.files[0].size/1024/1024; if (size_in_megabytes > 5) { alert('Maximum file size is 5MB. Please choose a smaller file.'); } }); </script>
実装はまだ不完全です。
仮に送信フォームを使った投稿をうまく制限できても、ブラウザのインスペクタ機能でJavaScriptをいじったり、curl
などを使って直接POST
リクエストを送信する場合には対応しきれません
演習
1:5MB以上の画像ファイルを送信しようとした場合、どうなりますか?
また無効な拡張子のファイルを送信しようとした場合、どうなりますか?
エラーメッセージが以下のようになる。
画像は5MB未満である必要があります
画像「xmind」ファイルのアップロードは許可されていません。
許可されているタイプ:jpg、jpeg、gif、png
13.4.3 画像のリサイズ
画像を表示させる前にサイズを変更する
画像をリサイズするためには、画像を操作するプログラムが必要になります。今回はImageMagickという
プログラムを使うので、これを開発環境にインストールします。
本番環境がHerokuであれば、既に本番環境でImageMagickが使えるようになっています。クラウドIDEでは、
次のコマンドでこのプログラムをインストールできます。
sudo yum install -y ImageMagick
もしローカル環境で開発している場合、それぞれの環境に応じてImagiMagickをインストールする手順が異なります。
例えばMacの場合であれば、Homebrewを導入し、
brew install imagemagick
コマンドを使ってインストールします。
次に、MiniMagickというImageMagickとRubyを繋ぐgemを使って、
画像をリサイズしてみましょう。
MiniMagickのドキュメントを見ると
様々な方法でリサイズできることがわかりますが、
今回はresize_to_limit: [400, 400]という方法を使います。これは、
縦横どちらかが400pxを超えていた場合、適切なサイズに縮小するオプションです(ただし小さい画像であっても拡大はしません)。
画像をリサイズするために画像アップローダーを修正する
app/uploaders/picture_uploader.rb
class PictureUploader < CarrierWave::Uploader::Base include CarrierWave::MiniMagick process resize_to_limit: [400, 400] storage :file # アップロードファイルの保存先ディレクトリは上書き可能 # 下記はデフォルトの保存先 def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end # アップロード可能な拡張子のリスト def extension_whitelist %w(jpg jpeg gif png) end end
演習
1:解像度の高い画像をアップロードし、リサイズされているかどうか確認してみましょう。画像が長方形だった場合、リサイズはうまく行われているでしょうか?
うまく出来る。バリデーション実装前のはもちろんリサイズされない
2:テストを追加していた場合、この時点でテストスイートを走らせると
紛らわしいエラーメッセージが表示されることがあります。
このエラーを取り除いてみましょう。
設定ファイルを修正し、テスト時はCarrierWaveに画像のリサイズを
させないようにしてみましょう。
config/initializers/skip_image_resizing.rb
if Rails.env.test?
CarrierWave.configure do |config|
config.enable_processing = false
end
end
13.4.4 本番環境での画像アップロード
storage :fileという行によって、
ローカルのファイルシステムに画像を保存するようになっているからです。
本番環境では、ファイルシステムではなくクラウドストレージサービスに画像を保存するようにしてみましょう。
本番環境でクラウドストレージに保存するためにはfog gemを使うと簡単です。
本番環境での画像アップロードを調整する
app/uploaders/picture_uploader.rb
class PictureUploader < CarrierWave::Uploader::Base include CarrierWave::MiniMagick process resize_to_limit: [400, 400] if Rails.env.production? storage :fog else storage :file end # アップロードファイルの保存先ディレクトリは上書き可能 # 下記はデフォルトの保存先 def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end # アップロード可能な拡張子のリスト def extension_whitelist %w(jpg jpeg gif png) end end
世の中には多くのクラウドストレージサービスがありますが、今回は有名で信頼性も高いアマゾンの「Simple Storage Service (S3) 」を使います。
Amazon Web Servicesアカウントにサインアップする
AWS Identity and Access Management (IAM)でユーザーを
作成し、AccessキーとSecretキーをメモする
AWS ConsoleからS3 bucketを作成し(bucketの名前はなんでも大丈夫です)作成したユーザーに対してRead権限とWrite権限を付与する
fogでリージョンを指定する場合は
:region => ENV[‘S3_REGION’] といったパラメータを渡し、
heroku config:set S3_REGION=”リージョン名”
といったコマンドを実行することで設定できます。
なお、東京のリージョン名は “ap-northeast-1” です。
config/initializers/carrier_wave.rb
if Rails.env.production?
CarrierWave.configure do |config|
config.fog_credentials = {
# Amazon S3用の設定
:provider => 'AWS',
:region => ENV['S3_REGION'], # 例: 'ap-northeast-1'
:aws_access_key_id => ENV['S3_ACCESS_KEY'],
:aws_secret_access_key => ENV['S3_SECRET_KEY']
}
config.fog_directory = ENV['S3_BUCKET']
end
end
今回は手動で設定する必要があります。heroku config:setコマンドを使って、次のようにHeroku上の環境変数を設定してください。
heroku config:set S3_ACCESS_KEY="ココに先ほどメモしたAccessキーを入力" heroku config:set S3_SECRET_KEY="同様に、Secretキーを入力" heroku config:set S3_BUCKET="Bucketの名前を入力" heroku config:set S3_REGION="Regionの名前を入力"
.gitignoreファイルにアップロード用ディレクトリを追加する
# アップロードされたテスト画像を無視する /public/uploads
それでは、これまでの変更をトピックブランチにコミットし、masterブランチにマージしていきましょう。
rails test git add -A git commit -m "Add user microposts" git checkout master git merge user-microposts git push
次に、Herokuへのデプロイ、データベースのリセット、サンプルデータの生成を順に実行していきます。
git push heroku heroku pg:reset DATABASE heroku run rails db:migrate heroku run rails db:seed
最終状態のGemfile
source 'https://rubygems.org' gem 'rails', '5.1.6' gem 'bcrypt', '3.1.12' gem 'faker', '1.7.3' gem 'carrierwave', '1.2.2' gem 'mini_magick', '4.7.0' gem 'will_paginate', '3.1.6' gem 'bootstrap-will_paginate', '1.0.0' gem 'bootstrap-sass', '3.3.7' gem 'puma', '3.9.1' gem 'sass-rails', '5.0.6' gem 'uglifier', '3.2.0' gem 'coffee-rails', '4.2.2' gem 'jquery-rails', '4.3.1' gem 'turbolinks', '5.0.1' gem 'jbuilder', '2.7.0' group :development, :test do gem 'sqlite3', '1.3.13' gem 'byebug', '9.0.6', platform: :mri end group :development do gem 'web-console', '3.5.1' gem 'listen', '3.1.5' gem 'spring', '2.0.2' gem 'spring-watcher-listen', '2.0.1' end group :test do gem 'rails-controller-testing', '1.0.2' gem 'minitest', '5.10.3' gem 'minitest-reporters', '1.1.14' gem 'guard', '2.14.1' gem 'guard-minitest', '2.4.6' end group :production do gem 'pg', '0.20.0' gem 'fog', '1.42' end # Windows環境ではtzinfo-dataというgemを含める必要があります gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]