はじめに
本記事は、Railsの学習を始めて1ヶ月の初学者が、学習を進めていて疑問に思った点について調べた結果を備忘録も兼ねてまとめたものです。
そのため、記事の内容に誤りが含まれている可能性があります。ご容赦ください。
間違いを見つけた方は、お手数ですが、ご指摘いただけますと幸いです。
今回の疑問点
今回の疑問点は、
フォロー/フォロワー機能のアソシエーションについて
です。
現在、Twitterクローンを作成しており、フォロー/フォロワー機能の実装の際に上記について疑問を抱きました。
疑問点についての解説
###いいね機能実装時との比較
おそらく、多くの方がフォロー/フォロワー機能実装の前にいいね機能の実装に取り組んでいることと思いますので、いいね機能と比較して確認していきます。
いいね機能のアソシエーションの記述
Favoriteモデルの記述
class Favorite < ApplicationRecord
belongs_to :user # FaviriteモデルとUserモデルの1対1の関係
belongs_to :tweet # FavoriteモデルとTweetモデルの1対1の関係
end
Userモデルの記述
class User < ApplicationRecord
has_many :tweets # UserモデルとTweetモデルの1対多の関係
has_many :favorites # UserモデルとFavoriteモデルの1対多の関係
has_many :favorite_tweets, through: :favorites, source: :tweet # UserモデルとTwitterモデルの多対多の関係(Favoriteモデル経由)
end
Tweetモデルの記述
class Tweet < ApplicationRecord
belongs_to :user # TweetモデルとUserモデルとの1対1の関係
has_many :favorites # TweetモデルとUserモデルとの1対多の関係
end
##### 各モデルの関係
上記の通り記述することで各モデルを以下の通り関連付けています。
〜〜〜Userモデル〜〜〜 ⇄ 多対多 ⇄ 〜〜〜Tweetモデル〜〜〜
⇅1対1及び1対多 ⇅1対1及び1対多
〜〜〜〜〜〜〜〜〜〜〜〜〜〜Favoriteモデル〜〜〜〜〜〜〜〜〜〜〜〜〜〜
各モデルを頂点とした三角形の相互関係となっています。
####フォロー/フォロワー機能のアソシエーションの記述
記述を見る前に各モデルの関係性から確認します。
フォロー/フォロワー機能の場合には以下の通りとなります。
〜〜〜〜〜Userモデル〜〜〜〜〜
⇅ 1対多の関係
〜〜〜〜Relationshipモデル〜〜〜〜
このまま、実装しようとしてもフォローする側のユーザーとフォローされる側のユーザーを区別することができないため、うまくいきません(フォロー数とフォロワー数を表示したいのにフォロー数とフォロワー数の合計しか出せない等)。
うまく実装するためには、先程のいいね機能の時のように三角形の関係を作ってあげる必要があります。
そこで以下のように記述します。
Relationshipモデルの記述
class Relationship < ApplicationRecord
belongs_to :following, class_name: "User" # RelationshipモデルとUserモデル(folloeing)との1対1の関係
belongs_to :follower, class_name: "User" # RelationshipモデルとUserモデル(follower)との1対1の関係
end
上記のように記述することで、Userモデルを便宜上following(いいね機能での「いいね」するUser)とfollower(いいね機能での「いいね」されるTweet)の2つに分けることができました。
ここで注目していただきたいのが、class_name: "User"
という記述です。
class_name: "User"
は、参照先のモデルクラス名を指定するために記述しています。
なぜ指定しなければならないかというと、関連名に基づいて参照先のモデルクラス名をRailsが推測しているからです。
今回は関連名にUserモデルのクラス名user
を使わず、following
及びfollower
としているため、Railsによる関連名からの参照先モデルの推測ができないため、明示してあげる必要があります。
Userモデルの記述
class User < ApplicationRecord
#フォローする側のUserから見て、フォローされる側のUserを(Relationshipを経由して)集める。なのでfollowing_id(フォローする側)
has_many :active_relationships, class_name: "Relationship", foreign_key: :following_id
# Relationship(active_relationshipsを経由して「follower」モデルのUser(フォローされた側)を集めることを「followings」と定義
has_many :followings, through: :active_relationships, source: :follower
#フォローされる側のUserから見て、フォローしてくる側のUserを(Relationship経由で)集める。なのでfollower_id(フォローされる側)
has_many :passive_relationships, class_name: "Relationship", foreign_key: :follower_id
# Relationship(active_relationshipsを経由して「following」モデルのUser(フォローする側)を集めることを「followers」と定義
has_many :followers, through: :passive_relationships, source: :following
end
続いてuser.rbファイルに上記の通り記述します。
has_many :active_relationships, class_name: "Relationship", foreign_key: :following_id
has_many :passive_relationships, class_name: "Relationship", foreign_key: :follower_id
の2つの記述は、いいね機能のuser.rb及びtweet.rbファイルの
has_many :favorites
に当たります。
いいね機能の際にはFavoriteモデルに対してUserモデルとTweetモデルからひとつずつ矢印が伸びていましたが、今回は、Userモデルのfollowing(いいね機能での「いいね」するUser)とfollower(いいね機能での「いいね」されるTweet)からひとつずつ矢印が伸びることになるのでUserモデルに2通り記述します。
2通り書く際に両方の関連名をrelationships
と同名にすることはできませんので、relationship.rbの時と同様に関連名を変えていきます。
今回は
has_many :active_relationships, class_name: "Relationship"
has_many :passive_relationships, class_name: "Relationship"
としています。
文の最後に
foreign_key: :following_id
foreign_key: :follower_id
とありますが、これは外部キー(foreign key)を指定するための記述です。
指定する理由は、class_name
による参照先モデル名の指定同様、関連名がモデル名の複数形(relationships)ではないため、Railsが推測をすることができないからです。
本来、関連名→モデル名→外部キーと関連付けられているものが、関連名を変更したことにより、関連性がなくなっています。
また、外部キーの名前は原則、親モデル名_id
とされており、この通り命名することで親モデルと外部キーが関連付けられます。しかし、今回はfollowing_id、follower_idであり同名のモデルは存在しませんので親モデルを推測することはできません。(※relationship.rbにおいて便宜上Userモデルをfollowingとfollowerの2つに分けていますが、実際にUserモデルが2つになったわけではないことにご注意ください。)
ここで、なぜ同様に関連名を変更したrelationship.rbにおいて外部キーを指定していないのか疑問に思う方もいらっしゃると思います。
これは、Relationshipモデルを親モデルとする外部キーが他のモデルに設定されていないからです。
(もし、外部キーが設定されていれば、同様に外部キーを指定する必要があるでしょう。)
続いて以下の記述について解説します。
has_many :followings, through: :active_relationships, source: :follower
has_many :followers, through: :passive_relationships, source: :following
上記の記述は、いいね機能のuser.ebの
has_many :favorite_tweets, through: :favorites, source: :tweet
に当たります。
has_many, through
はthrough
の後に記述したモデルを経由してデータを取得できるようにします。今回の場合は、Userモデル(following又はfollowers)からRelationshipモデル(active_relationships又はpassive_relationships)を経由してUserモデル(following又はfollowers)のデータを取得することができます。(※Relationshipモデルは便宜上、active_relationshipsとpassive_relationshipsに分かれていますが、実際に2つに分かれたわけではないことにご注意ください。Relationshipモデル自体は1つのままです。)
本記述は、フォロー/フォロワー一覧画面を作成する際に必要になります。
Relationshipモデルを経由しますが、実質的にはUser(following)モデルとUser(follower)モデルとの多対多の関連付けをしています。
また、今回も関連名が変更されていますので、参照先モデルを指定しなければなりません。
指定のための記述が、
source: :follower
source: :following
になります。
先ほどまでは、class_name
で指定していましたが、ここではsource
を使います。
class_name
とsource
はどちらも参照先モデルを指定するために使用しますが、class_name
は1対多の関係の際にsource
は多対多の関係の際に使用します。
has_many
は1対多の関係ですが、has_many, through
は多対多の関係になりますので、ここではsource
を使います。
上記の記述により、Relationshipモデルを経由したデータの取得が可能になるため、
本来は、user.rbファイルにfollowed_by?の定義をする必要がありますが、ここでは割愛いたします。
各モデルの関係
上述の通り設定をすると各モデルの関係性は以下の通りとなります。
Userモデル{〜〜〜(following)〜〜〜 ⇄ 多対多 ⇄ 〜〜〜(follower)〜〜〜}
⇅1対1及び1対多 ⇅1対1及び1対多
〜〜〜〜〜〜〜〜〜〜〜〜〜〜Relationshipモデル〜〜〜〜〜〜〜〜〜〜〜〜〜〜
いいね機能と同様に三角形の関係となっております。
上記のようになれば、フォローする側のユーザーとフォローされる側のユーザーが区別できるため、うまく実装できるのではないでしょうか。
まとめ
長くなってしましましたので最後にポイントをまとめます。
-
User同士を関連付ける必要があり、いいね機能と同じやり方では、フォローする側とされる側を区別できない。区別するためには、擬似的にUserモデルとRelationshipモデルを2つに分ける必要がある。
-
関連名を変更する場合には、
class_name
又はsource
で参照先モデルを指定する必要がある。また、必要があれば、foreign_key
で外部キーも指定する必要がある。 -
フォロー/フォロワー一覧画面を作成するためには、
has_many, through
を使ってUser(following)とUser(follower)の多対多の関連付けをする。