フォロー機能のアソシエーション備忘録(Userモデルの記述を理解する)


 目次

 はじめに

ここ数日間、フォロー機能のアソシエーションを理解するのにもがき苦しんだわけですが、やっと自分の中に落とし込むことができたので備忘録として残します。

個人的にUserモデルへのアソシエーションの記述のところで頭がごちゃごちゃになって詰まったので、そこを中心に書いています。ですので今回は、viewやcontrollerの実装に関しては触れていません。

ということで早速始めます。

 多対多なので中間テーブルを作成

フォロー機能のアソシエーションは、多対多(user対user)のアソシエーションなので、
中間テーブルを用意して「一対多」(user対relationships)のアソシエーションにしていく必要がある。

(一対多とか多対多とかなんのこっちゃっという方には【初心者向け】丁寧すぎるRails『アソシエーション』チュートリアル【幾ら何でも】【完璧にわかる】の記事がオススメです。私もこれを何回も見て、学習しました。)

ということでまずはrelationshipモデルを作成する。

$ rails g model Relationship

マイグレーションファイルに、following_id(フォローする側のid)とfollower_id(フォローされる側のid)などを追加する。

202008111333110_create_relationships.rb
class CreateRelationships < ActiveRecord::Migration[5.2]
  def change
    create_table :relationships do |t|
      #フォローする側
      t.integer :following_id
      #フォローされる側   
      t.integer :follower_id

      t.timestamps null: false
    end
    add_index :relationships, :following_id
    add_index :relationships, :follower_id    
    add_index :relationships, [:following_id, :follower_id], unique: true
  end
end

忘れずに・・・

$ rails db:migrate

モデルを2つに分けて考える(ここ重要)

ここからは
 ①フォローする側の目線
 ②フォローされる側の目線
この2つをしっかり区別して考える必要がある。
曖昧にすると何が何だか分からなくなるので注意!

まずは・・・
1. Relationshipモデルを2つに分けて考える
・active_relationships (フォローする側の目線)
・passive_relationships (フォローされる側の目線)

user.rb
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

あくまでも、1つのRelationshipモデルをそれぞれ名前を付けて2つのモデルに分けたという“想定”に過ぎないため、本当はRelationshipモデルであることをclass_nameで記載する必要がある。

また、フォローをする側の目線では、フォローする側(following_id)を元にフォローされる側(follower_id)を引っ張ってくるので、primary_key(外部キー)をfollowing_idに指定する必要がある。

逆に、フォローされる側の目線では、フォローされる側(follower_id)を元にフォローする側(following_id)を引っ張ってくるので、primary_key(外部キー)をfollower_idに指定する必要がある。

2. Userモデルを2つに分けて考える
・ following (フォローする側の目線)
・ follower (フォローされる側の目線)

relationship.rb
class Relationship < ApplicationRecord
  belongs_to :following, class_name: "User"
  belongs_to :follower, class_name: "User"
end

Relationshipモデルと同様にあくまでも、1つのUserモデルをそれぞれ名前を付けて2つのモデルに分けたという“想定”に過ぎないため、本当はUserモデルであることをclass_nameで記載する必要がある。

①フォローする側の目線で考える

1行目:Relationshipsモデル(ここではフォローする側目線なのでactive_relationships)について記述
上記ののRelationshipsモデルを2つに分けるところで説明済み。

2行目:active_relationshipを介してフォローされた人を集める
active_relationshipsを通ってフォローされた人を集める。フォローされた人を集めるには、"follower"モデルを参照することになるため、source: :followerを記述する。この一連の流れを"followings"と命名したのでhas_many :followingsと記述する。

user.rb
  has_many :active_relationships, class_name: "Relationship", foreign_key: :following_id
  has_many :followings, through: :active_relationships, source: :follower

②フォローされる側の目線で考える

1行目:まずはRelationshipsモデル(ここではpassive_relationships)について記述
上記ののRelationshipsモデルを2つに分けるところで説明済み。

2行目:passive_relationshipを介してフォローした人を集める
passive_relationshipsを通ってフォローされた人を集める。フォローされた人を集めるには、"following"モデルを参照することになるため、source: :followingを記述する。この一連の流れを"followers"と命名したのでhas_many :followersと記述する。

user.rb
  has_many :passive_relationships, class_name: "Relationship", foreign_key: :follower_id
  has_many :followers, through: :passive_relationships, source: :following

ということでUserモデルへの最終的な記述は以下のようになる。

user.rb
class User < ApplicationRecord
  #フォローする側の目線
  has_many :active_relationships, class_name: "Relationship", foreign_key: :following_id
  has_many :followings, through: :active_relationships, source: :follower

  #フォローされる側の目線
  has_many :passive_relationships, class_name: "Relationship", foreign_key: :follower_id
  has_many :followers, through: :passive_relationships, source: :following
end

これにて終了。

まとめ

フォロー機能を完成させるためには、別途viewやcontrollerの実装が必要です。

とは言ってもフォロー機能実装の山は、「Userモデルの記述を理解すること」にあると実際に自分が経験してみて感じたので、この山を越えればあとは難なく実装できると思います

今回のポイントは、1つのモデルを2つに分けて考えるというところだと思います。

そもそもフォローする人、フォローされる人の両方がUserモデルという1つのモデルを参照してデータを持ってくるという構造が話をややこしくしているので、いいね機能のPostモデルとUserモデルの関係みたいに、Userモデルを(本当は1つのモデルだけど)2つのモデルと想定して関係性を整理すれば、案外シンプルな話なのかもしれないと自分の中に落とし込むことができるのではないかと思います。

ここまで読んでいただきありがとうございました。
まだまだ知識が足りていない初心者なので、どこか突っ込みどころ・誤りなどがあれば是非是非アドバイスいただければ幸いです。