基本的な画像のアップロード
画像付きのマイクロポストを投稿できるようにする。
画像の投稿フォームと、投稿された画像の表示機能を実装する。
carrierwaveジェム
画像アップロード機能を追加するcarrierwaveジェムをGemfileに追加する。
加えて、画像をリサイズするmini_magickジェムと、本番環境で画像をアップロードできるfogジェムも追加する。
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.5'
gem 'bootstrap-will_paginate', '1.0.0'
.
.
.
group :production do
gem 'pg', '0.20.0'
gem 'fog', '1.42'
end
次に、pictureという名前の画像アップローダーを作成する。
$ rails generate uploader Picture
pictureカラム
Micropostモデルに、画像を格納するためのpictureカラムを追加する。
$ rails generate migration add_picture_to_microposts picture:string
$ rails db:migrate
CarrierWaveに画像と関連付けたモデルを伝えるためには、mount_uploaderというメソッドを使う。
このメソッドは、引数に属性名のシンボルと生成されたアップローダーのクラス名を取る。
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
railsサーバーとGuardを再起動して、テストがGREENになるようにする。
画像投稿フォーム
マイクロポストの投稿フォームにf.file_field :pictureを使って画像の投稿フォームを作成する。
<%= 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 %>
マイクロポスト投稿のStrong Parametersを修正して、picture属性が送信できるようにしておく。
private
def micropost_params
params.require(:micropost).permit(:content, :picture)
end
画像の表示
これで画像が投稿できるようになったので、image_tagとurlメソッドを使ってマイクロポストに画像を表示する。
<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>
picture?メソッドはCarrierWaveによって生成されるメソッドで、Micropostオブジェクトに画像が入っているかを論理値で返す。
画像アップロードのテスト
画像が投稿できるようになったので、テストを書く。
テスト用画像はtest/fixturesディレクトリに入れる。
マイクロポスト投稿用のテストに追記していく。
test "micropost interface" do
log_in_as(@user)
get root_path
assert_select 'div.pagination'
assert_select 'input[type="file"]'
# 無効な送信
post microposts_path, params: { micropost: { content: "" } }
assert_select 'div#error_explanation'
# 有効な送信
content = "This micropost really ties the room together"
picture = fixture_file_upload('test/fixtures/rails.png', 'image/png')
assert_difference 'Micropost.count', 1 do
post microposts_path, params: { micropost:
{ content: content,
picture: picture } }
end
assert @user.microposts.paginate(page: 1).first.picture?
follow_redirect!
assert_match content, response.body
# 投稿を削除する
.
.
.
end
fixture_file_uploadメソッドで、fixture内のファイルをアップロードする。
第一引数に画像へのパス、第二引数に'ファイル形式/拡張子'をとる(多分)。
投稿画像のバリデーション
投稿可能な画像ファイルのサイズや種類を制限するために、バリデーションを設定していく。
画像フォーマットとアップローダーファイル
画像ファイルの種類を制限するために、アップローダーファイルを修正する。
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
拡張子のリスト部分はデフォルトではコメントアウトされている。
画像サイズとvalidateメソッド
アップロードできる画像のサイズは5MB以下としたいので、Micropostモデルにバリデーションを追加する。
ここで、ファイルサイズを制限するバリデーションがRailsのデフォルトに無いため、自分で定義しなければならない。
定義したバリデーションを適用するには、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
private
# アップロードされた画像のサイズをバリデーションする
def picture_size
if picture.size > 5.megabytes
errors.add(:picture, "should be less than 5MB")
end
end
end
5MBを超えた場合はerrors.addを使って、Micropostオブジェクトのpicture属性にエラーメッセージを追加している。
まずフォーマットのバリデーションを反映するために、投稿フォームのf.file_fieldにacceptパラメータを付ける。
<%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
また、jQueryを使って警告メッセージを表示しておく。
<%= 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>
画像のリサイズ
ImageMagickとMiniMagickジェム
画像を自動でリサイズするには、ImageMagickというプログラムを使う。
Homebrewでは次のようにインストールする。
$ brew install imagemagick
ImageMagickを使うためには、MiniMagickジェムが必要である。
CarrierWaveの導入時に導入していない場合はしておく。
画像を縦400px、横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
end
画像のリサイズ機能を使いしたことでテストがREDになるので、次のように修正する。
if Rails.env.test?
CarrierWave.configure do |config|
config.enable_processing = false
end
end
本番環境での画像アップロード
AWSを使用するようなので、スキップする。