【Railsチュートリアル】フォロー機能のアソシエーションを解説してみる
みなさん一度くらいユーザーのフォロー機能を実装したくなったことありません?ありますか。奇遇ですね僕もです。
と、いうわけで今回の記事ではフォロー機能の実装とその解説をしたいと思います。
今回は実際のコードを見ながら説明をしていこうと思うので、実際にrailsでコードを書いたことがある人向けの内容になります。
全くコードを書いたことないんだけど!アソシエーションって何やねん!という方、なんとびっくり偶然にも僕の前回の記事がたまたまコードを書かずにアソシエーションの概念について説明しているのでそちらを見ていただければ幸いです。是非。
今回の目的
さてさて今回の記事の目的ですが、
- フォロー機能のためのアソシエーション(とそのコードの意味)の理解
とします。機能や実装方法については調べればこれでもかと出てくるので色々調べてみてください。
また今回の記事ではコードの曖昧さ回避の意味も兼ねて Ruby on Rails チュートリアル:実例を使って Rails を学ぼう (version 5.1)の14章、 14.1.1 データモデルの問題 (および解決策) から 14.1.5 フォロワー についての説明に絞ろうと思います。
railsチュートリアルをやったことがない人にもわかりやすいよう書くつもりですが、もしよくわからない部分があれば本家サイトの方も参照してみてください。
コード
Railsチュートリアルに沿っていればおおよそこんな感じになってるはずです。
# app/models/relationship.rb class Relationship < ApplicationRecord belongs_to :follower, class_name: "User" belongs_to :followed, class_name: "User" end # app/models/user.rb class User < ApplicationRecord has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy has_many :passive_relationships, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy has_many :following, through: :active_relationships, source: :followed has_many :followers, through: :passive_relationships, source: :follower end
今回はフォロー機能についての各モデルのアソシエーションを中心に解説するので一部余分なコード(Userモデルのmicropost
関連など)は省いています。
解説
それでは早速見ていきましょう。
そもそも
はい、いきなりコードの話から脱線するんですけど、そもそも今回のフォロー機能ではUserモデルからUserモデルに対しての多対多のアソシエーションを行っています。
前回の記事で言う所の「A組の学生」モデルと「B組の学生」モデルがどっちもUser
になっている状態です。
つまり、Userモデルは自分自身のインスタンスからその他の自分自身のインスタンスに対してアソシエーションを行わなければならないわけです。カオス。
これを解決する為に、has_many :through
アソシエーション(has_and_belongs_to_many
でも可)を使ってUserモデルの間にRelationshipモデル(中間モデル)を置きます。
前回の記事に習って書くと、今回実現されるモデル構造は以下のようになります。
図1
ただ実際にはUserモデルは一つだけなので、今回の記事では以下のような図を使って解説していきたいと思います。
図2
ちなみに今回の「フォロー機能」では図のように3つのアソシエーション(×2)が行われています。 ((×2)としたのはそれぞれに対して「フォローしている」アソシエーションと「フォローされている」アソシエーションの2種類が発生するからです。)
図の中の番号はこの後の解説の順番を示しているだけで深い意味はないのでそんなに気にしないでください。
コードの読み方
Relationshipモデル
それではまず比較的シンプルなRelationshipモデルから。図2で言う①にあたる部分です。
# app/models/relationship.rb class Relationship < ApplicationRecord belongs_to :follower, class_name: "User" belongs_to :followed, class_name: "User" end
図1からもわかるようにRelationshipモデルはUserモデルに対して常に1対1の関連を持っており、かつUserモデルに所属しているので使うアソシエーションはbelongs_to
です。
ではこのbelongs_to
ですが、カンマ(,)区切りで見てみるとそれぞれ二つの情報を取得していることがわかります。
まず一つ目の情報(:follower
, :followed
)ですが、これらはRelationshipモデルが関連を持つ先のモデルを示します。しかし上の図を見るとRelationshipモデルが関連を持つ相手はUserモデルだけですよね。follower
やfollowing
といったモデルはありません。そこで活きてくるのが二つ目の情報、class_name:
です。
class_name:
をつけることで、関連先のモデルを参照する際の名前を変更することができます。
上のコードでのbelongs_to :follower, class_name: "User"
という記述がありますが、日本語的に意訳すると
「Userモデルに対してfollower
って名前でbelongs_to
の関連付けしときますねー」
という意味になります。
なぜこんなことをするのかというと、あとで解説しますが今回実はRelationshipモデルはUserモデルに対して二つの関連付けをしたいんです。それなのに関連先として使える名前が"User"だけだと、二つの関連付けの見分けがつきませんね。そこでclass_name
で本当の名前を指定することで関連先の名前を変更できるというわけです。
この関連付けを図にすると以下のようになります。
図3
Userモデル
続いてUserモデルについてです。
# app/models/user.rb class User < ApplicationRecord has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy has_many :passive_relationships, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy has_many :following, through: :active_relationships, source: :followed has_many :followers, through: :passive_relationships, source: :follower end
Relationshipモデルより少し複雑に見えます。いや実際複雑なんですけども。
ここではhas_many
とhas_many :through
という大きく二つの関連付けがなされています。ひとつずつ見ていきましょう。
has_many
図2で言う②の部分ですね。以下の一文について考えてみましょう。
has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy
has_many
はカンマ区切りで見ると四つの情報を取得していることがわかります。このうちclass_name: "Relationship"
まではRelationshipモデルのbelongs_to
で説明したものと同じ内容になります。
ではforeign_key
とdependent
ですが、それぞれ以下のような意味になります。
foreign_key
関連先モデルの外部キーを指定する。dependent
自モデルと関連先モデルの依存性を指定する。
foreign_key
についてですが、Relationshipモデルに生成されたインスタンスはfollower_id
や followed_id
などいくつかのデータ(カラムといいます)を持ちます。その中で、アソシエーションによって参照するカラムのことをforeign_key
(外部キー)として指定しています。
dependent
では自モデルと関連先モデルの依存性を指定します。destroy
を指定すると自モデルのインスタンスが削除された際にその関連先モデルのインスタンスも自動的に削除されるようになります。
つまり、ここでのhas_many
アソシエーションを日本語的に意訳すると、
「Relationshipモデルに対してfollower_id
を見てactive_relathionships
って名前でアソシエーションしときますねー。あ、あと関連するUserインスタンスが消えた時は対応するactive_relationships
インスタンスも消しとくんでよろしくー。」
みたいな感じになります。
この関連付けを図にすると以下のようになります。
図4
has_many :through
最後に図2で言う③の部分です。以下の一文について考えてみます。
has_many :following, through: :active_relationships, source: :followed
ここで設定されている情報は以下のようになっています。
through
経由するモデル名を指定する。source
関連先モデル名を指定する。
前回の記事でも触れているんですが、has_many :through
ではアソシエーションを行うモデル間を中継する中間モデルが存在します。その中間モデル名をthrough
で指定していますね。今回の例だと、has_many
で関連付けしたactive_relationships
が指定されています。
またこのアソシエーションでも最終的な関連先モデルの名前をfollowing
に変更していますが、has_many
で指定していたclass_name
が見当たりません。代わりにsource
で関連先のモデルを指定するんですが、ここで指定するのはUser
ではなくfollowed
であることに注意が必要です。
このアソシエーションによって行う関連付けはUser→Relationship→Userですが、Relationshipモデルでのアソシエーションで説明した通りRelationship→Userではfollower
とfollowed
の2つの関連付けが行われています。そのような経緯で今回の場合だとsource
ではUser
ではなくfollower
かfollowed
を指定しなければならないため、follower
を指定しているということになります.
つまり、ここでのhas_many :through
アソシエーションを日本語的に意訳すると、
「active_relationships
モデルを経由した上でfollowed
モデルに対してfollowing
って名前でアソシエーションしときますねー」
といった感じになります。
この関連付けを図にすると以下のようになります。
図5
まとめ
最終的に今回のアソシエーションを全て一つの図に表すと以下のようになります。
図6
なんとなくでもユーザーをフォローするためのアソシエーションが理解できたでしょうか。
今回の例はアソシエーションを考える上でまだ比較的簡単な方かと思いうので、ぜひいろんなアソシエーションにチャレンジしてみてください。