DMが来た時、いいねされた時に通知が来る通知機能を実装した時のメモです。
DMといいね機能は既に実装済みとします。
DMの実装は以下の記事を参考にしてください。
実装
① モデルの作成
通知モデルを作成します。
$ rails g model Notification
以下を記述してmigrate。
action
にはいいねかDMどっちの通知なのかが入ります。
checked
には通知が既読済みかどうかの部分です。
add_index
を使用すると検索が早く行われるらしいです。
class CreateNotifications < ActiveRecord::Migration[6.1]
def change
create_table :notifications do |t|
t.integer :visitor_id, null: false
t.integer :visited_id, null: false
t.integer :event_id
t.integer :room_id
t.integer :message_id
t.string :action, default: '', null: false
t.boolean :checked, default: false, null: false
t.timestamps
end
add_index :notifications, :visitor_id
add_index :notifications, :visited_id
add_index :notifications, :event_id
add_index :notifications, :room_id
add_index :notifications, :message_id
end
end
$ rails db:migrate
② モデルの関連付け
作成したNotification
モデルを関連付けていきます。
関連付けるモデルはUser
、投稿モデルのEvent
、DMに関するモデルRoom
とMessage
です。
-
User
モデル
has_many :active_notifications, class_name: 'Notification', foreign_key: 'visitor_id', dependent: :destroy
has_many :passive_notifications, class_name: 'Notification', foreign_key: 'visited_id', dependent: :destroy
active_notifications
は自分からの通知、passive_notifications
は相手からの通知です。
Userが消えたら通知も削除するので、dependent: :destroy
-
Event
モデル
has_many :notifications, dependent: :destroy
-
Room
モデル
has_many :notifications, dependent: :destroy
-
Message
モデル
has_many :notifications, dependent: :destroy
-
Notification
モデル
class Notification < ApplicationRecord
default_scope -> { order(created_at: :desc) }
belongs_to :event, optional: true
belongs_to :comment, optional: true
belongs_to :room, optional: true
belongs_to :message, optional: true
belongs_to :visitor, class_name: 'User', foreign_key: 'visitor_id', optional: true
belongs_to :visited, class_name: 'User', foreign_key: 'visited_id', optional: true
end
default_scope
で常に新しい通知からデータを取得するようにデフォルトの並び順を作成日時の降順で指定しています。
optional: true
でnil
を許可しています。
nil
を許可することでDM通知ならいいねはnil
、いいねならDMはnil
を格納することができます。
③ 通知作成メソッド
いいね
いいねが押された時に通知されるメソッドを作ります。
def create_notification_like!(current_user)
# すでいいねの通知があるか?
temp = Notification.where(["visitor_id = ? and visited_id = ? and event_id = ? and action = ? ", current_user.id, user_id, id, 'join'])
# ない場合いいねの通知作成
if temp.blank?
notification = current_user.active_notifications.new(
event_id: id,
visited_id: user_id,
action: 'like'
)
#自分の投稿だったら通知済み
if notification.visitor_id == notification.visited_id
notification.checked = true
end
notification.save if notification.valid?
end
end
いいねされていない場合のみ通知を作成することでいいねを間違って連打した時に初回通知のみで、毎回通知が行くことがなくなります。
あとはいいねモデルのControllerで通知作成メソッドを呼びます。
def create
event = Event.find(params[:event_id])
event.likes.create!(user_id: current_user.id)
#---------------------追加----------------------
event.create_notification_like!(current_user)
#----------------------------------------------
render json: { status: 'ok' }
end
DM
続いてDMです。
DMはMessage Controllerに先程の処理を書いていきます。
def create
message = Message.new(message_params)
message.user_id = current_user.id
#-------追加------------
room = message.room
#----------------------
if message.save
#-----------------------追加-----------------------------------------
another_room = Entry.where(room_id: room.id).where.not(user_id: current_user.id)
theid = another_room.find_by(room_id: room.id)
notification = current_user.active_notifications.new(
room_id: room.id,
message_id: message.id,
visited_id: theid.user_id,
visitor_id: current_user.id,
action: 'dm'
)
if notification.visitor_id == notification.visited_id
notification.checked = true
end
notification.save if notification.valid?
#--------------------------------------------------------------------
redirect_to room_path(message.room)
else
redirect_back(fallback_location: root_path)
end
end
やっていることはいいねの時と基本的には同じです。
④ 通知一覧
通知の一覧画面を作っていきます。
$ rails g controller notifications
resources :notifications, only: [:index, :update]
class NotificationsController < ApplicationController
before_action :authenticate_user!
def index
@notifications = current_user.passive_notifications
end
def update
notification = Notification.find(params[:id])
if notification.update(checked: true)
redirect_to notifications_path
end
end
end
index
は受け取った通知一覧を表示。
update
は通知を既読済みにするためのものです。
Viewを作成します。
# 自分の投稿にいいねした場合は一覧に表示されないように
- notifications = @notifications.where.not(visitor_id: current_user.id)
.container
- breadcrumb :notice
= breadcrumbs separator: " › "
.title
%h2= I18n.t('notice.title')
- if notifications.exists?
= render notifications
- else
%p 通知はありません
- テンプレート部分
- visitor = notification.visitor
- visited = notification.visited
- if notification.checked == false
.card
.card-body
.d-flex
.avatar-wrapper
= image_tag visitor.avatar_image, class:'avatar-notice'
.notice-wrapper
= link_to account_path(visitor),class: 'btn btn-link notice-link' do
= visitor.display_name
さんが
- case notification.action
- when 'dm' then
あなたに
= link_to 'メッセージ', notification.room, class: 'btn btn-link notice-link'
を送りました。
= link_to I18n.t('notice.read'),notification_path(notification.id),method: :put, class: 'btn btn-link notice-link'
- when 'like'
= link_to 'あなたのイベント', notification.event, class: 'btn btn-link notice-link'
にいいねしました。
= link_to I18n.t('notice.read'),notification_path(notification.id),method: :put, class: 'btn btn-link notice-link'
.time
= notification.how_long_ago
action
で表示を分岐しています。
ここは実際に画像で見てもらった方が早いので画像を載せておきます。
ちなみに通知を受けった時間を表示したい場合は以下のメソッドを追加するといい感じに表示してくれます!
module NotificationDecorator
def how_long_ago
if (Time.now - self.created_at) <= 60 * 60
((Time.now - self.created_at) / 60).to_i.to_s + "分前"
elsif (Time.now - self.created_at) <= 60 * 60 * 24
((Time.now - self.created_at) / 3600).to_i.to_s + "時間前"
elsif (Date.today - self.created_at.to_date) <= 30
(Date.today - self.created_at.to_date).to_i.to_s + "日前"
else
self.created_at
end
end
end
⑤ 通知があるか
最後に通知があるかどうかを表示させます。
module NotificationsHelper
def unchecked_notifications
@notifications = current_user.passive_notifications.where(checked: false)
end
end
helperに未確認の通知を検索するメソッドを作ります。
あとは条件分岐で振り分けてCSSを当てれば完成です。
%li.nav-item
- if unchecked_notifications.any?
= link_to I18n.t('header.notice'), notifications_path , class: "nav-link notice-active"
- else
= link_to I18n.t('header.notice'), notifications_path , class: "nav-link"
これで完成!