3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

【Rails6】友達登録機能(ユーザーフォロー機能)の実装方法

Last updated at Posted at 2021-03-20

※2022年から技術系の記事は個人ブログに投稿しております。ぜひこちらもご覧ください→yamaday0u Blog

Railsで友達登録機能を実装する方法を紹介します。
※あくまで業界未経験からのエンジニア転職を目指しているぼくが実際にオリジナルアプリに実装した1つの例です。

目次

  • 前提条件
  • 友達登録機能の仕様
  • 動作イメージ
  • 実装手順1:Modelの作成
    • Relationshipsテーブルの作成
    • アソシエーションとバリデーションの定義
    • メソッドの定義
  • 実装手順2:コントローラーの作成
  • 実装手順3:ルーティングを設定
  • 実装手順4:Viewの作成

前提条件

  • Rails 6.0.3.5
  • deviseなどでユーザー管理機能を実装済み

友達登録機能の仕様

TwitterやInstagramのように一方的にフォローするようなものではなく、Facebookのようにお互いにフォローし合うことで友達としてつながるようにします。

動作イメージ

リクエストボタンをクリックすることで相手をフォローしている状態になります。
Image from Gyazo
相手からフォローされている状態で、相手をフォローすることで友達(GIFイメージのアプリでは"Mate"と呼んでいます)になります。
Image from Gyazo

実装手順1:Modelの作成

Relationshipsテーブルの作成

ターミナル
$ rails g model relationship

生成されたマイグレーションファイルに以下のように記述します。

db/migrate/2021XXXXXXXXX_create_relationships.rb
class CreateRelationships < ActiveRecord::Migration[6.0]
  def change
    create_table :relationships do |t|
      t.integer :follower_id # フォローしている側のユーザー (active relationship)
      t.integer :followed_id # フォローされている側のユーザー(passive relationship)
      t.timestamps
    end
    add_index :relationships, :follower_id
    add_index :relationships, :followed_id
    # 保存するデータの組み合わせが一意になるよう設定
    add_index :relationships, [:follower_id, :followed_id], unique: true

アソシエーションとバリデーションの定義

app/models/user.rb
class User < ApplicationRecord
  # アソシエーションの定義
  # フォローしている側のユーザー (active relationship)
  has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy
  has_many :followings, through: :active_relationships, source: :followed

  # フォローされている側のユーザー(passive relationship)
  has_many :passive_relationships, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy
  has_many :followers, through: :passive_relationships, source: :follower
has_manyの補足

:active_relationships:passive_relationships関連モデルを指定します。
class_name:関連名と参照元のクラス名を異なるものにしたい場合に指定します。
foreign_key:参照元のテーブルに定義されている外部キーの名前を指定します。
dependent:値を:destroyにすると、参照先が削除される場合に参照元もDBから削除します。

app/models/relationship.rb
class Relationship < ApplicationRecord
  # アソシエーションの定義
  belongs_to :follower, class_name: "User"
  belongs_to :followed, class_name: "User"

  # バリデーションの定義
  validates :follower_id, presence: true
  validates :followed_id, presence: true
  # follower_idとfollowed_idの組み合わせが一意になるようバリデーション
  validates :follower_id, uniqueness: { scope: :followed_id }
end

メソッドの定義

フォロー状態などを確認するメソッドを定義します。

app/models/user.rb
class User < ApplicationRecord
  # アソシエーションの定義(省略)

  # メソッドの定義
  # ユーザーをフォロー
  def follow(other_user)
    active_relationships.create(followed_id: other_user.id)
  end

  # ユーザーをアンフォロー
  def unfollow(other_user)
    active_relationships.find_by(followed_id: other_user.id).destroy
  end

  # 相手をフォローしていればtrueを返す
  def following?(other_user)
    active_relationships.find_by(followed_id: other_user.id)
  end

  # 友達(互いにフォローしている)をデータベースから取得
  def matchers
    followings & followers
  end

  # 相手と友達になっていればtrueを返す
  def matchers?(other_user)
    active_relationships.find_by(followed_id: other_user.id) && passive_relationships.find_by(follower_id: other_user.id)
  end

  # 自分はフォローしていない&相手からフォローされていればtrueを返す
  def follow_request?(user, other_user)
    !user.matchers?(other_user) && other_user.following?(user)
  end
end

実装手順2:コントローラーの作成

usersコントローラーはユーザー一覧ページを表示するためのindexアクションと、ユーザー詳細ページを表示するためのshowアクションを定義します。

app/controllers/users_controller.rb
class UsersController < ApplicationController
  def index
    @mates = current_user.matchers
    @users = User.all
  end

  def show
    @user = User.find(params[:id])
  end
end

次にrelationshipsコントローラーを作成します。

