※2022年から技術系の記事は個人ブログに投稿しております。ぜひこちらもご覧ください→yamaday0u Blog
Railsで友達登録機能を実装する方法を紹介します。
※あくまで業界未経験からのエンジニア転職を目指しているぼくが実際にオリジナルアプリに実装した1つの例です。
目次
- 前提条件
- 友達登録機能の仕様
- 動作イメージ
- 実装手順1:Modelの作成
- Relationshipsテーブルの作成
- アソシエーションとバリデーションの定義
- メソッドの定義
- 実装手順2:コントローラーの作成
- 実装手順3:ルーティングを設定
- 実装手順4:Viewの作成
前提条件
- Rails 6.0.3.5
- deviseなどでユーザー管理機能を実装済み
友達登録機能の仕様
TwitterやInstagramのように一方的にフォローするようなものではなく、Facebookのようにお互いにフォローし合うことで友達としてつながるようにします。
動作イメージ
リクエストボタンをクリックすることで相手をフォローしている状態になります。
相手からフォローされている状態で、相手をフォローすることで友達(GIFイメージのアプリでは"Mate"と呼んでいます)になります。
実装手順1:Modelの作成
Relationshipsテーブルの作成
$ rails g model relationship
生成されたマイグレーションファイルに以下のように記述します。
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
アソシエーションとバリデーションの定義
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から削除します。
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
メソッドの定義
フォロー状態などを確認するメソッドを定義します。
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アクションを定義します。
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コントローラーに以下のように記述します。
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
のルーティングの中にネストします。
Rails.application.routes.draw do
resources :users, only: [:index, :show] do
resources :relationships, only: [:create, :destroy]
end
end
実装手順4:Viewの作成
- ユーザー一覧ページ
<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です。
- ユーザー詳細ページ
<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です。
以上、みなさんのアプリ開発の参考になれば幸いです。
参考資料
-
Ruby on Railsチュートリアル
-
Qiita