😽

フォロー/フォロワー機能

2023/08/01に公開

実装機能
・relationshipsコントローラ(create, destroy)
・relationshipsモデル
・フォロー数 / フォロワー数の表示(ビュー)
・フォローボタン(ビュー)
・フォロー一覧 / フォロワー一覧(ビュー)

前提
・deviseを使用
・userモデルを作成済み

step1 モデルの作成

relationshipsモデル

カラム データ型 詳細
id 初期カラム 主キー
follower_id integer フォローするユーザーのid
followed_id integer フォローされるユーザーのid

・フォロー vs フォロワーは多対多の関係なので、中間テーブルの作成が必要になる。
・relationshipsモデルは、中間テーブル。
 フォロー(ユーザー) vs relationship
 フォロワー(ユーザー) vs relationship
は、1対多の関係になる。

$ ralis g model relationship follower_id:integer followed_id:integer

また、migrationファイルを開き、timestampの下に

index :relationships, [:follower_id, :followed_id], unique: true

を追記する。
※これは同一のユーザーに対して複数のフォローを作成しないための制約です。

$ rails db:migrate

step2 モデル間のアソシエーションを作成

フォロー(ユーザー) vs relationship
フォロワー(ユーザー) vs relationship
は、1対多の関係。

そのため、relationshipモデルには以下のように記載する。

models/relationship.rb
  belongs_to :follower, class_name: "User"
  belongs_to :followed, class_name: "User"

・follower や、followed は、勝手に便宜上命名したもの。
 本来はuserモデルなので、class_name: "User" の記述を忘れずに。

userモデルには以下のように記載する。

models/user.rb
  has_many :relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy
  has_many :reverse_of_relationships, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy
  
  has_many :followings, through: :relationships, source: :followed
  has_many :followers, through: :reverse_of_relationships, source: :follower

以下、解説。

models/user.rb
has_many :relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy

この部分は、図中の青部分と黄色部分の関係性を表しています。
方向性は赤い矢印の向きです。

user は多くのrelationshipsを持っています。
user と relationshipsを紐づける外部キー(foreign_key)は follower_idです。

models/user.rb
has_many :reverse_of_relationships, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy

この部分は、図中の緑部分と黄色部分の関係性を表しています。
方向性は赤い矢印の向きです。

user は多くのrelationshipsを持っています。
ただ、先に出たrelationshipsとは矢印の向きが逆なので、便宜上、reverse_of_relationshipsと名づけました。
user と reverse_of_relationshipsを紐づける外部キー(foreign_key)は followed_idです。

models/user.rb
has_many :followings, through: :relationships, source: :followed
# has_many :自身がフォローしている全ユーザー, through: :スルーするモデル, source: :参照元となるモデル

この部分は、has_manyとは少し違います。
has_many throughという関連付けです。

https://railsguides.jp/association_basics.html#has-many-through関連付け

*この部分のおかげで、 user.following_users で「自身がフォローしている全ユーザー」を取得できます。

models/user.rb
has_many :followers, throuhg: :reverse_of_relationships, source: :follower

*この部分のおかげで、 user.followers_users で「自身の全フォロワーユーザー」を取得できます。

step3 メソッドの作成

ユーザーモデルにメソッドを作成します。
ここで作成したメソッドは、後にrelationshipsコントローラやビューで使用します。

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

以下、解説です。

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

この部分は、引数のuserをフォローする時のメソッドです。
relationshipsモデルでデータをcreateしています。
followed_idは user.idです。
follower_idは この followメソッドを呼び出し元のuser.idです。

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

この部分は、引数のuserのフォローを外す時のメソッドです。
relationshipsモデルでデータをdestroyしています。

 def following?(user)
   followings.include?(user)
 end

この部分は、引数のuserをフォローしているかどうか?を確認するためのメソッドです。
このメソッドのおかげで、ビューで「このuserをフォローしている場合は、『フォロー解除ボタン』を表示」みたいな記述ができます。

step4 ルーティングの設定

