Rails|非同期通信のコメントの通知機能
目標
・他のユーザーが自分の投稿にコメントをした際に、通知が来るようにする。
・自分で自分の投稿にコメントした際には通知が来ない。
・未読の通知がある場合、ヘッダーの通知ボタンに🔴がつくようにする。
ER図
開発環境
ruby 3.1.2p20
Rails 6.1.7.4
Cloud9
前提
User, Review, Commentモデルは作成済み。
comment機能は作成済み。
モデルの作成
Notificationモデルを作成する
$ rails g model Notification
マイグレーションファイルを編集する
class CreateNotifications < ActiveRecord::Migration[6.1]
def change
create_table :notifications do |t|
t.integer :visiter_id, null: false
t.integer :visited_id, null: false
t.integer :review_id
t.string :action
t.boolean :is_checked, default: false, null: false
t.timestamps
end
end
end
visiter_id
コメントするユーザー
visited_id
コメントされるユーザー
action
何をされたのか
is_checked
既読か未読か
DBに反映する
$ rails db:migrate
ルーティングの編集
以下のように追記する。
resources :notifications, only: [:index, :destroy]
モデルの編集
class Notification < ApplicationRecord
default_scope -> { order(created_at: :desc) }
belongs_to :review
belongs_to :visiter, class_name: 'User', foreign_key: 'visiter_id', optional: true
belongs_to :visited, class_name: 'User', foreign_key: 'visited_id', optional: true
end
default_scope -> { order(created_at: :desc) }
Notificationのレコードを取得するとき、デフォルトで新着順に並び替えられるように設定。
belongs_to :visiter, class_name: 'User', foreign_key: 'visiter_id', optional: true
visiter(Userモデル)に紐づいており、FKがvisiter_id
であることを示している。また、optional: true
で、visiter_id
がnilであっても問題ないということにしている。
belongs_to :visited, class_name: 'User', foreign_key: 'visited_id', optional: true
visited(User)モデルに紐づいており、FKがvisited_id
である。また、visited_id
がnilであっても問題ない。
has_many :active_notifications, class_name: 'Notification', foreign_key: 'visiter_id', dependent: :destroy
has_many :passive_notifications, class_name: 'Notification', foreign_key: 'visited_id', dependent: :destroy
active_notification(Notificationモデル)
に紐づいており、FKはvisiter_id
である。また、Userが削除された場合、そのUserに紐づくNotificationは削除される。
passive_notification(Notificationモデル)
に紐づいており、FKはvisiter_id
である。また、Userが削除された場合、そのUserに紐づくNotificationは削除される。
has_many :notifications, dependent: :destroy
# 通知作成
def create_notification_by(current_user)
notification = current_user.active_notifications.new(
review_id: id,
visited_id: user_id,
action: "comment"
)
if notification.visiter_id == notification.visited_id
notification.is_checked = true
end
notification.save if notification.valid?
end
notification = current_user.active_notifications.new(...)
current_userのactive_notifications
という関連付けから、新しいnotification
オブジェクトを生成する。内容は()の通り。
if notification.visiter_id == notification.visited_id
コメントの送信者と受信者が同一人物の場合、通知は自動的に既読となる。
コントローラの作成
$ rails g controller public/notifications
class Public::NotificationsController < ApplicationController
def index
@notifications = current_user.passive_notifications.page(params[:page]).per(20)
@notifications.where(is_checked: false).each do |notification|
notification.update(is_checked: true)
end
end
def destroy
@notifications = current_user.passive_notifications.destroy_all
redirect_to notifications_path
end
end
@notifications = current_user.passive_notifications.page(params[:page]).per(20)
current_userに紐づくpassive_notificationsを表示。ページネーションを導入済み。
@notifications.where(is_checked: false).each do |notification| ... end
未読の通知を取得し、既読処理をする。
コメントコントローラの編集
class Public::CommentsController < ApplicationController
before_action :authenticate_user!, only: [:create, :destroy]
def create
comment = Comment.new(comment_params)
comment.user_id = current_user.id
comment.review_id = params[:comment][:review_id]
if comment.save
@review = comment.review
@review.create_notification_by(current_user) ##ココを追記
else
flash[:alert] = comment.errors.full_messages.join(", ")
redirect_back(fallback_location: root_path)
end
end
...
@review.create_notification_by(current_user)
ここが今回の追記部分。
コメントが保存された後、notificationを作成する。このメソッドの詳細はreviewモデルに記述済み。
ビューの作成
<div class="container">
<div class='row justify-content-center mx-auto'>
<div class="notification_box mt-5">
<h5 class="text-center"><i class="fa-solid fa-bell"></i> <span class="translatable-text">通知</span></h5>
<% if @notifications.exists? %>
<p class="text-right"><%= link_to "通知削除",notification_path(@notifications), method: :delete, class:"btn btn-secondary translatable-text" %></p>
<%= render @notifications %>
<% else %>
<p class="translatable-text">通知はありません</p>
<% end %>
</div>
</div>
</div>
<%= render @notifications %>
この部分は、省略型の部分テンプレート呼び出し。
Railsが自動的に、同じビューフォルダ内の_notification.html.erb
を探す。
そして、_notification.html.erb
は@notifications
の一つずつのオブジェクトに対して繰り返しレンダリングされる。
<% visiter = notification.visiter %>
<% review = notification.review %>
<ul class="list-unstyled">
<li class="translatable-text">
<%= visiter.name %>さんが
<%= link_to "あなたの投稿したレビュー", review_path(notification.review_id) %>にコメントしました。
<%= " (#{time_ago_in_words(notification.created_at)}前)" %></li>
</ul>
<% if unchecked_notifications.any? %>
<li>
<div class="alert-wrapper">
<%= link_to notifications_path, class: "btn btn-outline-secondary mr-3 translatable-text" do %>
<i class="fa-solid fa-bell"></i> 通知
<% end %>
<i class="fa-solid fa-circle fa-sm text-danger alert-mark"></i>
</div>
<% else %>
<li>
<%= link_to notifications_path, class: "btn btn-outline-secondary mr-3 translatable-text" do %>
<i class="fa-solid fa-bell"></i> 通知
<% end %>
<% end %>
</li>
ヘルパーモジュールの作成
module Public::NotificationsHelper
def unchecked_notifications
current_user.passive_notifications.where(is_checked: false)
end
end
ヘルパーモジュールとは、ビュー内で使用できるメソッドの集合。上記のビューで使用している。
参考にさせていただいた記事
Discussion