####【フォロー機能のモデル同士の関連付けについての理解】
SNS系WEBアプリの制作中に、フォロー機能のモデル同士の関連付けについて理解できていないまま実装していた部分があったので、復習も兼ねて理解を深めていきたいと思い、実装の部分を一から学習し直しました。
今回はフォロー機能の関連付けを行う際の理解を掘り下げることで学んだ内容をまとめてみようと思います。
・フォロー機能の実装に関してはこちらの記事を参考にさせていただきました。
注)この記事では主にモデル同士の関連付けの理解について深掘りを行っていくため、実装の部分は割愛させていただきます。
はじめに
class User < ApplicationRecord
#
#
has_many :active_relationships, class_name: "Relationship", foreign_key: :follower_id
has_many :followings, through: :active_relationships, source: :following
has_many :passive_relationships, class_name: "Relationship", foreign_key: :following_id
has_many :followers, through: :passive_relationships, source: :follower
突然ですが、この部分。
Userモデルに記述する、Relationshipsモデルとの関連性を持たせるこの部分のコードの理解がなかなかできずに困っていました(class_name? through? source?といった感じです)
そこでフォローする側の関連付けの部分と、フォローされる側の関連付けの部分に分けた上で、一行づつに丁寧にメソッドの意味も踏まえて考えていきたいと思います。そこで先程のコードの上2行
has_many :active_relationships, class_name: "Relationship", foreign_key: :follower_id
has_many :followings, through: :active_relationships, source: :following
active_relationships(能動的)、つまり自分がフォローしているユーザーとの関連を持たせるための記述の部分だけに着目して理解の深掘りを行っていきたいと思います。
・前提としてRelationshipsのマイグレーションファイルには、フォローしているユーザーとフォローされているユーザーのidが保存できるカラムを作成してあることとしています。
class CreateRelationships < ActiveRecord::Migration[6.0]
def change
create_table :relationships do |t|
#フォローしているユーザー(自分)のid
t.integer :follower_id
#フォローされているユーザー(相手)のid
t.integer :following_id
t.timestamps
end
end
##has_many, belongs_to
has_many :active_relationships, class_name: "Relationship", foreign_key: :follower_id
まずはこの行からいきたいと思います。
そもそもhas_manyなのですが、これは今までもモデルとの関連性を持たせる場合に使用してきました。
UserモデルとContentモデル(投稿)を紐付けるような場合には、ユーザーはたくさんの投稿を持っていて、その投稿は特定のユーザーのものだから1対多の関係、だからhas_manyを使うんだ。
という認識でいました。この認識は間違っていないのですが、実は**has_manyには「メソッドを生成するためのメソッド」**という役割もあります。
どういうことかと言いますと
has_many :active_relationships
これでactive_relationships
というメソッドを使えるようにした!ということです。
もちろんUserクラス内で定義されているのでUserクラスのインスタンスでしか使用できませんが
@user = User.first
@user.active_relationships
例えば上記@user.active_relationships
のようなメソッドが利用できるようになります(belongs_toにおいても同様です)この事を頭の片隅に置いていて下さい。
##class_name, foreign_key
次にclass_nameとforeign_keyについてです。まずはclass_nameですが
has_many :active_relationships, class_name: "Relationship"
前述のように@user.active_relationships
でactive_relationshipsメソッドを呼び出した際に、class_nameで何も指定していないデフォルトの状態だとrailsは
"ActiveRelationship"テーブルにある、user_idを参照しに行こうとします。しかし、そのようなテーブルは作成していません。
なので、**参照先は"Relationship"テーブルにしてネ。**といった指示をclass_nameでrailsに伝えているような形です。
この後passive_relationship(フォローされる側の関連)も作成する予定です。
has_many :relationshipとしてしまうと名前が重複してしまうので、分かりやすい名前のメソッド(active_relationships)を作成して、参照先はRelationshipテーブルだヨ。と示している訳です。
※(正しくはActiveRelationship model(class)ですが、テーブルと書く方が分かりやすいと思ったのでここではテーブルとしています)
次はforeign_keyです
先程class_nameの説明で、class_nameで何も指定していないデフォルトの状態だと
"Active_relationship"テーブルにある、user_idを参照しに行く
と書きました。class_nameで参照先はRelationshipテーブルと示せたものの、user_idというカラムは用意していません。
ここでなぜrailsはuser_idを参照しに行くのか?なのですが、今回のRelationshipモデルとの関連付けのように、Userクラスでhas_manyとしているとデフォルトではforeign_keyがuser_id(モデル名_id)となっているためです。
例えば投稿モデル(content)と関連付けを行う場合
has_many :contents
とすると思います。これはデフォルトでは下記のようになっています。
has_many :contents
# class_name :"Content"
# foreign_key :user_id
つまり、**Contentテーブルのuser_idを参照して下さいネ。**ということです
Contentテーブルでは外部キーをuser_idとしてカラムを用意し、class_nameもそのままのContentでよいため、デフォルトの挙動に従って何も追加で記述する必要はありませんでした。
今回に関してはRelationshipテーブルを参照しに行く際に、user_idではなく、follower_idを代わりに参照してヨ。ということをforeign_keyでrailsに指示している、ということになります。
以上のことから1行目のコードをまとめます。
has_many :active_relationships, class_name: "Relationship", foreign_key: :follower_id
・Relatinoshipモデルと関連付けをするヨ
・active_relationshipsというメソッドを使えるようにしたけど、呼び出す時の参照先はRelationshipテーブルのfollower_idを参照しに行ってネ。
##through, source
次に2行目です
has_many :followings, through: :active_relationships, source: :following
前述した内容と同様に、has_many :followingsでfollowingsというメソッドが使えるようになりました。
throughとsourceですが**あまり直訳した意味は考えずに、このメソッドがどういう動きをするのか?**を理解した方が簡単ですので、メソッドの動き方で理解していきましょう。
まず目標としては、has_manyで作成したfollowingsというメソッドを使って、コントローラーなどで@user.followingsとしただけで、そのユーザーがフォローしているユーザーのデータが手に入る。というような状況を実現したいです。
しかし、現時点でも上記のようなデータを取得することは可能です。
・rails consoleで下記のようなコードを入力してみます
user = User.first
user.active_relationships # > そのユーザーがフォローしているユーザーidの集合を取得
user.active_relationships.first.following # > そのユーザーがフォローした1人目のユーザーのデータを取得
# ↓ user.followingsと同じ動き
user.active_relationships.map(&:following) # > そのユーザーがフォローしたユーザーのデータの集合を取得
# mapメソッドは 配列の要素それぞれに、与えられた処理を実行して新しい配列を作り出す
# &(アンパサンド)は mapで渡された配列一つ一つにメソッドを実行する
# 例えば user.contents.map($:body)などとすると、ユーザーが投稿したbodyカラムの内容だけを集めた配列が新しく作られる
結果user.active_relationships.map(&:following)
でもフォローしているユーザーのデータは取り出せるのですが、これだとコードが長くなり、今後何度も呼び出すとなるとコードが見づらくなってしまいます。
そこで、話を戻しましてhas_manyで作成したfollowingsメソッドに戻ります。
throughもsourceも上記のrails consoleで実行した内容が理解できると分かり易いのですが
through: :active_relationships
1行目のhas_manyで作成した、active_relationshipsメソッドを呼び出し
source: :following
map(&:following)
と同じように、それぞれの要素にfollowingメソッドを呼び出しているのです。
※(followingメソッドとは何かは、後述するRelationshipモデル側の関連付けの部分で説明します)
これでfollowingsメソッドでそのユーザーがフォローしているユーザーのデータの集合が取得できるようになり、user.followings
で簡単にデータの取得が可能になりました。
(名前が似ているのでややこしいですが、followingsメソッドとfollowingメソッドは別物です)
2行目をまとめます。
has_many :followings, through: :active_relationships, source: :following
・throughでactive_relationshipメソッドを呼び出して、Relationshipテーブルから自分がフォローしているユーザーのidの集合を取得するヨ
・sourceで、throughで取得したデータにそれぞれfollowingメソッドをかけて具体的なユーザーの情報を持った配列にして、followingsメソッドにそのデータが返すヨ
・followingsメソッドを呼び出すことで自分がフォローしているユーザーの情報を集合で取得できるようになったヨ
##Relationshipモデル
Relationshipモデル側の関連付け
class Relationship < ApplicationRecord
belongs_to :following, class_name: "User"
belongs_to :follower, class_name: "User"
end
先程まではUserモデル側の関連付けを行なってきましたが、考え方としては前述した内容とさほど変わりありません。
belongs_to :following
という記述でfollowingメソッドが使えるようになり、class_name :"User"
でUserモデルと1対多の関連付けを行っています。
※ここで使えるようになったfollowingメソッドが、先程後述説明するとしました、followingメソッドです。
つまり先程のfollowingsメソッドの動きのイメージとしましては
① throughの部分でactive_relationshipsメソッドが呼び出されて、UserモデルからRelationsihpモデルへ(foreign_key :follower_idで)参照を行い、フォローしているユーザーのidの集合を取得します。
② 今度はsourceの部分で、①で取得したidの集合(配列)にfollowingメソッドがそれぞれ実行されて、RelationshipモデルからUserモデルへ(foreign_key :user_idで)参照を行います。
最後の部分はなかなかイメージが難しいかもしれませんが、rails consoleでコマンドを打って確認すると、それぞれの動き方や取得できるデータの内容が分かって理解し易くなると思います。
##まとめ
フォロー機能の関連付けだけをまとめると
・Userクラスのhas_manyで、Userモデルのuser_idとRelationshipモデルのfollower_idが紐づいている
・Relationshipクラスのbelongs_toで、Relationshipモデルのfollower_idとUserモデルのuser_idが紐付いている
ということになります。理解するためのコツは、has_manyやbelongs_toはメソッドを生成するということを頭に置いて考えることだと思いました。
(フォローされているユーザーとの関連付けは、答えのコードもありますし、この内容と逆のことをすればいいので、片方理解ができればそこまで難しくないと思うので省略させていただきます。)
Rails初学者がまとめた物なので至らない点もあるかと思います、気づいた点などあればコメントいただけると幸いです。ありがとうございました!