静的型付け言語原理主義者によるRailsチュートリアル(第13章)
第13章 ユーザーのマイクロポスト
- ユーザーが短いメッセージを投稿できるようにするためのリソースであるマイクロポストを追加していく
- Micropost データモデルを作成
- User モデルと
has_many
およびbelong_to
メソッドを使って関連付けを行う - 結果を処理し表示するために必要なフォームとその部品を作成する
13.1 Micropost モデル
- まずはモデルを作成するところから
13.1.1 基本的なモデル
- マイクロポストの内容を保存する
content
属性と、特定のユーザーとマイクロポストを関連付けるuser_id
属性の2つの属性だけを持つ Micropost モデルを生成する
rails generate model Micropost content:text user:references
user:reference
の引数により、生成されたモデルにはbelongs_to
のコードも追加されているreference
型を使うことにより、自動的にインデックスと外部キー参照付きのuser_id
カラムが追加され、User と Micropost を関連付ける下準備をしてくれる- つよつよでは
user_id
に関連付けられたすべてのマイクロポストを作成時刻の逆順で取り出しやすくするために、user_id
とcreated_at
カラムにインデックスを付与する
class CreateMicroposts < ActiveRecord::Migration[5.0]
def change
create_table :microposts do |t|
t.text :content
t.references :user, foreign_key: true
t.timestamps
end
add_index :microposts, [:user_id, :created_at]
end
end
- 以上をもってマイグレーションを実施
13.1.2 Micropost のマイグレーション
- まずは
Micropost
モデル単体を TDD で動くようにしてみる setup
で fixture のサンプルユーザーと紐付けた新しいマイクロポストを作成し、その有効性をチェックしする- あらゆるマイクロポストはユーザーの id を持っているべきなので、
user_id
の存在性バリデーションに対するテストも追加する
- あらゆるマイクロポストはユーザーの id を持っているべきなので、
require 'test_helper'
class MicropostTest < ActiveSupport::TestCase
def setup
@user = users(:michael)
# このコードは慣習的に正しくない
@micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id)
end
test "should be valid" do
assert @micropost.valid?
end
test "user id should be present" do
@micropost.user_id = nil
assert_not @micropost.valid?
end
end
- 1つ目のテストでは正常な状態かどうかをテストしている
- 2つ目のテストでは
user_id
が存在しているかどうかをテストしている- パスするために存在性のバリデーションを追加する
class Micropost < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
end
- Rails5 では上記バリデーションを追加せずともテストが通ってしまう
- 慣習的に正しくないコードを書いた場合のみ発生する
- そんな慣習に依存する仕様はどうなのよ。。。
- 慣習的に正しくないコードを書いた場合のみ発生する
- 次にマイクロポストの
content
属性に対するバリデーションを追加する- 存在必然性の制限と、140文字より長くならないようにする制限を加える
- TDD スタイルで
- 存在必然性の制限と、140文字より長くならないようにする制限を加える
require 'test_helper'
class MicropostTest < ActiveSupport::TestCase
def setup
@user = users(:michael)
@micropost = Micropost.new(content: "Lorem ipsum", user_id: @user.id)
end
test "should be valid" do
assert @micropost.valid?
end
test "user id should be present" do
@micropost.user_id = nil
assert_not @micropost.valid?
end
test "content should be present" do
@micropost.content = " "
assert_not @micropost.valid?
end
test "content should be at most 140 characters" do
@micropost.content = "a" * 141
assert_not @micropost.valid?
end
end
- アプリケーション側の実装を対応させる
class Micropost < ApplicationRecord
belongs_to :user
validates :user_id, presence: true
validates :content, presence: true, length: { maximum: 140 }
end
13.1.3 User/Micropost の関連付け
- それぞれのマイクロポストは1人のユーザーと関連付けられ、それぞれのユーザーは複数のマイクロポストと関連付けられる
- マイクロポストがユーザーに所属するための関連付けはマイグレーションによって自動生成されているが、ユーザーがマイクロポストを複数所有できるようにする関連付けは手動で行う必要がある
class User < ApplicationRecord
has_many :microposts
.
.
.
end
- 正しく関連付けができたら、慣習的に正しくマイクロポストを作成する
require 'test_helper'
class MicropostTest < ActiveSupport::TestCase
def setup
@user = users(:michael)
@micropost = @user.microposts.build(content: "Lorem ipsum")
end
test "should be valid" do
assert @micropost.valid?
end
test "user id should be present" do
@micropost.user_id = nil
assert_not @micropost.valid?
end
.
.
.
end
13.1.4 マイクロポストを改良する
- User と Micropost の関連付けを改良していく
- ユーザーのマイクロポストを特定の順序で取得できるようにする
- マイクロポストをユーザーに依存させ、ユーザー削除に伴ってマイクロポストも自動削除されるようにする
デフォルトのスコープ
user.microposts
メソッドはデフォルトでは読み出しの順序に対して何も保証しないので、最新のものを先頭表示するようにしてみる- 例によって TDD で
- DB 上の最初のマイクロポストが fixture 内のマイクロポストと同じであるか
require 'test_helper'
class MicropostTest < ActiveSupport::TestCase
.
.
.
test "order should be most recent first" do
assert_equal microposts(:most_recent), Micropost.first
end
end
- 上記に対応した fixture ファイルも用意しておく
- この状態ではテストが失敗するので、
default_scope
でマイクロポストの順序を変更する
class Micropost < ApplicationRecord
belongs_to :user
default_scope -> { order(created_at: :desc) }
validates :user_id, presence: true
validates :content, presence: true, length: { maximum: 140 }
end
Dependent: destroy
- サイト管理者はユーザーを廃棄する権限を持つので、ユーザーのは気に伴ってマイクロポストも破棄する
class User < ApplicationRecord
has_many :microposts, dependent: :destroy
.
.
.
end
dependent: :destroy
オプションを使うと、ユーザー削除時にそのユーザーに紐付いたマイクロポストも一緒に削除されるようになる- DB に所有者不明のマイクロポストが残ってしまうのを防ぐ
- こういう不正データ対応は大事よね。。。
- DB に所有者不明のマイクロポストが残ってしまうのを防ぐ
dependent: :destroy
が機能するか User モデルを検証する
require 'test_helper'
class UserTest < ActiveSupport::TestCase
def setup
@user = User.new(name: "Example User", email: "user@example.com",
password: "foobar", password_confirmation: "foobar")
end
.
.
.
test "associated microposts should be destroyed" do
@user.save
@user.microposts.create!(content: "Lorem ipsum")
assert_difference 'Micropost.count', -1 do
@user.destroy
end
end
end
13.2 マイクロポストを表示する
- ユーザーの
show
ページで直接マイクロポストを表示させる
13.2.1 マイクロポストの描画
- まずは Micropost のコントローラとビューを作成するために、コントローラを生成する
- 1つのマイクロポストを表示するパーシャルは以下のようになる
<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 %></span>
<span class="timestamp">
Posted <%= time_ago_in_words(micropost.created_at) %> ago.
</span>
</li>
- 一度にすべてのマイクロポストが表示されてしまう問題に対処する
@microposts
変数を Users コントローラのshow
アクションで明示的にwill_paginate
に渡す
class UsersController < ApplicationController
.
.
.
def show
@user = User.find(params[:id])
@microposts = @user.microposts.paginate(page: params[:page])
end
.
.
.
end
- 最後に投稿数表示を加味してマイクロポストをユーザーの
show
ページに追加する
<% provide(:title, @user.name) %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<h1>
<%= gravatar_for @user %>
<%= @user.name %>
</h1>
</section>
</aside>
<div class="col-md-8">
<% if @user.microposts.any? %>
<h3>Microposts (<%= @user.microposts.count %>)</h3>
<ol class="microposts">
<%= render @microposts %>
</ol>
<%= will_paginate @microposts %>
<% end %>
</div>
</div>
- この時点ではまだマイクロポストがないので何も表示されない
13.2.2 マイクロポストのサンプル
- サンプルデータにマイクロポストを追加する
.
.
users = User.order(:created_at).take(6)
50.times do
content = Faker::Lorem.sentence(5)
users.each { |user| user.microposts.create!(content: content) }
end
- 開発環境用の DB で再度サンプルデータを生成する
- サンプルデータから生成されたページにはマイクロポスト固有のスタイルが与えられていないので、CSSを適用する
13.2.3 プロフィール画面のマイクロポストをテストする
- プロフィール画面で表示されるマイクロポストに対して統合テストを書いていく
- プロフィール画面におけるマイクロポストをテストするために、ユーザーと関連付けられたマイクロポストの fixture を追加する
orange:
content: "I just ate an orange!"
created_at: <%= 10.minutes.ago %>
user: michael
tau_manifesto:
content: "Check out the @tauday site by @mhartl: http://tauday.com"
created_at: <%= 3.years.ago %>
user: michael
cat_video:
content: "Sad cats are sad: http://youtu.be/PKffm2uI4dk"
created_at: <%= 2.hours.ago %>
user: michael
most_recent:
content: "Writing a short test"
created_at: <%= Time.zone.now %>
user: michael
<% 30.times do |n| %>
micropost_<%= n %>:
content: <%= Faker::Lorem.sentence(5) %>
created_at: <%= 42.days.ago %>
user: michael
<% end %>
- 上記データを用いてテストを実装する
- プロフィール画面にアクセスし
- ページタイトルとユーザー名、Gravatar、マイクロポストの投稿数、ページ分割されたマイクロポストという順でテストしていく
require 'test_helper'
class UsersProfileTest < ActionDispatch::IntegrationTest
include ApplicationHelper
def setup
@user = users(:michael)
end
test "profile display" do
get user_path(@user)
assert_template 'users/show'
assert_select 'title', full_title(@user.name)
assert_select 'h1', text: @user.name
assert_select 'h1>img.gravatar'
assert_match @user.microposts.count.to_s, response.body
assert_select 'div.pagination'
@user.microposts.paginate(page: 1).each do |micropost|
assert_match micropost.content, response.body
end
end
end
13.3 マイクロポストを操作する
- データモデリングとマイクロポスト表示テンプレートが完成したので、次は Web 経由でそれらを作成するためのインターフェイスに取り掛かる
- Micropost リソースへのインターフェースは、主にプロフィールページと Home ページのコントローラを経由して実行されるので、Micropost コントローラには
new
やedit
のようなアクションは不要になるcreate
とdestroy
があれば十分なので、マイクロポストリソースのルーティングは以下のようになる
Rails.application.routes.draw do
root 'static_pages#home'
get '/help', to: 'static_pages#help'
get '/about', to: 'static_pages#about'
get '/contact', to: 'static_pages#contact'
get '/signup', to: 'users#new'
get '/login', to: 'sessions#new'
post '/login', to: 'sessions#create'
delete '/logout', to: 'sessions#destroy'
resources :users
resources :account_activations, only: [:edit]
resources :password_resets, only: [:new, :create, :edit, :update]
resources :microposts, only: [:create, :destroy]
end
13.3.1 マイクロポストのアクセス制御
- 関連付けられたユーザーを通してマイクロポストにアクセスするので、
create
アクションやdestroy
アクションを利用するユーザーはログイン済みでなければならない - ログイン済みかどうかを確かめるテストでは、正しいリクエストを各アクションに向けて発行し、マイクロポストの数が変化していないかどうか、またリダイレクトされていないかどうかを確かめればよい
require 'test_helper'
class MicropostsControllerTest < ActionDispatch::IntegrationTest
def setup
@micropost = microposts(:orange)
end
test "should redirect create when not logged in" do
assert_no_difference 'Micropost.count' do
post microposts_path, params: { micropost: { content: "Lorem ipsum" } }
end
assert_redirected_to login_url
end
test "should redirect destroy when not logged in" do
assert_no_difference 'Micropost.count' do
delete micropost_path(@micropost)
end
assert_redirected_to login_url
end
end
- テストを通すためにリファクタリングをする
logged_in_user
メソッドを Application コントローラに移す
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
include SessionsHelper
private
# ユーザーのログインを確認する
def logged_in_user
unless logged_in?
store_location
flash[:danger] = "Please log in."
redirect_to login_url
end
end
end
- コードが重複しないように Users コントローラからも
logged_in_user
を削除する - これで Microposts コントローラからも
logged_in_user
メソッドを呼び出せるようになったので、create
アクションやdestroy
アクションに対するアクセス制限が before フィルターで簡単に実装できるようになる
class MicropostsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
def create
end
def destroy
end
end
13.3.2 マイクロポストを作成する
- micropost/new ページを使う代わりに、ホーム画面(ルートパス)にフォームを置いてマイクロポストを作成する
- マイクロポストの
create
アクションを作る- 新しいマイクロポストを
build
するために User/Micropost 関連付けを使用する
- 新しいマイクロポストを
class MicropostsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
def create
@micropost = current_user.microposts.build(micropost_params)
if @micropost.save
flash[:success] = "Micropost created!"
redirect_to root_url
else
render 'static_pages/home'
end
end
def destroy
end
private
def micropost_params
params.require(:micropost).permit(:content)
end
end
- マイクロポスト作成フォームを構築するために、サイト訪問者がログインしているかどうかに応じて異なる HTML を提供する
<% if logged_in? %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<%= render 'shared/user_info' %>
</section>
<section class="micropost_form">
<%= render 'shared/micropost_form' %>
</section>
</aside>
</div>
<% else %>
<div class="center jumbotron">
<h1>Welcome to the Sample App</h1>
<h2>
This is the home page for the
<a href="https://railstutorial.jp/">Ruby on Rails Tutorial</a>
sample application.
</h2>
<%= link_to "Sign up now!", signup_path, class: "btn btn-lg btn-primary" %>
</div>
<%= link_to image_tag("rails.png", alt: "Rails logo"),
'http://rubyonrails.org/' %>
<% end %>
- 上記コードを動かすためにサイドバーで表示するユーザー情報のパーシャルを作成する
<%= link_to gravatar_for(current_user, size: 50), current_user %>
<h1><%= current_user.name %></h1>
<span><%= link_to "view my profile", current_user %></span>
<span><%= pluralize(current_user.microposts.count, "micropost") %></span>
- マイクロポスト作成フォームも定義する
<%= 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" %>
<% end %>
- 上記コードを動かすために、
home
アクションにマイクロポストのインスタンス変数を追加する
class StaticPagesController < ApplicationController
def home
@micropost = current_user.microposts.build if logged_in?
end
def help
end
def about
end
def contact
end
end
@micropost
変数はログインしているときのみ定義される- マイクロポスト投稿フォームのパーシャルを動かすために、エラーメッセージのパーシャルを再定義する必要もある
- User オブジェクト以外でも動作するように error_message パーシャルを更新する
<% if object.errors.any? %>
<div id="error_explanation">
<div class="alert alert-danger">
The form contains <%= pluralize(object.errors.count, "error") %>.
</div>
<ul>
<% object.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
- このパーシャルは他の場所でも使用されているので、テストは失敗する
- ユーザー登録、パスワード再設定、ユーザー編集のそれぞれのビューを更新すればテストは通る
13.3.3 フィードの原型
- Home ページにマイクロポストを表示する部分が実装されていないので、まだ投稿内容をすぐに見ることはできない
- 投稿後にマイクロポストをフィードを表示する
- User モデルに
feed
メソッドを作り、マイクロポストのステータスフィードを実装する準備を行う
class User < ApplicationRecord
.
.
.
# 試作feedの定義
# 完全な実装は次章の「ユーザーをフォローする」を参照
def feed
Micropost.where("user_id = ?", id)
end
private
.
.
.
end
- フィード機能導入のため、
home
アクションにフィードのインスタンス変数を追加する
class StaticPagesController < ApplicationController
def home
if logged_in?
@micropost = current_user.microposts.build
@feed_items = current_user.feed.paginate(page: params[:page])
end
end
def help
end
def about
end
def contact
end
end
- Home ページのフィード用パーシャルは以下
<% if @feed_items.any? %>
<ol class="microposts">
<%= render @feed_items %>
</ol>
<%= will_paginate @feed_items %>
<% end %>
- Home ページにステータスフィードを追加する
<% if logged_in? %>
<div class="row">
<aside class="col-md-4">
<section class="user_info">
<%= render 'shared/user_info' %>
</section>
<section class="micropost_form">
<%= render 'shared/micropost_form' %>
</section>
</aside>
<div class="col-md-8">
<h3>Micropost Feed</h3>
<%= render 'shared/feed' %>
</div>
</div>
<% else %>
.
.
.
<% end %>
- マイクロポスト投稿に失敗すると Home ページが
@feed_items
インスタンスを保持しているので壊れてしまう- 空の配列を渡しておけば回避できる
class MicropostsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
def create
@micropost = current_user.microposts.build(micropost_params)
if @micropost.save
flash[:success] = "Micropost created!"
redirect_to root_url
else
@feed_items = []
render 'static_pages/home'
end
end
def destroy
end
private
def micropost_params
params.require(:micropost).permit(:content)
end
end
13.3.4 マイクロポストを削除する
- 自分が投稿したマイクロポストに対してのみ削除リンクが動作するようにする
- マイクロポストのパーシャルに削除リンクを追加する
<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 %></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>
- 次に Micropost コントローラの
destroy
アクションを定義する
class MicropostsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
.
.
.
def destroy
@micropost.destroy
flash[:success] = "Micropost deleted"
redirect_to request.referrer || root_url
end
private
def micropost_params
params.require(:micropost).permit(:content)
end
def correct_user
@micropost = current_user.microposts.find_by(id: params[:id])
redirect_to root_url if @micropost.nil?
end
end
13.3.5 フィード画面のマイクロポストをテストする
- マイクロポスト用の fixture に別々のユーザーに紐付けられたマイクロポストを追加していく
- 次に自分以外のユーザーのマイクロポストを削除しようとすると、適切にリダイレクトされることを確認する
require 'test_helper'
class MicropostsControllerTest < ActionDispatch::IntegrationTest
def setup
@micropost = microposts(:orange)
end
test "should redirect create when not logged in" do
assert_no_difference 'Micropost.count' do
post microposts_path, params: { micropost: { content: "Lorem ipsum" } }
end
assert_redirected_to login_url
end
test "should redirect destroy when not logged in" do
assert_no_difference 'Micropost.count' do
delete micropost_path(@micropost)
end
assert_redirected_to login_url
end
test "should redirect destroy for wrong micropost" do
log_in_as(users(:michael))
micropost = microposts(:ants)
assert_no_difference 'Micropost.count' do
delete micropost_path(micropost)
end
assert_redirected_to root_url
end
end
- 最後にマイクロポストのUIに対する統合テストを書く
- ログイン
- マイクロポストのページ分割の確認
- 無効なマイクロポストを投稿
- 有効なマイクロポストを投稿
- マイクロポストの削除
- 他のユーザーのマイクロポストには[delete]リンクが表示されないことを確認
require 'test_helper'
class MicropostsInterfaceTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
test "micropost interface" do
log_in_as(@user)
get root_path
assert_select 'div.pagination'
# 無効な送信
assert_no_difference 'Micropost.count' do
post microposts_path, params: { micropost: { content: "" } }
end
assert_select 'div#error_explanation'
# 有効な送信
content = "This micropost really ties the room together"
assert_difference 'Micropost.count', 1 do
post microposts_path, params: { micropost: { content: content } }
end
assert_redirected_to root_url
follow_redirect!
assert_match content, response.body
# 投稿を削除する
assert_select 'a', text: 'delete'
first_micropost = @user.microposts.paginate(page: 1).first
assert_difference 'Micropost.count', -1 do
delete micropost_path(first_micropost)
end
# 違うユーザーのプロフィールにアクセス (削除リンクがないことを確認)
get user_path(users(:archer))
assert_select 'a', text: 'delete', count: 0
end
end
13.4 マイクロポストの画像投稿
- 画像つきマイクロポストを投稿できるようにする
- 画像アップロード用フォームと投稿された画像そのものという2つの視覚的要素が必要
13.4.1 基本的な画像アップロード
- CarrierWave という画像アップローダーを
Gemfile
に追加していつものようにbundle install
- 失敗するので
gem fog
をgem fog-aws
にしてゴリ押しで進める
- 失敗するので
- CarrierWave により Rails のジェネレーターで画像アップローダーが生成できる
- 必要となる
picture
属性を Micropost モデルに追加するためにマイグレーションファイルを生成して開発環境の DB に適用する - 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
- マイクロポスト投稿フォームに画像アップローダーを追加する
<%= 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
属性を追加する
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
end
- さらに、マイクロポストの画像表示を追加する
<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 画像の検証
- 画像サイズやフォーマットに対するバリデーションを実装する
- 画像フォーマットのバリデーションは CarrierWave を修正する
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
- 画像サイズについては
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 }
validate :picture_size
private
# アップロードされた画像のサイズをバリデーションする
def picture_size
if picture.size > 5.megabytes
errors.add(:picture, "should be less than 5MB")
end
end
end
- フロント側にもバリデーションを追加する
- 拡張子チェックを追加
- ファイルサイズは jQuery でチェックする
- 令和になっても 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>
- 大きすぎるファイルのアップロードは完全には防げないが、ここでは一旦良しとする
13.4.3 画像のリサイズ
- 画像の縦横の長さに対する制限はないので、画像を表示させる前にリサイズする
- ImageMagick を入れる
- ImageMagick かぁ。。。これがデファクトなのかな?
- 画像をリサイズするために画像アップローダーを修正する
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
13.4.4 本番環境での画像アップロード
- 本番環境での画像アップロードを調整する
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
- 画像アップロード先として S3 のセットアップが必要なのでスキップ
13.5 最後に
- いつもの通り master にマージして heroku にデプロイして終了
所感
いよいよ Rails チュートリアルも終盤であるが、ここまでくると Web アプリ開発として目新しいことはあまりなく、Rails の機能をただ扱うだけになってきたので正直なところモチベーションの維持が難しくなってきた。(まあ Rails のためのチュートリアルなので何も間違っていないのだが) 次が最後の章なのでなんとか完走したい。。。