##ステータスフィード
自身のマイクロポストと、フォローしているユーザーのマイクロポストを表示するフィードを実装する。
###フィードの条件とテスト
フィードが満たす条件は以下の3つである。
①フォローしているユーザーのマイクロポストを含む
②自身のマイクロポストを含む
③フォローしていないユーザーのマイクロポストを含まない
これらを満たすように、まずテストを書く。
require 'test_helper'
class UserTest < ActiveSupport::TestCase
.
.
.
test "feed should have the right posts" do
michael = users(:michael)
archer = users(:archer)
lana = users(:lana)
# フォローしているユーザーの投稿を確認
lana.microposts.each do |post_following|
assert michael.feed.include?(post_following)
end
# 自分自身の投稿を確認
michael.microposts.each do |post_self|
assert michael.feed.include?(post_self)
end
# フォローしていないユーザーの投稿を確認
archer.microposts.each do |post_unfollowed|
assert_not michael.feed.include?(post_unfollowed)
end
end
end
Michaelはarcherをフォローしており、lanaをフォローしていない設定である。
###フィードの実装
条件を満たすフィードの実装には、自身のIDまたはフォローしているユーザーのIDに対応するuser_idを持つマイクロポストを取得する必要がある。
試作フィードでは自身のマイクロポストのみを表示していたので、feedメソッドは以下のようだった。
# 試作feedの定義
def feed
Micropost.where("user_id = ?", id)
end
フォローしているユーザーのマイクロポストを取得するために、まずフォローしているユーザーのIDを配列の形で取得する。
そのためには、following_idsメソッドを使う。
>> User.first.following_ids
=> [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
42, 43, 44, 45, 46, 47, 48, 49, 50, 51]
following_idsメソッドは、has_many :followingの関連付けをした時に自動で生成される。
feedメソッドでこれを使うためには、SQLを使って以下のようにする。
# ユーザーのステータスフィードを返す
def feed
Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
end
これでテストがGREENになる。
###サブセレクトとフィードの改良
ここまでで実装したフィードではフォロー数が膨大になるとアプリケーションの動作が遅くなる。
フォローしているユーザーのIDをデータベースから取得した後、それを使ってまたデータベースからマイクロポストを取得するという操作をしていることが原因である。
この問題をSQLのサブセレクトを使用することで解決する。
まずfeedメソッドを少し書き換える。
# ユーザーのステータスフィードを返す
def feed
Micropost.where("user_id IN (:following_ids) OR user_id = :user_id", following_ids: following_ids, user_id: id)
end
このコードでは、変更前のコードで?に代入されていた部分がハッシュのキーと値に変わっている。
?にはキーが入っている。
これは同じ変数を複数の箇所で使用するためである。
次にfollowing_idsを以下のようなSQLの文字列で置き換える。
following_ids = "SELECT followed_id FROM relationships
WHERE follower_id = :user_id"
user_idが1の場合、このコードは「ユーザー1がフォローしているユーザーすべてを選択する」という意味を持つ。
これによりフォローしているユーザーをIDを経由せずに直接データベースから取得できるため、動作を高速化できる。
# ユーザーのステータスフィードを返す
def 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)
end
following_idsにはSQLの文字列が入っているので、式展開を使っている。