14.3 ステータスフィード
- ステータスフィードを実装する
14.3.1 動機と計画
テストから書いていく。
このテストで重要なことは以下の3つ。
- フォローしているユーザーのマイクロポストがフィードに含まれていること(Lana)
- 自分自身のマイクロポストもフィードに含まれていること(Michael)
- フォローしていないユーザーのマイクロポストがフィードに含まれていないこと(Archer)
演習 1
マイクロポストのidが正しく並んでいると仮定して(すなわち若いidの投稿ほど古くなる前提で)、図 14.22のデータセットでuser.feed.map(&:id)を実行すると、どのような結果が表示されるでしょうか? 考えてみてください。ヒント: 13.1.4で実装したdefault_scopeを思い出してください。
user.feedを配列にして取り出す。若いidの投稿ほど古くなるので、もっとも古い投稿がトップに来る。
[1, 2, 4, 5, 7, 9, 10]
14.3.2 フィードを初めて実装する
SELECT * FROM microposts
# micropostsからすべて(*)を取り出してください
WHERE user_id IN (<list of ids>) OR user_id = <user id>
# 取り出す条件は
# user_idがフォローしているユーザーidと一致している場合
# または自分自身のユーザーidと一致している場合
(<list of ids>)
: フォローしているユーザーidの集合がほしい
Active Recordでfollowing_ids
が用意されている。
# ユーザーのステータスフィードを返す
def feed
Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
# ?には2つ目の引数「following_ids」が入る
end
演習 1
リスト 14.44において、現在のユーザー自身の投稿を含めないようにするにはどうすれば良いでしょうか? また、そのような変更を加えると、リスト 14.42のどのテストが失敗するでしょうか?
def feed
# Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
Micropost.where("user_id IN (?) ", following_ids)
end
# 自分自身の投稿を確認
michael.microposts.each do |post_self|
assert michael.feed.include?(post_self)
end
FAIL["test_feed_should_have_the_right_posts", #<Minitest::Reporters::Suite:0x00007f5a9fdf4110 @name="UserTest">, 2.079927581999982]
test_feed_should_have_the_right_posts#UserTest (2.08s)
Expected false to be truthy.
test/models/user_test.rb:101:in `block (2 levels) in <class:UserTest>'
test/models/user_test.rb:100:in `block in <class:UserTest>'
演習 2
リスト 14.44において、フォローしているユーザーの投稿を含めないようにするにはどうすれば良いでしょうか? また、そのような変更を加えると、リスト 14.42のどのテストが失敗するでしょうか?
def feed
# Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
Micropost.where("user_id IN (?) ", id)
end
# フォローしているユーザーの投稿を確認
lana.microposts.each do |post_following|
assert michael.feed.include?(post_following)
end
FAIL["test_feed_should_have_the_right_posts", #<Minitest::Reporters::Suite:0x00007f5a9e0d11f0 @name="UserTest">, 4.210437281999475]
test_feed_should_have_the_right_posts#UserTest (4.21s)
Expected false to be truthy.
test/models/user_test.rb:97:in `block (2 levels) in <class:UserTest>'
test/models/user_test.rb:96:in `block in <class:UserTest>'
演習 3
リスト 14.44において、フォローしていないユーザーの投稿を含めるためにはどうすれば良いでしょうか? また、そのような変更を加えると、リスト 14.42のどのテストが失敗するでしょうか? ヒント: 自分自身とフォローしているユーザー、そしてそれ以外という集合は、いったいどういった集合を表すのか考えてみてください。
def feed
# Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
Micropost.all
end
# フォローしていないユーザーの投稿を確認
archer.microposts.each do |post_unfollowed|
assert_not michael.feed.include?(post_unfollowed)
end
FAIL["test_feed_should_have_the_right_posts", #<Minitest::Reporters::Suite:0x00007f5a9c39c3f0 @name="UserTest">, 3.210739447999913]
test_feed_should_have_the_right_posts#UserTest (3.21s)
Expected true to be nil or false
test/models/user_test.rb:105:in `block (2 levels) in <class:UserTest>'
test/models/user_test.rb:104:in `block in <class:UserTest>'
14.3.3 サブセレクト
- フォローしているユーザー数に応じてスケールできるように、ステータスフィードを改善する。
演習 1
Homeページで表示される1ページ目のフィードに対して、統合テストを書いてみましょう。リスト 14.49はそのテンプレートです。
require 'test_helper'
class FollowingTest < ActionDispatch::IntegrationTest
def setup
@user = users(:michael)
log_in_as(@user)
end
.
.
.
test "feed on Home page" do
get root_path
# root_pathにGETリクエスト
@user.feed.paginate(page: 1).each do |micropost|
# @userのフィードのページネート1ページ目から1つずつ取り出してmicropostに代入
assert_match CGI.escapeHTML(micropost.content), response.body
end
end
end
演習 2
リスト 14.49のコードでは、期待されるHTMLをCGI.escapeHTMLメソッドでエスケープしています(このメソッドは11.2.3で扱ったCGI.escapeと同じ用途です)。このコードでは、なぜHTMLをエスケープさせる必要があったのでしょうか? 考えてみてください。ヒント: 試しにエスケープ処理を外して、得られるHTMLの内容を注意深く調べてください。マイクロポストの内容が何かおかしいはずです。また、ターミナルの検索機能(Cmd-FもしくはCtrl-F)を使って「sorry」を探すと原因の究明に役立つはずです。
Expected /I'm\ sorry\.\ Your\ words\ made\ sense,\ but\ your\ sarcastic\ tone\ did\ not\./ to match " ...
記号が特殊文字になってしまう。
演習 3
リスト 14.47のコードは、いわゆるSQLのINNER JOIN、すなわちjoinメソッドを使えばRailsで直接表現できます。このテストを実行したときにリスト 14.50のコードが正しいフィードを返すことを確認してください12 。このコードではどのようなSQLクエリが生成されますか?(ヒント: User.first.feedをコンソールで実行してみましょう)
following_ids = "SELECT followed_id FROM relationships
WHERE follower_id = :user_id"
Micropost.where("user_id IN (#{following_ids})
OR user_id = :user_id", user_id: id)
>> User.first.feed
(4.8ms) SELECT sqlite_version(*)
User Load (4.3ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT ? [["LIMIT", 1]]
Micropost Load (14.2ms) SELECT "microposts".* FROM "microposts" INNER JOIN "users" ON "users"."id" = "microposts"."user_id" INNER JOIN "relationships" ON "relationships"."followed_id" = "users"."id" INNER JOIN "users" "followers_users" ON "followers_users"."id" = "relationships"."follower_id" WHERE (relationships.follower_id = 1 or microposts.user_id = 1) ORDER BY "microposts"."created_at" DESC LIMIT ? [["LIMIT", 11]]
さいごに
ついにRailsチュートリアルを完走しました!
途中で挫折しそうになったこともありましたが、粘り強く続けて良かったです。
これまではインプットの作業なので、次からは自分でWebアプリを作成していこうと思います。
ここまでご覧いただきありがとうございました!