LoginSignup
3
4

More than 1 year has passed since last update.

[Rails] 通知機能

Posted at

DMが来た時、いいねされた時に通知が来る通知機能を実装した時のメモです。
DMといいね機能は既に実装済みとします。

DMの実装は以下の記事を参考にしてください。

実装

① モデルの作成

通知モデルを作成します。

ターミナル
$ rails g model Notification

以下を記述してmigrate。
actionにはいいねかDMどっちの通知なのかが入ります。
checkedには通知が既読済みかどうかの部分です。

add_indexを使用すると検索が早く行われるらしいです。

db/migrate/20210712225000_create_notifications.rb
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に関するモデルRoomMessageです。

  • Userモデル
app/models/user.rb
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モデル
app/models/event.rb
has_many :notifications, dependent: :destroy
  • Roomモデル
app/models/room.rb
has_many :notifications, dependent: :destroy
  • Messageモデル
app/models/message.rb
has_many :notifications, dependent: :destroy
  • Notificationモデル
app/models/notification.rb
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: truenilを許可しています。
nilを許可することでDM通知ならいいねはnil、いいねならDMはnilを格納することができます。

③ 通知作成メソッド

いいね

いいねが押された時に通知されるメソッドを作ります。

app/models/event.rb
 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で通知作成メソッドを呼びます。

app/controllers/likes_controller.rb
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に先程の処理を書いていきます。

app/controllers/messages_controller.rb
 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
config/routes.rb
resources :notifications, only: [:index, :update]
app/controllers/notifications_controller.rb
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を作成します。

app/views/notifications/index.html.haml
# 自分の投稿にいいねした場合は一覧に表示されないように
- notifications = @notifications.where.not(visitor_id: current_user.id)
.container 
  - breadcrumb :notice
  = breadcrumbs separator: " &rsaquo; "
  .title 
    %h2= I18n.t('notice.title')
  - if notifications.exists?
    = render notifications
  - else
    %p 通知はありません 
  • テンプレート部分
app/views/notifications/_notification.html.haml
- 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で表示を分岐しています。
ここは実際に画像で見てもらった方が早いので画像を載せておきます。
スクリーンショット 2021-10-06 10.13.01.png
ちなみに通知を受けった時間を表示したい場合は以下のメソッドを追加するといい感じに表示してくれます!

app/decorators/notification_decorator.rb
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 

⑤ 通知があるか

最後に通知があるかどうかを表示させます。

app/helpers/notifications_helper.rb
module NotificationsHelper
  def unchecked_notifications
    @notifications = current_user.passive_notifications.where(checked: false)
  end
end 

helperに未確認の通知を検索するメソッドを作ります。

あとは条件分岐で振り分けてCSSを当てれば完成です。

app/views/layouts/application.html.haml
 %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"

完成イメージ
通常時
スクリーンショット 2021-10-06 10.11.50.png

通知がある時
スクリーンショット 2021-10-06 10.10.02.png

これで完成!

3
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
4