ターミナル
rails g controller relationships

作成したrelationshipsコントローラーに以下のように記述します。

app/controllers/relationships_controller.rb
class RelationshipsController < ApplicationController
  def create
    Relationship.create(follower_id: current_user.id, followed_id: params[:user_id])
    redirect_to user_path(params[:user_id]), notice: "Requested successfully!"
  end

  def destroy
    other_user = User.find(params[:user_id])
    current_user.unfollow(other_user)
    redirect_to user_path(params[:user_id]), notice: "Canceld request"
  end
end

実装手順3:ルーティングを設定

relationshipsコントローラーでparams[:user_id]を利用するアクションを定義しているので、relationships_controllerのルーティングはusers_controllerのルーティングの中にネストします。

config/routes.rb
Rails.application.routes.draw do
  resources :users, only: [:index, :show] do
    resources :relationships, only: [:create, :destroy]
  end
end

実装手順4:Viewの作成

  • ユーザー一覧ページ
app/views/users/index.html.erb
<div class="item-list">
  
  <div class="item-list-header">
    <p class="bold">Mate list</p>
  </div>

  <div class="item-list-content">
    <ul>
      <% @mates.each do |user| %>
        <%# 友達(互いにフォローしている)を一覧表示 %>
        <div class="each-item">
          <li>
            <div class="each-item-content">

              <div class="item-content-left">
                <p>Mate<br>image</p>
              </div>

              <div class="item-content-right">
                <p><%= link_to user.name, user_path(user.id) %></p>
                <p><%= user.identity %></p>
              </div>

            </div>
          </li>
        </div>
      <% end %>
    </ul>
  </div><%# End of item-list-content %>

  <div class="item-list-header">
    <p class="bold">User list</p>
  </div>

  <div class="item-list-content">
    <ul>
      <% @users.each do |user| %>
        <%# 自分以外のユーザーを一覧表示 %>
        <% unless current_user.id == user.id %>
          <div class="each-item">

            <li>
              <div class="each-item-content">
                <div class="item-content-left">
                  <p>User<br>image</p>
                </div>
                <div class="item-content-right">
                  <p><%= link_to user.name, user_path(user.id) %></p>
                  <p><%= user.identity %></p>
                  <% if user.follow_request?(current_user, user) %>
                    <p><i class="fas fa-star"></i></p>
                  <% end %>
                </div>
              </div>
            </li>

          </div>
        <% end %>
      <% end %>
    </ul>
  </div><%# End of item-list-content %>
</div>

Viewイメージ
上記コードにない記述もしているViewです。
Image from Gyazo

  • ユーザー詳細ページ
app/views/users/show.html.erb
<div class="form-wrap">
  <div class="form-group">
    <div class="schedule-item">
      <div class="schedule-item-header text-left">
        <p class="bold">User name</p>
      </div>
      <div class="schedule-item-body text-left">
        <p><%= @user.name %></p>
      </div>
    </div>
  </div>

  <div class="form-group">
    <div class="schedule-item">
      <div class="schedule-item-header text-left">
        <p class="bold">Identity</p>
      </div>
      <div class="schedule-item-body text-left">
        <p><%= @user.identity %></p>
      </div>
    </div>
  </div>

  <% if @user.follow_request?(current_user, @user) %>
    <div class="form-group">
      <div class="schedule-item">
        <div class="schedule-item-header text-left">
          <p class="bold"><i class="fas fa-star"></i>You have follow request<i class="fas fa-star"></i></p>
        </div>
        <div class="schedule-item-body text-left">
          <p><%= @user.name %> requests you to be mate</p>
        </div>
      </div>
    </div>
  <% end %>

  <% if current_user.matchers?(@user) %>
    <div class="form-group">
      <div class="schedule-item">
        <div class="schedule-item-header text-left">
          <p class="bold"><%= @user.name %> is your mate</p>
        </div>
      </div>
    </div>
  <% end %>

  <% if current_user.id == @user.id %>
    
  <% else %>
    <% unless current_user.following?(@user) %>
      <div class="actions">
        <%= link_to 'Request to be mate', user_relationships_path(@user.id), method: :post, class:"sign-up-btn" %>
      </div>
    <% else %>
      <div class="actions">
        <%= link_to 'Cancel to your request', user_relationship_path(@user.id), method: :delete, class:"sign-up-btn" %>
      </div>
    <% end %>
  <% end %>

  <% if current_user.id == @user.id %>
    <div class="actions">
      <%= link_to 'Edit profile', edit_user_registration_path, class:"sign-up-btn" %>
    </div>
  <% end %>
</div>

Viewイメージ
上記コードにない記述もしているViewです。
Image from Gyazo

以上、みなさんのアプリ開発の参考になれば幸いです。

参考資料

3
2
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
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?