13.3 マイクロポストを操作する
- データモデリングとマイクロポスト表示テンプレートをWeb経由でそれらを作成するためのインターフェイスに取り掛かる
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
# Micropost.countが変化していなければtrue
post microposts_path, params: { micropost: { content: "Lorem ipsum" } }
# マイクロソフトを作成する
end
assert_redirected_to login_url
# login_urlにリダイレクトされているか
end
test "should redirect destroy when not logged in" do
# ログインしていないときに削除しようとするとリダイレクトされるか検証するテスト
assert_no_difference 'Micropost.count' do
# Micropost.countが変化していなければtrue
delete micropost_path(@micropost)
# マイクロソフトを削除する
end
assert_redirected_to login_url
# login_urlにリダイレクトされているか
end
end
演習 1
なぜUsersコントローラ内にあるlogged_in_userフィルターを残したままにするとマズイのでしょうか? 考えてみてください。
コードが重複しているから。
エラーの原因になるかもしれないし、DRYではない。
13.3.2 マイクロポストを作成する
class MicropostsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
# 各アクションに入る前にlogged_in_userを実行
# logged_in_userはユーザーのログインを確認するメソッド
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'
# static_pages/homeを描画
end
end
def destroy
end
private
def micropost_params
params.require(:micropost).permit(:content)
end
end
演習 1
Homeページをリファクタリングして、if-else文の分岐のそれぞれに対してパーシャルを作ってみましょう。
<% if logged_in? %>
<%= render 'static_pages/user_logged_in' %>
<% else %>
<%= render 'static_pages/user_logged_out' %>
<% end %>
<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>
<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.svg", alt: "Rails logo", width: "200px"), "https://rubyonrails.org/" %>
13.3.3 フィードの原型
演習 1
新しく実装したマイクロポストの投稿フォームを使って、実際にマイクロポストを投稿してみましょう。Railsサーバーのログ内にあるINSERT文では、どういった内容をデータベースに送っているでしょうか? 確認してみてください。
Micropost Create (11.7ms)
INSERT INTO "microposts" ("content", "user_id", "created_at", "updated_at")
VALUES (?, ?, ?, ?)
[["content", "Hello SAMPLE APP !"],
["user_id", 1],
["created_at", "2021-03-16 03:08:23.301012"],
["updated_at", "2021-03-16 03:08:23.301012"]]
↳ app/controllers/microposts_controller.rb:9:in `create'
演習 2
コンソールを開き、user変数にデータベース上の最初のユーザーを代入してみましょう。その後、Micropost.where("user_id = ?", user.id)とuser.microposts、そしてuser.feedをそれぞれ実行してみて、実行結果がすべて同じであることを確認してみてください。ヒント: ==で比較すると結果が同じかどうか簡単に判別できます。
>> user = User.first
(0.1ms) begin transaction
User Load (0.7ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "example@railstutorial.org", created_at: "2021-03-16 03:01:20", updated_at: "2021-03-16 03:01:20", password_digest: [FILTERED], remember_digest: nil, admin: true, activation_digest: "$2a$12$VFPF70fqU0mWCdYrpnPI5uRjn1yQePv61n5WYjNlvvo...", activated: true, activated_at: "2021-03-16 03:01:19", reset_digest: nil, reset_sent_at: nil>
>> Micropost.where("user_id = ?", user.id) == user.microposts
Micropost Load (0.4ms) SELECT "microposts".* FROM "microposts" WHERE (user_id = 1) ORDER BY "microposts"."created_at" DESC
=> true
>> user.microposts == user.feed
Micropost Load (0.8ms) SELECT "microposts".* FROM "microposts" WHERE (user_id = 1) ORDER BY "microposts"."created_at" DESC
=> true
>> user.microposts == user.feed
Micropost Load (0.8ms) SELECT "microposts".* FROM "microposts" WHERE (user_id = 1) ORDER BY "microposts"."created_at" DESC
=> true
>> Micropost.where("user_id = ?", user.id) == user.feed
=> true
13.3.4 マイクロポストを削除する
- マイクロポストリソースにポストを削除する機能を追加する
- 自分が投稿したマイクロポストに対してのみ削除リンクが動作するようにする
class MicropostsController < ApplicationController
before_action :logged_in_user, only: [:create, :destroy]
before_action :correct_user, only: :destroy
# destroyアクションが呼び出される前にcorrect_userメソッドを実行する
.
.
.
def destroy
@micropost.destroy
# マイクロポストを削除する
flash[:success] = "Micropost deleted"
# フラッシュメッセージを表示する
redirect_to request.referrer || root_url
# DELETEリクエストが発行されたページにリダイレクト
# DELETEリクエストが発行されたページが取得できない場合は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
演習 1
マイクロポストを作成し、その後、作成したマイクロポストを削除してみましょう。次に、Railsサーバーのログを見てみて、DELETE文の内容を確認してみてください。
Micropost Destroy (7.8ms)
DELETE FROM "microposts" WHERE "microposts"."id" = ? [["id", 301]]
↳ app/controllers/microposts_controller.rb:20:in `destroy'
演習 2
redirect_to request.referrer || root_urlの行をredirect_back(fallback_location: root_url)と置き換えてもうまく動くことを、ブラウザを使って確認してみましょう(このメソッドはRails 5から新たに導入されました)。
確認のみなので省略。
13.3.5 フィード画面のマイクロポストをテストする
- Micropostsコントローラの認可をチェックする短いテストを作成する
- それらをまとめる統合テストを作成する
require 'test_helper'
class MicropostsControllerTest < ActionDispatch::IntegrationTest
def setup
@micropost = microposts(:orange)
end
.
.
.
test "should redirect destroy for wrong micropost" do
# 自分以外のユーザーがマイクロポストを削除できないか検証するテスト
log_in_as(users(:Michael))
# Michaelでログイン
micropost = microposts(:ants)
# ユーザー「ants」でマイクロソフトを投稿
assert_no_difference 'Micropost.count' do
# do-end前後で投稿数に違いがないか
delete micropost_path(micropost)
# マイクロポストにDELETEリクエスト
end
assert_redirected_to root_url
# root_urlにリダイレクトする
end
end
require 'test_helper'
class MicropostsInterfaceTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
test "micropost interface" do
# マイクロソフトインターフェイスのテスト
log_in_as(@user)
# @userでログイン
get root_path
# root_pathにGETリクエスト
assert_select 'div.pagination'
# <div>にpaginationがあるかどうか
# 無効な送信
assert_no_difference 'Micropost.count' do
# do-end前後で投稿数に違いが無いか
post microposts_path, params: { micropost: { content: "" } }
# 無効な投稿でPOSTリクエスト
end
assert_select 'div#error_explanation'
# <div>内にerror_explanationがあるかどうか
assert_select 'a[href=?]', '/?page=2'
# 正しいページネーションリンクがあるかどうか
# 有効な送信
content = "This micropost really ties the room together"
# 有効な投稿内容をcontentに代入
assert_difference 'Micropost.count', 1 do
# do-end前後で投稿数に違いが無いか
post microposts_path, params: { micropost: { content: content } }
# 有効な投稿でPOSTリクエスト
end
assert_redirected_to root_url
# root_urlにリダイレクトされているか
follow_redirect!
# リダイレクト先に移動しているか
assert_match content, response.body
# <body>内にcontentがあるかどうか
# 投稿を削除する
assert_select 'a', text: 'delete'
# deleteリンクがあるかどうか
first_micropost = @user.microposts.paginate(page: 1).first
# データを変数に代入
assert_difference 'Micropost.count', -1 do
# do-end内で投稿数が-1されているかどうか
delete micropost_path(first_micropost)
# マイクロソフトにDELETEリクエスト
end
# 違うユーザーのプロフィールにアクセス(削除リンクがないことを確認)
get user_path(users(:archer))
# archerのユーザーページにGETリクエスト
assert_select 'a', text: 'delete', count: 0
# DELETEリンクが表示されていないか
end
end
演習 1
リスト 13.56で示した4つのコメント(「無効な送信」など)のそれぞれに対して、テストが正しく動いているか確認してみましょう。具体的には、対応するアプリケーション側のコードをコメントアウトし、テストが red になることを確認し、元に戻すと green になることを確認してみましょう。
確認のみなので省略。
演習 2
サイドバーにあるマイクロポストの合計投稿数をテストしてみましょう。このとき、単数形(micropost)と複数形(microposts)が正しく表示されているかどうかもテストしてください。ヒント: リスト 13.58を参考にしてみてください。
require 'test_helper'
class MicropostInterfaceTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
end
.
.
.
test "micropost sidebar count" do
# 投稿数のテスト
log_in_as(@user)
# ログイン
get root_path
# root_pathにGETリクエスト
assert_match "#{@user.microposts.count} microposts", response.body
# <body>に#{@user.microposts.count} micropostsが表示されているか
# まだマイクロポストを投稿していないユーザー
other_user = users(:malory)
# maloryを代入
log_in_as(other_user)
# 違うユーザーでログイン
get root_path
# root_pathにGETリクエスト
assert_match "0 microposts", response.body
# <body>に0 micropostsと表示されているか
other_user.microposts.create!(content: "A micropost")
# 投稿する
get root_path
# root_pathにGETリクエスト
assert_match "1 micropost", response.body
# <body>に1 micropostが表示されているか
end
end