ユーザーフォローの関連付けを自分の言葉で説明してみる
Rails Tutorial 14章でお馴染みのユーザーフォロー機能。 ユーザー同士の多対多と関係するコードについて、自分の言葉で説明してみたいと思います。 尚、実装例や全ての解説とコードは以下の記事にまとまっていますので、とりあえず動かしたいという方はそちらを読んでください。
ユーザーフォロー概観
- 1ユーザーはたくさんのユーザーをフォローでき、たくさんのユーザーによってフォローされる
- このような関係のことを自己結合多対多と呼ぶ
- 多対多なので、中間テーブル(Relationships)が必要
ゴール
irb(main):002:0> user.followings => #<ActiveRecord::Associations::CollectionProxy [#<User id: 3, email: "testuser-3@example.com", created_at: "2020-08-11 00:05:15", updated_at: "2020-08-11 00:05:15", address: nil, postal_code: nil, bio: nil, name: "testuser-3", uid: "d36fa9e5-c0ba-41b0-a386-1122be7e8bde", provider: "">, #<User id: 4, email: "testuser-4@example.com", created_at: "2020-08-11 00:05:16", updated_at: "2020-08-11 00:05:16", address: nil, postal_code: nil, bio: nil, name: "testuser-4", uid: "4a93e555-40ec-4308-a3d5-c2a59ae03c2e", provider: "">]> irb(main):004:0> user.followers => #<ActiveRecord::Associations::CollectionProxy [#<User id: 2, email: "testuser-2@example.com", created_at: "2020-08-11 00:05:15", updated_at: "2020-08-11 00:05:15", address: nil, postal_code: nil, bio: nil, name: "testuser-2", uid: "a927f181-595e-4e74-9c29-11367612cb85", provider: "">, #<User id: 3, email: "testuser-3@example.com", created_at: "2020-08-11 00:05:15", updated_at: "2020-08-11 00:05:15", address: nil, postal_code: nil, bio: nil, name: "testuser-3", uid: "d36fa9e5-c0ba-41b0-a386-1122be7e8bde", provider: "">]>
自分がフォローしているユーザー、フォローされているユーザーを取得できるようにします。
ユーザーモデルの切り分け
Railsのコードを書く前に、DBレベルではどのようなデータが保存されているか確認します。(手書きのメモが汚いです すんません..)
とてもシンプルですね。 ユーザーはユーザーをたくさん持っていて、ユーザーはユーザーをたくさん持っている状態を表しています。 DBレベルではデータを保存するだけなのでこの実装で問題ないのですが、Rails側でデータを取り出したいとなった時にひとつ困ることがあります。 「ユーザーはユーザーをたくさん持っていて、ユーザーはユーザーをたくさん持っている」これだけだと、ユーザー間の関係性をうまく表現できません。フォロワーとフォローされる側を明確にする必要があります。 よって以下のコードを追記します。
class Relationship < ApplicationRecord belongs_to :following, class_name: 'User' belongs_to :follower, class_name: 'User' end
この記述により、Railsからは以下のようにデータを扱えるようになりました。
「フォロワーはたくさんのフォローしているユーザーを持っている」&「フォローしているユーザーはたくさんのフォローされているユーザーを持っている」という状態を表現できました。 この記述により最後に実装する「ユーザーの取得」で、適切なユーザーを取得できるようになります。 (あくまでRailsから操作できる仮想的なモデル名であり、DBに保存される情報はコード追記前と変わりません)
Relationshipから適切なデータを取得する
最終目標である「自分がフォローしているユーザー、フォローされているユーザーを取得」するには、中間テーブルであるRelationshipsを使えば書けそうです。 現在の中間テーブルの中身は以下のようになっています。
この中から自分をフォローしているユーザーとの関係(relationship)を表す列を取得するにはどうすれば良いでしょうか? 答えは 「follower_idの値が自分のidの値と同じ列」を取得するよう指定する必要があります。 逆に自分がフォローしているユーザーとの関係(relationship)を表す列を取得するには、「following_idの値が自分のidの値と同じ列」を取得するよう指定する必要があります。 UserをFollowerとFollowingに分けたように上記二つに名前を付けてあげましょう。 自分をフォローしているユーザーとの関係を「passive_relationships」、自分がフォローしているユーザーとの関係を「active_relationships」とします。 コードは以下のようになります。
class User < ApplicationRecord has_many :active_relationships, class_name: 'Relationship', foreign_key: :following_id, dependent: :destroy has_many :passive_relationships, class_name: 'Relationship', foreign_key: :follower_id, dependent: :destroy end
「follower_idの値が自分のidの値と同じ列」を取得する指定はforeign_key
オプションで行います。
dependent: :destroy
はユーザーが削除された時、relationshipも削除するという意味になります。
ユーザーの取得
active_relationships
とpassive_relationships
を通してフォロー中/フォロワーのユーザーを取得します。
followings
を例に説明します。自分がフォローしているユーザーとの関係性を表すactive_relationshipsから、自分にフォローされているユーザーを取得します。
コードは以下のようになります
class User < ApplicationRecord has_many :followings, through: :active_relationships, source: :follower has_many :followers, through: :passive_relationships, source: :following end
「ユーザーモデルの切り分け」で記述した以下のコードによってどちらのユーザーを取ってくるかsource
を指定できるのです!!
class Relationship < ApplicationRecord belongs_to :following, class_name: 'User' belongs_to :follower, class_name: 'User' end
まとめ
既にユーザーフォローに関する素晴らしい記事は存在しますが、自分の言葉でまとめてみると理解度も上がるなと感じました。 疑問点、間違いの指摘等ありましたらコメントお願いします。