config/routes.rb
  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

relationshipsは userモデルに紐づいています。
そのため、userにネストします。
relationshipsの idを urlに使ったビューは必要ないので、resource にします。

step5 relationshipsコントローラの作成

$ rails g controller relationships

上記コマンドを実行後、以下のようにコントローラを記述

relationships_controller.rb
  def create
    user = User.find(params[:user_id])
    current_user.follow(user) #userモデルに記述したメソッド
    redirect_to request.referer
  end
  
  def destroy
    user = User.find(params[:user_id])
    current_user.unfollow(user) #userモデルに記述したメソッド
    redirect_to request.referer
  end
  
  def followings
    user = User.find(params[:user_id])
    @users = user.followings #userモデルに記述したアソシエーション
  end

  def followers
    user = User.find(params[:user_id])
    @users = user.followers #userモデルに記述したアソシエーション
  end

step6 ビューの作成 (User info部分)

次に、この部分のビューを作成する

users/_info.html.erb
<table class='table'>
  <tr><%= image_tag user.get_profile_image, size:'100x100' %></tr>
  <tr>
	  <th>name</th>
	  <th><%= user.name %></th>
  </tr>
  <tr>
	  <th>introduction</th>
	  <th><%= user.introduction %></th>
  </tr>
  <tr>
    <th>Follows</th>
    <th>
      <%= link_to user.followings.count, user_followings_path(user) %></th>
  </tr>
  <tr>
    <th>Followers</th>
    <th><%= link_to user.followers.count, user_followers_path(user) %></th>
  </tr>
  </table>

<div class='row'>
  <% if user != current_user %>
    <%= render 'relationships/btn', user: user %>
  <% else %>
    <%= link_to edit_user_path(user), class: "btn btn-outline-secondary btn-block edit_user_#{user.id}" do %>
      <i class="fas fa-user-cog"></i>
    <% end %>
  <% end %>
</div>

render部分

relationships/_btn.html.erb
  <% if current_user != user %>
    <% if current_user.following?(user) %>
      <%= link_to "フォローを外す", user_relationships_path(user.id), method: :delete, class: "btn btn-danger" %>
    <% else %>
      <%= link_to "フォローする", user_relationships_path(user.id), method: :post, class: "btn btn-success" %>
    <% end %>
  <% end %>

step7 ビューの作成 (Users部分)

次に、このビューを作成する

users/_index.html.erb
<table class='table'>
  <thead>
    <tr>
      <th>image</th>
      <th>name</th>
      <th colspan="3"></th>
    </tr>
  </thead>
  <tbody>
    <% users.each do |user| %>
      <tr>
        <td><%= image_tag user.get_profile_image, size: '50x50' %></td>
        <td><%= user.name %></td>
        <td><%= render "relationships/btn", user: user %></td>
        <td><%= link_to 'Show', user, class: "user_#{user.id}" %></td>
      </tr>
    <% end %>
  </tbody>
</table>

step8 ビューの作成 (Follow Users部分)

次に、フォロー中のユーザー一覧を作成します。

<h2>Follow Users</h2>
<% if @users.exists? %>
  <%= render 'users/index', users: @users %>
<% else %>
  <p>ユーザーはいません</p>
<% end %>

step9 ビューの作成 (Follower Users部分)

次に、フォロワー一覧を作成します。

<h2>Follow Users</h2>
<% if @users.exists? %>
  <%= render 'users/index', users: @users %>
<% else %>
  <p>ユーザーはいません</p>
<% end %>

完成です!

追記

relationshipモデルに

validates :follower_id, uniqueness: { scope: :followed_id }

こちらのバリデーションを追加しました。
※同一のユーザーに対し複数のフォローを作成しないようにするためです。

参照

https://zenn.dev/goldsaya/articles/61c33fec6ddf8a

https://qiita.com/nakachan1994/items/e6107fe3003f6515e385

https://www.youtube.com/watch?v=gATeEnr8gh4&ab_channel=FarStep【プログラミング講座】

Discussion