Rialsでのフォロー機能についてのメモです。
ツイッタークローンのフォロー機能のモデルの説明です。
こちらの続きです。
フォロー機能の概要
投稿機能は1対多で、
フォロー機能は、モデルの多対多の関係で成り立ちます。
投稿機能では、ユーザ(1)に対して、投稿(多)という関係が成り立ちました。
フォロー機能では、ユーザ(多)に対して、ユーザ(多)という構造を取ります。
「ユーザって言っても、自分は一人やし、1対多なんじゃないの?」と疑問に思われる方もいらっしゃるかと思いますが、半分合ってます!
前回も使わせてもらいましたが、こちらの記事を参考にしてみてください。めっちゃ分かりやすいです。
https://qiita.com/kazukimatsumoto/items/14bdff681ec5ddac26d1
多対多のモデルの構造の場合、中間にテーブルを新しく作成し、相互のやり取りをするのが一般的です。
全体像
モデル
User(ユーザ)
name:string amail:string password_digest:string
Relationship (中間テーブル)
user:references follow:references
ユーザ同士のフォロー機能なので、中間テーブルをはさむことによって、
モデルの構造はUser-Relationship-Userのようになります。
User(フォローする側)-Relationship-User(フォローされる側)と分けることが出来ます。
もう一つ細かく分けると、
User(フォローする側)-(フォローするための中間テーブル)Relationship(フォローされるための中間テーブル)-User(フォローされる側)
となります。
モデルの構成で表してみると、
User(フォローする側)-(user)Relationship(follow)-User(フォローされる側)
こうなります。
User(フォローする側)-(user)Relationship
User(フォローされる側)-(follow)Relationship
とも捉えることができます。(捉え方多すぎ!笑)
こうすると1対多+1対多となっているように見えてきませんか?
実は、多対多は、中間テーブル(多)を挟んだ1対多+1対多なのです!
モデルの作成
上記の構成を参考に、モデルの作成をしましょう。
マイグレーションファイルでの設定
中間テーブルのモデルの下記のコードのマイグレーションファイルが出来上がっているかと思います。
class CreateRelationships < ActiveRecord::Migration[5.0]
def change
create_table :relationships do |t|
t.references :user, foreign_key: true
t.references :follow, foreign_key: true
t.timestamps
end
end
end
userもfollowもUserテーブルに紐づけたいです。
railsでは、別のテーブル名をモデルのカラム名に設定すると、自動で参照してくれます。なので、t.references :user は、Userテーブルを参照することが出来ます。
t.references :followをUserテーブルに紐づけたい。。
そんなときは、
t.references :follow, foreign_key: { to_table: :users }
こう記述すればfollowをUserテーブルに紐づけることが出来ます!
下記のコードに書き換えます。
class CreateRelationships < ActiveRecord::Migration[5.0]
def change
create_table :relationships do |t|
t.references :user, foreign_key: true
t.references :follow, foreign_key: { to_table: :users }
t.timestamps
t.index [:user_id, :follow_id], unique: true
end
end
end
ここで、
t.index [:user_id, :follow_id], unique: true
というコードを追記しています。unique: trueを記述することで、フォローとフォロワーがごっちゃにならないよにしています。
モデル
モデルのコードを見てみましょう。
まず、中間テーブルから。
class Relationship < ApplicationRecord
belongs_to :user
belongs_to :follow, class_name: 'User'
end
多対多の構造は、細かく見ていくと
1対多+1対多
だと説明しました。
なので、relationshipテーブル(中間テーブル)では、haa_manyではなく、belongs_to でUserテーブルと紐づけてます。Userテーブルで、has_manyでrelationshipテーブルと紐づけることで多対他になります。
また、Userテーブルと各カラムを紐づけたいのですが、このままの状態ではuserカラムしか紐づいていないです。(railsの機能でカラムと同じ名前のテーブルを探しにいくため)
belongs_to :follow, class_name: 'User'
ですが、class_name: 'User'と追加で指定することで、followカラムもUserテーブルを参照することが出来るようになります。
Userテーブルを見てみましょう!
class User < ApplicationRecord
has_many :microposts
has_many :relationships
has_many :followings, through: :relationships, source: :follow
has_many :reverses_of_relationship, class_name: 'Relationship', foreign_key: 'follow_id'
has_many :followers, through: :reverses_of_relationship, source: :user
end
has_many :microposts
は、投稿機能を追加したときに作成したコードです。
下の4つについて見ていきましょう。
has_many :relationships
で、relationshipsテーブルから、データを取得してくることが出来ます。1対多の関係です。
自分がフォローしているユーザを取得します。
railsでは、同じ名前のテーブル・カラムがあると自動で取得してくれるので、RelationテーブルにUserカラムの情報を取得していることになります。
一行飛ばして、
has_many :reverses_of_relationship, class_name: 'Relationship', foreign_key: 'follow_id'
を見てみましょう。
reverses_of_relationship
というのは自由に決められる名前です。
class_name: 'Relationship'
では、Relationshipテーブルからデータを取ってきてるということを表しています。
そして、foreign_key: 'follow_id'
でfollow_idのデータですよ!というようになります。
has_many :reverses_of_relationship, class_name: 'Relationship', foreign_key: 'follow_id'
では、reverses_of_relationshipという名前の箱に、Relationshipテーブルを通して、follow_idのデータが入ると言えます。
では、
has_many :followings, through: :relationships, source: :follow
has_many :followers, through: :reverses_of_relationship, source: :user
end
について見ていきます。
throughとsourceがミソになってきます。
through: :中間テーブル, source: :カラム名
で、中間テーブルに設定されているカラムのデータを引っ張てくることが出来ます。
今回のように、ユーザがフォローしているユーザを取得したければ、中間テーブルを通過することで、
has_many :followings, through: :relationships, source: :follow
は、ユーザ(user)がフォローしているユーザ(followings)を取得
has_many :followers, through: :reverses_of_relationship, source: :user
end
で、ユーザ(User)のことをフォローしているユーザ(followers)を取得することが出来ます。
例えば、ユーザ(user)がフォローしているユーザ(followings)を取得したい場合、
@user = User.find(params[:id])
@followings = @user.followings.page(params[:page])
というような記述の仕方も可能になります。