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.rbとdb/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>