Railsで簡単に1テーブルでフォロー機能を実装する
更新
2022/08/16 : 読者さんからのご指摘により、ルーティングにdestroyの記述忘れを修正しました。
2024/04/22: 読者さんからのご指摘により、一部変更しています。(Show関連)
背景
- 色々記事を見たけど、1テーブルで実装している記事が少ない
- 1テーブルでシンプルな方が良い
- ユーザーのフォロー情報が見られるようにしたい
サンプルリポジトリ
こちらから、実装済みソースコードが見られます。
注意
サンプルのリポジトリは、Dockerで起動することを前提に作成されています。
Dockerを使用しない場合は、appフォルダ以下で通常のrailsの起動処理を行ってください。
なお、記事ではDockerを使用せず導入する手順で記載していますのでご注意ください。
Rails6で記述していますが、Rails5でもロジックは同様に動作すると思います。
ソースコードのコメントについて
基本的にコメントを多めに記載しています。こちらを参考に実装して、ロジックの理解を深めてください。
実装
Deviseの導入は、今回は紹介しません。すでにUserを作成している前提で進めます
まずは、モデル、コントローラーなど作成します。
rails g model follow
rails g controller homes
rails g controller follows
ルーティング
Rails.application.routes.draw do
root to: 'homes#index'
devise_for :users
resources :follows, only: %w(index show create destroy)
end
マイグレーションファイル
class CreateFollows < ActiveRecord::Migration[6.1]
def change
# TODO : SQLiteでは、デフォルトでは外部キーを使用できませんのでご注意ください
create_table :follows do |t|
# foreign_keyで外部キー制約
# 意図しないユーザーIDが入らないようにしている
# to_tableキーの値で実際のテーブル名を指定
# TODO : フォロー主のIDが入る
t.references :follow, foreign_key: { to_table: :users }
# foreign_keyで外部キー制約
# 意図しないユーザーIDが入らないようにしている
# TODO : フォロー先のIDが入る
t.references :user, foreign_key: true
t.timestamps
end
end
end
モデル
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
# アソシエーション
has_many :follows, dependent: :destroy
# フォローユーザーの検索
# フォロー済みの場合、ユーザーデータを返す
# フォローしていない場合は、nilを返す
def following?(current_user, follow_id)
# Followから自分と対象のユーザーがANDになっているレコードを検索
Follow.find_by(user_id: follow_id, follow_id: current_user.id)
end
# フォローしている一覧取得
def followed(user)
Follow.where(follow_id: user.id)
end
# フォローされている一覧取得
def follower(current_user)
Follow.where(user_id: current_user.id)
end
end
class Follow < ApplicationRecord
# アソシエーション
belongs_to :user
end
コントローラー
class HomesController < ApplicationController
def index; end
end
class FollowsController < ApplicationController
before_action :authenticate_user!
def index
@users = User.all # ユーザー一覧取得
end
def show
@user = User.find(params[:id]) # ユーザー情報取得
@follows = @user.followed(@user) # フォローしているユーザー一覧取得
@followers = User.where(id: @user.follower(@user).pluck(:follow_id)) # フォローされているユーザー一覧取得
end
def create
# フォローレコードが1件もない場合は、レコードを作る
Follow.find_or_create_by(user_id: params[:follow_id], follow_id: current_user.id)
redirect_to follows_path
end
def destroy
# フォローレコードを探し、フォローレコードを削除する
# TODO : 勝手に他のユーザーのフォローを外されないようにparams[:id]で対象のユーザーIDを受け取る
Follow.find_by(user_id: params[:id], follow_id: current_user.id).delete
redirect_to follows_path
end
end
ビュー
<h1>Homes#index</h1>
<p>Find me in app/views/homes/index.html.erb</p>
<ul>
<li>
<%= link_to '新規登録', new_user_registration_path %>
</li>
<li>
<%= link_to 'ログイン', new_user_session_path %>
</li>
<%# ログイン済みならログアウト・ユーザー一覧を表示 %>
<% if user_signed_in? %>
<li>
<%= link_to 'ログアウト', destroy_user_session_path, method: :delete %>
</li>
<li>
<%= link_to 'ユーザー一覧', follows_path %>
</li>
<% end %>
</ul>
<h1>Follows#index</h1>
<p>Find me in app/views/follows/index.html.erb</p>
<% @users.each do |user| %>
<div>
<%# ユーザーのメールアドレスを表示 %>
<%= link_to user.email, follow_path(user) %>
<%# 自分の場合は、フォロー対象外 %>
<% unless user == current_user %>
<%# フォロー済みかどうか確認する %>
<%# following?は、Userモデルで定義しており、見つからない場合はnilが戻る %>
<% if user.following?(current_user, user.id).nil? %>
<%# フォローする : follows#create %>
<%# TODO: current_userをここで渡すと勝手に追加されるため、フォロー対象のユーザーの情報のみ渡す %>
<%= link_to 'Follow', follows_path(follow_id: user.id), method: :post %>
<% else %>
<%# フォローを外す : follows#destroy %>
<%# TODO: current_userをここで渡すと勝手に削除されるため、フォロー対象のユーザーをidで渡す %>
<%= link_to 'UnFollow', follow_path(user.id), method: :delete %>
<% end %>
<% end %>
</div>
<% end %>
<h1>Follows#show</h1>
<p>Find me in app/views/follows/show.html.erb</p>
<h2>
<%= @user.email %>
</h2>
<%# フォロー一覧表示 %>
<h3>Follows --></h3>
<% @follows.each do |follower| %>
<div>
<%= follower.user.email %>
</div>
<% end %>
<%# フォローワー一覧表示 %>
<h3>Followers --></h3>
<% @followers.each do |follower| %>
<div>
<%= follower.email %>
</div>
<% end %>
最後に
以上で、実装は完了のはずです。うまく動かない場合は、サンプルリポジトリを確認してください。
ぜひ、実装の参考にしてください!