Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Railsチュートリアル 第14章 ユーザーをフォローする - ステータスフィード - フィードを初めて実装する

More than 1 year has passed since last update.

フィードに必要となるSQLクエリと、対応するwhereメソッドの引数

要件は以下です。

  • 対象ユーザーがフォローしているユーザーのユーザーidを持つマイクロポストを全て選択する
  • かつ、対象ユーザー自身のマイクロポストも全て選択する

以上の要件を満たす最小限のSQLクエリを模式的に書くと、以下のようになります。

SELECT * FROM microposts
WHERE user_id IN (<list of ids>) OR user_id = <user id>

「ログインしているユーザーがフォローしているユーザーのid」というのは、多くの場合複数となります。ゆえに、それらをクエリ問い合わせに使う場合、単一の値ではなく集合として与えなければなりません。そのような場合に使うSQLのキーワードがINとなります。

Active Recordでは、whereメソッドを使うことによって、SQL文のWHERE以下に相当する検索条件を与えることができます。上記のSQL文に対応するwhereメソッドの引数の与え方は以下のようになります。

Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)

following_idsの内容は、「ユーザーidをカンマ区切りで列挙したもの」となります。となると、次に問題となるのは、「カンマ区切りでユーザーidを列挙したものを文字列で得るためにはどうすればいいか」ということになりますね。

参考…自身のマイクロポスト全てを取得するためのSQL文と、whereメソッドの引数の与え方

「対象ユーザー自身のマイクロポストを全て選択する」という要件を満たす最小限のクエリは、以下のような内容になります。

SELECT * FROM microposts
WHERE user_id = <user id>

上記のSQL文に対応するwhereメソッドの引数の与え方は以下のようになります。

Micropost.where("user_id = ?", id)

ユーザーidの列挙を、カンマ区切りの文字列として得る方法

Rubyにおいては、「オブジェクトの列挙から、各オブジェクトの特定の属性値を文字列化し、その結果の列挙を配列として得る」という操作は、「当該オブジェクトの列挙に対して、ブロック内でto_sメソッドを呼び出す形でmapメソッドを実行する」という処理を行うことによって実現できます。

>> [1, 2, 3, 4].map { |i| i.to_s}
=> ["1", "2", "3", "4"]

上記のmapメソッドの呼び出しでは、各要素に対してto_sメソッドが実行されています。mapメソッド(やeachメソッド等)に与えるブロックにおいて、内部の処理が「単一のメソッドを実行する」というものである場合、「mapメソッド(やeachメソッド等)の引数として、ブロック中で実行するメソッドのシンボルを&に続けて記述したものを与える」という省略表記を使うことが可能です。

>> [1, 2, 3, 4].map(&:to_s)      
=> ["1", "2", "3", "4"]

上記結果に対してjoinメソッドを使うと、以下のように、idの集合をカンマ区切りで繋げた文字列を得ることができます。

>> [1, 2, 3, 4].map(&:to_s).join(", ")
=> "1, 2, 3, 4"

実際に、Userオブジェクトの列挙からidの集合をカンマ区切りの文字列として得てみる

データベースの最初のユーザーを対象として、フォローしている全ユーザーのidを配列として得てみます。

User.first.following.map(&:id)
=> [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]

このような呼び出しは、実際のアプリケーションでも頻繁に行われます。そのため、Active Recordでは次のようなメソッドも用意されています1

>> 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]

ここまでで得られたのはあくまで配列です。最終的には、得られた配列を「カンマ区切りの文字列」に変換しなければなりません。お目当ての出力を得るためには、join(', ')をメソッドチェーンの最後につないでやる必要がある、ということですね。

>> User.first.following_ids.join(', ')
=> "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"

これで「カンマ区切りでユーザーidを列挙したものを文字列で得る」という出力結果を得ることができました。

whereメソッドの引数内でfollowing_idsメソッドを使う

user.following_ids.join(', ')というのは、あくまで説明用のコードであり、実際のRailsアプリケーションでこのようなコードを使うことはありません。実際のRailsアプリケーションにおいて、whereメソッドの引数内でfollowing_idsメソッドを使う場合、特に引数を取ることなくfollowing_idsと書けばいいのです。whereの第一引数で正しく?が使われていれば、それで「カンマ区切りでユーザーidを列挙したものを含めたSQLクエリを文字列で得る」ことができるのです。RDBMS依存の非互換性の一部も含め、全てはRailsがよしなにしてくれます。

Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)

app/models/user.rbの実装を変更する

ここまでの内容を踏まえ、実際にapp/models/user.rbに加える変更の内容は以下のようになります。

app/models/user.rb
  class User < ApplicationRecord
    ...略

    # 試作feedの定義
    # 完全な実装は次章の「ユーザーをフォローする」を参照
    def feed
-     Micropost.where("user_id = ?", id)
+     Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)
    end

    ...略
  end

test/models/user_test.rbに対し、改めてテストを実行する

ここまでの実装を反映すれば、test/models/user_test.rbに対するテストが通るようになります。

# rails test test/models/user_test.rb
Running via Spring preloader in process 178
Started with run options --seed 54454

  15/15: [=================================] 100% Time: 00:00:01, Time: 00:00:01

Finished in 1.41146s
15 tests, 64 assertions, 0 failures, 0 errors, 0 skips
  • ログインユーザー自身のマイクロポストがステータスフィードに含まれていること
  • フォローしているユーザーのマイクロポストがステータスフィードに含まれていること
  • フォローしていないユーザーのマイクロポストがステータスフィードに含まれていないこと

以上の機能が実現されている、ということになりますね!


  1. 当該メソッド名は、Active Recordで使用しているモデル名そのものが含まれています。ライブラリ作成時にモデル名がわかるはずがないので、「黒魔術」ことメタプログラミングの手法を使わなければ実現できないタイプの実装といえますね。 

rapidliner00
* 現在のところは、エンジニアに憧れる非エンジニア * エンジニア的な業務効率化・改善に興味あり
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away