13.4 マイクロポストの画像投稿
ここではあとは応用として画像付きマイクロポストを投稿できるようにする。
手順としては、開発環境用のβ版を実装し、いくつかの改善をとおして本番環境用の完成版を実装する、
画像アップロード機能を追加するためには、2つの視覚的な要素が必要。
1つは、画像をアップロードするためのフォーム、
もう1つは、投稿された画像そのもの。
[Upload image]ボタンと画像付きマイクロポストのモックアップは↓
13.4.1 基本的な画像アップロード
投稿した画像を扱ったり、Micropostモデルと関連付けするために、今回はCarrierWaveという画像アップローダーを使う。
まずはcarrierwave gemをGemfile
に追加。
このとき、mini_magick gemとfog gemsも含める点に注目!
これらのgemは画像をリサイズしたり、本番環境で画像をアップロードするために使う。
source 'https://rubygems.org' : gem 'rails', '~>5.2.6' gem 'bcrypt', '3.1.11' gem 'faker', '~>1.7' gem 'carrierwave', '1.2.2' #追加 gem 'will_paginate', '3.1.7' gem 'bootstrap-will_paginate', '1.0.0' group :production do gem 'pg', '0.20.0' gem 'fog','1.42' #追加 end :
$ bundle install
Carrier Waveを導入すると、Railsのジェネレーターで画像アップローダーが生成できるようになる。
次のコマンドを実行して適用する(画像のことをimageとすると一般的過ぎるので、pictureと呼ぶ)。
rails g uploader Picture
Carrier Waveでアップロードされた画像は、Active Recordモデルの属性と関連付けされているべき。
関連付けされる属性には画像のファイル名が格納されるため、pictureのデータ型はstring型にしておく。
microposts | |
---|---|
id | integer |
content | text |
user_id | integer |
created_at | datetime |
update_at | datetime |
picture | string |
picture
属性をMicropostモデルに追加するために、マイグレーションファイルを生成し、開発環境のDBに適用する。
$ rails g migration add_picture_to_microposts picture:string $ rails db:migrate
CarrierWaveに画像と関連付けたモデルを伝えるためには、mount_uploader
というメソッドを使う。
このメソッドは、引数に属性名のシンボルと生成されたアップローダーのクラス名を取る。
mount_uploader :picture, PictureUploader
(picture_uploader.rb
というファイルでPictureUploader
クラスが定義されている。
13.4.2で修正するが、今はデフォルトのままでok。)Micropostモデルにアップローダーを追加した結果↓
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 } end
テストは成功
Homeページにアップローダーを追加するためには、マイクロポストのフォームにfile_field
タグを含める必要がある。
<%= 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 %> #追加 </span> <% end %>
最後に、Webから更新できる許可リストにpicture
属性を追加する。追加すると、micropost_params
メソッドは次のようになる。
class MicropostsController < ApplicationController before_action :logged_in_user, only: [:create, :destroy] before_action :correct_user, only: :destroy : private def micropost_params params.require(:micropost).permit(:content, :picture) end def correct_user @micropost = current_user.microposts.find_by(id: params[:id]) redirect_to root_url if @micropost.nil? end
image_tag
ヘルパーでその画像を描画できるようになる。また、画像の無い(テキストのみの)マイクロポストでは画像を表示させないようにするため、picture?
という論理値を返すメソッドを使っている。マイクロポストの画像表示を追加したコードは次↓
<li id="micropost-<%= micropost.id %>"> <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %> <span class="user"><%= link_to micropost.user.name, micropost.user %></span> <span class="content"> <%= micropost.content %> <%= image_tag micropost.picture.url if micropost.picture? %> #追加 </span> <span class="timestamp"> Posted <%= time_ago_in_words(micropost.created_at) %> ago. <% if current_user?(micropost.user) %> <%= link_to "delete", micropost, method: :delete, data: { confirm: "You sure?" } %> <% end %> </span> </li>
13.4.2 画像の検証
13.4.1のアップローダーにはいくつかの欠点がある。
例えば、アップロードされた画像に対する制限がないため、もしユーザーが巨大なファイルを上げたり、無効なファイルを上げると問題が発生してしまう。
この欠点を直すために、画像サイズやフォーマットに対するバリデーションを実装し、サーバー用とクライアント(ブラウザ)用の両方に追加する。
最初のバリデーションでは、有効な画像の種類を制限していくが、これはCarrierWaveのアップローダーの中に既にヒントがある。
生成されたアップローダーの中にコメントアウトされたコードがあるが、ここのコメントアウトを取り消すことで、画像のファイル名から有効な拡張子(PNG/GIF/JPEG)を検証することができる。
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
これはMicropostモデルに書き足していく。先ほどのバリデーションと異なり、ファイルサイズに対するバリデーションはRailsの既存のオプション(presence
やlength
など)には無い。
したがって、手動でpicture_size
というバリデーションを定義する。
独自のバリデーションを定義するために、今まで使っていたvalidates
ではなく、validate
(単数形)メソッドを使っている点に注目!
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 #追加 end private # アップロードされた画像のサイズをバリデーションする←追加 def picture_size if picture.size > 5.megabytes errors.add(:picture, "should be less than 5MB") end end end
validate
メソッドでは、引数にシンボル(:picture_size
)を取り、そのシンボル名に対応したメソッドを呼び出す。また、呼び出されたpicture_size
メソッドでは、5MB上限とし、それを超えた場合はカスタマイズしたエラーメッセージをerrors
コレクションに追加している。先で定義した画像のバリデーションビューに組み込むために、クライアント側に2つの処理を追加。
まずはフォーマットのバリデーションを反映するためには、file_field
タグにaccept
パラメータを付与して使う。
<%= f.file_field :picture, accept: 'image/jpeg,image/git,image/png' %>
このときacceptパラメータでは、許可したファイル形式を、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 chooose a smailler file.'); } });
上のコードではCSS idのmicropost_picture
を含んだ要素を見つけ出し、この要素を監視している。
そして、このidを持った要素とは、マイクロポストのフォームを指す(なお、ブラウザ上で画面を右クリックし、インスペクターで要素を調べることで確認できる)。つまり、このCSS idを持つ要素が変化したとき、このjQueryの関数が動き出す。
そして、もしファイルサイズが大きすぎた場合、alert
メソッドで警告を出すといった仕組み。
これらの追加的なチェック機能をまとめる↓
<%= 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/git.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リクエストを送信する場合には対応しきれない。
13.4.3 画像のリサイズ
画像を表示させる前にサイズを変更する(リサイズする)ようにしてみる。
画像をリサイズするために、UnageMagickというプログラムを使うので、これを開発環境にインストールする。
(あとでも説明するが、本番環境がHerokuであれば、既に本番環境でImageMagickが使えるようになっている)。
クラウドIDEでは、次のコマンドであればインストールできる。
$ sudo yum install -y ImageMagick
次に、MiniMagickというImageMagickとRubyをつなぐgemを使って、画像をリサイズしてみる。
MiniMagickのドキュメント(英語)を見ると、様々な方法でリサイズできることが分かる。
今回はresize_to_limit: [400, 400]
という方法を使う。
これは縦横どちらかが400pxを超えていた場合、適切なサイズに縮小するオプション(ただし小さい画像であっても拡大はしない)。
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