0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Rails】フォロー機能を実装する

0
Posted at

1. 概要

  • ユーザー同士をフォロー/フォロー解除できる
  • ユーザー詳細(マイページ含む) にフォロー数/フォロワー数とボタンを表示
  • フォロー中一覧 / フォロワー一覧ページを用意
  • JSなし:button_toのPOST/DELETEだけで完結

追加・編集するファイル

追加(新規)

  • db/migrate/*_create_follows.rb:フォロー関係テーブル
  • app/models/follow.rb:中間モデル
  • app/controllers/follows_controller.rb:フォロー/解除
  • app/views/users/_follow_button.html.erb:ボタン部品
  • app/views/users/following.html.erb:フォロー中一覧
  • app/views/users/followers.html.erb:フォロワー一覧

編集(追記)

  • app/models/user.rb:関連とヘルパーメソッド
  • config/routes.rb:follows と users のメンバールート
  • app/controllers/users_controller.rb:following / followers(showが既にある想定)
  • app/views/users/show.html.erb:数/リンク/ボタン表示(既存に追記)

2. migration

コマンドプロンプト
rails g migration CreateFollows follower:references followed:references

これで app/models/follow.rbdb/migrate/..._create_follows.rb が同時にできます。
ただしこのままだと外部キーの参照先が「followers テーブル」扱いになってしまうため、マイグレーションを1か所修正します。

db/migrate/xxxxxx_create_follows.rb
class CreateFollows < ActiveRecord::Migration[7.2]
  def change
    create_table :follows do |t|
      # ↓ 生成直後は foreign_key: true のはず。必ず to_table: :users に直す
      t.references :follower, null: false, foreign_key: { to_table: :users }
      t.references :followed, null: false, foreign_key: { to_table: :users }
      t.timestamps
    end
    add_index :follows, [:follower_id, :followed_id], unique: true
  end
end
コマンドプロンプト
rails db:migrate

3. models

app/models.follow.rb
class Follow < ApplicationRecord
  belongs_to :follower, class_name: "User"
  belongs_to :followed, class_name: "User"

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

  private
  def not_self
    errors.add(:base, "自分はフォローできません") if follower_id == followed_id
  end
end
app/models/user.rb
class User < ApplicationRecord
  # Devise モジュールは既存

  # ======== ここから ========
  # 自分がフォローしている関係(能動)
  has_many :active_follows, class_name: "Follow",
                            foreign_key: :follower_id,
                            dependent: :destroy
  has_many :following, through: :active_follows, source: :followed

  # 自分をフォローしている関係(受動)
  has_many :passive_follows, class_name: "Follow",
                             foreign_key: :followed_id,
                             dependent: :destroy
  has_many :followers, through: :passive_follows, source: :follower

  # 便利メソッド
  def follow(other_user)
    return if self == other_user || following?(other_user)
    active_follows.create!(followed: other_user)
  end

  def unfollow(other_user)
    active_follows.find_by(followed: other_user)&.destroy
  end

  def following?(other_user)
    following.exists?(other_user.id)
  end
  # ======== ここまで ========
end

4. Controllers

コマンドプロンプト
rails g controller follows
app/controllers/follows_controller.rb
class FollowsController < ApplicationController
  before_action :authenticate_user!

  def create
    user = User.find(params[:followed_id])
    current_user.follow(user)
    redirect_back fallback_location: user_path(user), notice: "フォローしました"
  end

  def destroy
    follow = Follow.find(params[:id])
    return head :forbidden unless follow.follower_id == current_user.id

    current_user.unfollow(follow.followed)
    redirect_back fallback_location: user_path(follow.followed), notice: "フォローを解除しました"
  end
end
app/controllers/users_controller.rb
class UsersController < ApplicationController
  # showは既存想定。以下2アクションを追記
  def following
    @user  = User.find(params[:id])
    @users = @user.following.order(created_at: :desc)
  end

  def followers
    @user  = User.find(params[:id])
    @users = @user.followers.order(created_at: :desc)
  end
end

5. routes

config/routes.rb
Rails.application.routes.draw do
  devise_for :users
  resources :posts

  resources :users, only: [:show] do
    member do
      get :following
      get :followers
    end
  end

  resources :follows, only: [:create, :destroy]

  root "posts#index"
end

6. Views

ボタン部品

app/views/users/_follow_button.html.erb
<%# 引数: user表示対象のユーザー %>
<% if user_signed_in? && current_user != user %>
  <% if current_user.following?(user) %>
    <% follow = current_user.active_follows.find_by(followed_id: user.id) %>
    <%= button_to "フォロー中解除)", follow_path(follow), method: :delete %>
  <% else %>
    <%= button_to "フォローする", follows_path(followed_id: user.id), method: :post %>
  <% end %>
<% end %>

ユーザー詳細(追記)

app/views/users/show.html.erb
<p>名前 : <%= @user.name %></p>
<p>メールアドレス : <%= @user.email %></p>
<p>プロフィール : <%= @user.profile %></p>

# ======== ここから ========
<p>
  フォロー中: <%= @user.following.count %> /
  フォロワー: <%= @user.followers.count %>
  |
  <%= link_to "フォロー中一覧", following_user_path(@user) %> |
  <%= link_to "フォロワー一覧",  followers_user_path(@user) %>
</p>

<%= render "follow_button", user: @user %>
# ======== ここまで

<% if current_user.id == @user.id %>
  <%= link_to "編集する", edit_user_registration_path %>
<% end %>

一覧ページ

app/views/users/following.html.erb
<h2><%= @user == current_user ? "あなたがフォロー中" : "#{@user.email} さんがフォロー中" %></h2>
<ul>
  <% @users.each do |u| %>
    <li>
      <%= link_to(u.respond_to?(:name) ? u.name : u.email, user_path(u)) %>
      <%= render "follow_button", user: u %>
    </li>
  <% end %>
</ul>
app/views/users/followers.html.erb
<h2><%= @user == current_user ? "あなたのフォロワー" : "#{@user.email} さんのフォロワー" %></h2>
<ul>
  <% @users.each do |u| %>
    <li>
      <%= link_to(u.respond_to?(:name) ? u.name : u.email, user_path(u)) %>
      <%= render "follow_button", user: u %>
    </li>
  <% end %>
</ul>
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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?