チュートリアル 第14章 ユーザーをフォローする - ステータスフィード - サブセレクト
前準備 - where
メソッド内の変数に、キーと値のペアを使うようにする
where
メソッドの第1引数であるSQL文において、Rails側の変数の内容を使う部分は、これまで?
(疑問符)として与えてきました。以下のようなメソッド呼び出しがその例です。
Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
where
メソッドは、実は「上記?
の部分に、?
ではなくRubyのシンボルを与える」という使い方ができます。以下のようなメソッド呼び出しがその例です。
Micropost.where(
"user_id IN (:following_ids) OR user_id = :user_id",
following_ids: following_ids,
user_id: id
)
結果、app/models/user.rb
内のfeed
メソッドは以下のように変更できます。
def feed
- Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
+ Micropost.where(
+ "user_id IN (:following_ids) OR user_id = :user_id",
+ following_ids: following_ids,
+ user_id: id
+ )
end
このような変更をするからには、「following_ids
もしくはuser_id
を複数箇所で使う」という実装が発生するということなのでしょう。
「フィードを初めて実装する」の実装の問題点
「フィードを初めて実装する」の実装では、「投稿されたマイクロポストの数が膨大になった際にうまくスケールしない」という問題点があります。Railsチュートリアル本文には以下のようにあります。
フォローしているユーザーが5,000人程度になるとWebサービス全体が遅くなる可能性があります
現状の実装は、一体どのような点でスケールしないのでしょうか。「よりスケールする実装」というのは、一体どのような実装なのでしょうか。
現状の実装ではどういう処理がされているのか、現状の実装は何が問題なのか
「フィードを初めて実装する」の実装におけるfeed
メソッドの実装内容は、以下のようなものでした。
def feed
Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
end
上記のコードは、最終的に以下のような動作をすることになります。
-
following_ids
メソッドにより、現在フォローしている全てのユーザーを得るためにRDBに問い合わせを行う - 全ての
Micropost
オブジェクトを得るため、前述following_ids
メソッドの戻り値を条件に、RDBのmicropostsテーブル全体を対象として問い合わせを行う
今回開発しているアプリケーションのユースケースでは、idが上記1.の集合に内包されているかどうかだけをチェックするため、RDBに2回問い合わせを行う現状の動作はどうにもまどろっこしいです。また、Railsが介入する必要がないであろうところにRailsが介入しているのはよろしくありません。
より効率的な方法はないのでしょうか。いや、こうした集合計算に特化した言語であるSQLなら、より効率的な方法はきっとあるはずです。
どういう処理だとよりいいのか - サブクエリ(サブセレクト)を用いたクエリの使用
今回行おうとしている処理の場合、「SELECT
文の結果そのものを、(Railsに渡すことなく)次段のSELECT
文の評価の対象とする」という形にすることによって、全てをRDBMS内で完結させることができます。RDBMSはこうした処理に最適化されているので、全てをRDBMS内で完結させることができれば、途中でRailsが介在する実装より高速な処理が実現できます。
例えば現在のユーザーのidが1である場合、このような処理を実現するためのSQL文は以下のようになります。
SELECT * FROM microposts
WHERE user_id IN (
SELECT followed_id FROM relationships
WHERE follower_id = 1
) OR user_id = 1
上記SQL文のように、「SELECT
文の結果そのものを、次段のSQL文の評価の対象とする」処理は「サブクエリを用いたクエリ」と呼ばれます。()
の内側のSQL文は「サブクエリ(もしくはサブセレクト)」と呼ばれます。
このようなサブクエリを用いたクエリにおいては、集合を組み立てるロジックはRDBMS内で完結します。
サブクエリを用いたクエリをwhere
メソッドで使用する
Micropost.where(
"user_id IN (:following_ids) OR user_id = :user_id",
following_ids: following_ids,
user_id: id
)
Micropost.where(
"user_id IN (:following_ids) OR user_id = :user_id",
following_ids: following_ids,
user_id: id
)
上記Micropost.where
の引数:following_ids
は、前述「サブクエリを用いたクエリの使用」を踏まえて、以下のように書き換えることができます。
following_ids =
"SELECT followed_id FROM relationships
WHERE follower_id = :user_id"
結果、User#feed
メソッドの実装は以下のように書き換えられる、ということになるわけです。
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
フィードの最終的な実装
ここまでの内容を踏まえると、app/models/user.rb
に対する変更の内容は、以下のようになります。
def feed
- Micropost.where(
- "user_id IN :following_ids, OR user_id = :user_id",
- following_ids: following_ids,
- user_id: id
- )
+ 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
test/models/user_test.rb
を対象としたテストも実行しておきましょう、
# rails test test/models/user_test.rb
Running via Spring preloader in process 428
Started with run options --seed 8307
15/15: [=================================] 100% Time: 00:00:01, Time: 00:00:01
Finished in 1.29007s
15 tests, 64 assertions, 0 failures, 0 errors, 0 skips
無事テストが通りました。
開発環境において、Homeページにフィードが表示されている様子は以下のようになります。
本番環境において、Homeページにフィードが表示されている様子は以下のようになります。
Author And Source
この問題について(チュートリアル 第14章 ユーザーをフォローする - ステータスフィード - サブセレクト), 我々は、より多くの情報をここで見つけました https://qiita.com/rapidliner00/items/6aef0a85813cd7be273c著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .