LoginSignup
0
0

Ruby on Rails で フォロー機能を実装してみる

Last updated at Posted at 2024-02-11

はじめに

初学者がわかったつもりで発信しているものです。誤りなどあればご指摘いただけると幸いです。
  • ユーザー管理機能はdevise(Gem)を使用しています
  • Rails 7.0での解説となります
  • ユーザー詳細ページからアクションを起こすを前提条件として解説を行っていきます

今回実装する機能

60e023ff7a30cc84879bc4dacd0fa7e7.gif
フォローボタンを押したらフォロワーカウントが増減する機能を作ってみます

  • フォローするボタンを押したら 表示がフォロー中に切り替わる
  • データベース内にデータが保存されるとカウントされる

仕組みを考える

フォロー機能とは、「ユーザーが→ユーザーを」フォローする機能です。
フォロー機能を実装するうえでのデータベース設計を考えてみましょう

ユーザーが ユーザーをフォローするのでこのような形を連想してしまいます。
40d61425ca23324cc6883f8dd2132133.png

このままではUsersテーブルにUsersテーブルが紐づいてしまいN対Nの関係が生まれてしまいます。
データベース設計の鉄則としてN対Nの関係のテーブルは存在してはいけません

N対Nをさけるためには中間テーブルを用いますよね!

N対N(多対多)?中間テーブル? こちらのサイトで詳しく解説されています

中間テーブルとしてRelationshipsテーブルを作って・・
1e6056a5b99e8c58f068708a255a1bec.png
こうすればRelationshipsテーブルからフォロー中のuser_idとフォロワーのuser_idを参照でき・・

あれ?deviseを使ってユーザー管理を実現できているから Usersテーブルは1つのはず・・
フォロー中のuser_idとフォロワーのuser_idってどう判別するんだ!?

となってしまいます。


ではこうしたらどうでしょうか?
9f7d8afe5f0e73063be4f0350889b6c8.png
誰が誰をフォローしているかRelationshipsテーブルを見れば一目でわかりますよね
この図ではAさんは BさんとCさんをフォローしていることがわかります。

そのためにはFollowingとFollowerのデータをUsersテーブルから参照したいです

アソシエーションを考える

1.中間テーブルを作りましょう

ここでは中間テーブルとしてRelationshipsテーブルで説明を進めます。

rails g model relationships

コマンドで中間テーブルを作ったら

2024xxx_create_relationships.rb
class CreateRelationships < ActiveRecord::Migration[7.0]
  def change
    create_table :relationships do |t|
      t.references :following
      t.references :follower
      t.timestamps
    end
  end
end

2つのカラムを追加しましょう。
これは先述のとおり誰が誰をフォローしているのかを区別するためです。


こうなるように作るには・・

9f7d8afe5f0e73063be4f0350889b6c8 (1).png

RelationshipsテーブルFollowingsテーブルFollwersテーブルの間には一対多の関係ができますよね。
Relationshipsモデル

Relationships.rb
class Relationship < ApplicationRecord
  belongs_to :following
  belongs_to :follower
end

ただ上記の記述では存在しないテーブルを参照されてしまいエラーが起きてしまいます。


なので・・

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

class_nameオプションを使います

  • ? class_nameオプション

参照先のモデル名(FollowingとFollower)が命名規則から外れている場合や関連先のモデルが特定の名前でない場合、class_nameオプションを使用して経路を明確にすることができます。

2. Usersモデルのアソシエーション

フォローする側からのリレーションを考えましょう
Userモデル

user.rb
has_many :following_relationships, class_name: "Relationship", foreign_key: :following_id

上記の記述では
Relationshipsテーブルのfollowingという経路をつたい、foreign_key: :following_idによってuser_idを外部キーとして使用しているといえます。


次にフォローされる側のリレーションです

user.rb
has_many :follower_relationships, class_name: "Relationship", foreign_key: :follower_id

同じ経路ではダメなので:follower_relationshipsforeign_key: :follower_idを変更しています。

仮想的にfollowingとfollowerというテーブルがあるので、それぞれの経路を明記しました
図で表すとこういう感じになります。
1810e0a0f1b8d942201a9c89a00852d6.png


冒頭で紹介したGIF画像のような、カウント増減機能のみでしたら一通りで以上です。
フォロー中一覧やフォロワー一覧を作りたい場合は追加記述をしましょう

続いて、自分が誰をフォローしているのか、誰にフォローされているのかを表現してみましょう。
Userモデル

has_many :followings, through: :following_relationships, source: :follower

自分がフォローしている人を参照するにはどこを見ますか?
source: :followerです。
followingを見ても それは誰がフォローしているのかの集合体なのでfollowerを参照している
という記述になります。

完成コード

user.rb
  has_many :following_relationships, class_name: "Relationship", foreign_key: :following_id
  has_many :followings, through: :following_relationships, source: :follower  /これと/
  
  has_many :follower_relationships, class_name: "Relationship", foreign_key: :follower_id
  has_many :followers, through: :follower_relationships, source: :following  /これを追加/

この記述でできること

show.html.erb
  <% @user.followers.each do |user| %>
    <p class=“f-user”><%= link_to user.name, user_path(user.id) %></p>
  <% end %>

でフォロー中一覧やフォロワー一覧を出力できます

ルーティングを設定する

routes.rbにフォローを可能とするcreateアクションと フォローを解除するdestroyアクションのルーティングを設定しましょう。

routes.rb
  resources :users, only: [:show] do
    resources :relationships, only: [:create, :destroy]
  end

UsersコントローラーRelationshipsコントローラーとでネストを組んでいることに注意してください
ネストを組むことによってRelationshipsコントローラー内でparams[:user_id]を使用して、ユーザーIDを取得し、それを使用してcreateアクション・destroyアクションを実行できるといえます。

コントローラーを設定する

relationships_controller.rb
class RelationshipsController < ApplicationController
  def create
    follow = current_user.following_relationships.new(follower_id: params[:user_id])
    follow.save
    redirect_to user_path(params[:user_id])
  end

  def destroy
    follow = current_user.following_relationships.find_by(follower_id: params[:user_id])
    follow.destroy
    redirect_to user_path(params[:user_id])
  end
end

上記の記述では

  • createアクション→フォローを行うとfollower_idに params[:user_id] が保存されます
  • destroyアクション→フォロー解除を行うとfind_byにてアクションを起こしたparams[:user_id] を見つけてdestroyを行います

改めてルーティングを確認しましょう
5c119e71562bb9a8dd24a7b6d2e897a9.png

show.html.erb
<%= link_to 'フォローする', user_relationships_path(@user.id), data: { turbo_method: :post } %>

<%= link_to 'フォローを解除', user_relationship_path(@user.id), data: { turbo_method: :delete } %>

ルーティング通り、フォローするボタンにcreateアクションのパスを指定して、HttpメソッドとしてPOSTを指定します。
反対に、フォロー解除ボタンにはHttpメソッドDELETEを指定していますので解除も行えます。

これでフォロー機能とフォロー解除機能が実装できました。

続きまして、フォロー中(データベース内にデータがある場合) にビューの表示を変える記述をしていきます。

フォローしているか否かの判定メソッドを定義する

現状ではフォローリクエストを無限に送り放題です。
そのためフォローしているか否かの判定が必要になります。

Userモデル

user.rb
  def followed_by?(user)
    follower =  follower_relationships.find_by(following_id: user.id)
    return follower.present?
  end

followed_by?メソッドの引数に渡されたuserにフォローされているか否かの判定メソッドとなります。

つまり、ビューファイルにて

show.html.erb
  <% if @user.followed_by?(current_user) %>
    <%= link_to 'フォローを解除', user_relationship_path(@user.id), data: { turbo_method: :delete } %>
  <% else %>
    <%= link_to 'フォローする', user_relationships_path(@user.id), data: { turbo_method: :post } %>
  <% end %>

<% if @user.followed_by?(current_user) %>の記述では @ usercurrent_user(自分) にフォローされていますか?という意味になります。

followed_by?メソッドより、follower.present?で

trueを返す場合(following_idが存在=フォローしている)→フォロー解除ボタンが表示され、
falseを返す場合(following_idがnil=フォローしていない)→フォローボタンが表示される という仕組みになります。

これでフォローしているか否かでのビューの表示条件分岐が実装できました。

フォロー・フォロワーをカウントしよう

これまでに比べるとひじょーーーうに簡単です

show.html.erb
  <div class='follow'>
    フォロー <%= @user.following_relationships.count %> 人
  </div>
  <div class='follower'>
    フォロワー <%= @user.follower_relationships.count %> 人
  </div>

ユーザー情報が入ったテーブルのレコード数をカウントすればいいだけです。

最後に

以上がフォロー機能の実装の解説でした。
ここまで読んでいただきありがとうございました。

フォロー機能に関しては、class_name?中間テーブルってなんだっけ?アソシエーションむず!!の繰り返しでした。

自分なりの解釈でまとめてみましたが 認識・解釈に誤りや 前提条件の不足などありましたらご指摘いただければ幸いでございます。

なんせ初投稿ですから見にくい箇所もあったと思います。アドバイス等いただけたら嬉しいです!

参考にさせていただいたサイト

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0