Zenn
👻

[Rails]フォロー・フォロワー機能(through,中間テーブル,MVCの役割)

2025/03/21に公開

フォロー・フォロワーの考え方


上記図のように、

  • フォローする側も、複数人フォローできる
  • フォローされる側も、複数人からフォローされる
    …ため、「多対多」の関係になる。

「多対多」を回避するために、中間テーブル(relationships)を作成する。
中間テーブル(relationships)は、誰が誰をフォローしているのかという情報を保持する。

つまり、

  • フォローするユーザーのID(follower_id)
  • フォローされるユーザーのID(followed_id)

を格納する。

followerテーブル・followedテーブルは、ユーザーテーブルに名前を付けるだけで、実際には作成しない。
この考え方を踏まえて、モデルを作成する!

モデル

rails g model relationship followed_id:integer follower_id:integer

relationship.rb

followingテーブル・followerテーブルは、ユーザーテーブルに名前を付けるだけで、実際には作成しない。⇒class_name: "User"を付ける。

class Relationship < ApplicationRecord
    belongs_to :follower, class_name: "User"
    belongs_to :followed, class_name: "User"
end

1. through・中間テーブルについて

復習・前回もやったよ

through については下記でもまとめた。(今見返すとあんまり理解できてなかったなあ)
https://zenn.dev/eliri/articles/9f1f62244fdc78


has_many :through関連付けは、他方のモデルと「多対多」のリレーションシップを設定する場合によく使われます。この関連付けでは、2つのモデルの間に「第3のモデル」(joinモデル)が介在し、それを経由(through)して相手のモデルの「0個以上」のインスタンスとマッチします。

以下から引用。Railsガイドのthroughの説明箇所。
https://railsguides.jp/association_basics.html#has-many-through関連付け

through は、中間テーブルを介した向こう側のテーブルのデータを取ってくる

下記の場合は、中間テーブルを通して…という意味になる。

user.rb

  # フォローする側
  # 中間テーブルへ
  has_many :relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy
  # 中間テーブルを通して、フォローされる側テーブルへ
  has_many :followings, through: :relationships, source: :followed

  # フォローされる側
  # 中間テーブルへ
  has_many :reverse_of_relationships, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy
  # 中間テーブルを通して、フォローする側テーブルへ
  has_many :followers, through: :reverse_of_relationships, source: :follower

2. モデルに書くメソッド

ビジネスロジックはモデルで定義する。
ここでのロジックとは「このクラス・モデル(User)が何をできるのか?」という振る舞い

  • モデルはデータをどうするか定義する、ルールを書くというイメージ?
  • コントローラは、データの受け渡しの処理。中間地点。

今回Userモデルに書いたメソッド。

  1. ユーザーの画像の表示、画像なければデフォルト画像を表示
    • Userというデータに関する振る舞いなので、Userモデルに書く
    • Userが持ってるデータをどう処理するかという振る舞い
  2. フォロー機能
    • 自分(Userインスタンス=個々のユーザー)の状態が変わる処理なので、Userモデルの責任。(Fat Models=モデルになるべく責任を持たせる、モデルを太らせる)
    • もしコントローラに書くと、コントローラはrelationships_controller favorites_controller task_comments_controllerなど、機能ごとに増えていくので、Userが何をできるのかが分散してしまう(管理しづらくなる)。UserができることはUserモデルにまとめて書く。
  3. フォロー外す機能
    • 同上。自分が他人をアンフォローする、という「Userの振る舞い」。
  4. フォローされてるかチェック
    • Userが他Userに対してどんな関係を持っているかを見るので、Userに書くのが自然。

user.rb

  def get_profile_image(width, height)
    if profile_image.attached?
      profile_image.variant(resize_to_limit: [width, height]).processed
    else
      'default_profile_image.png'
    end
  end

  def follow(user)
    relationships.create(followed_id: user.id)
  end

  def unfollow(user)
    relationships.find_by(followed_id: user.id).destroy
  end

  def following?(user)
    followings.include?(user)
  end
復習になるリンクまとめ

前回もリンクさせていただいたところ。あやふやだったら読むこと!

前回似たようなこと自分でまとめた
https://zenn.dev/eliri/articles/cbe8beab0e5135

前回参考にさせていただいた記事

https://zenn.dev/airiswim/articles/2215300ede2ff6
https://qiita.com/os1ma/items/25725edfe3c2af93d735


ルーティング

  resources :users, only: [:index,:show,:edit,:update] do
    resource :relationships, only: [:create, :destroy]
    get 'followings' => 'relationships#followings', as: 'followings'
    get 'followers' => 'relationships#followers', as: 'followers'
  end

コントローラー

rails g controller relationships

relationships_controller

class RelationshipsController < ApplicationController
    def create
        user = User.find(params[:user_id])
        current_user.follow(user)
        redirect_to request.referer
    end

    def destroy
        user = User.find(params[:user_id])
        current_user.unfollow(user)
        redirect_to request.referer
    end

    def following
        user = User.find(params[:user_id])
        @users = User.followings
    end

    def followers
        user = User.find(params[:user_id])
        @users = user.followers
    end
end

request.referer とは、直前にアクセスしていたURL(リクエストの元のページ)を返す。


ビュー

<td>
  フォロー数:<%= user.followings.count %>
  <br>
  フォロワー数:<%= user.followers.count %>
</td>
<td>
  <% if current_user != user %>
    <% if current_user.following?(user) %>
      <%= link_to user_relationships_path(user.id), method: :delete do %>
        フォローを外す
      <% end %>
    <% else %>
      <%= link_to user_relationships_path(user.id), method: :post do %>
        フォローする
      <% end %>
    <% end %>
  <% end %>
</td>

参考文献

https://www.youtube.com/watch?v=gATeEnr8gh4&t=499s
https://qiita.com/kuuuuumiiiii/items/f64c5292acff6120270e

Discussion

ログインするとコメントできます