【Rails6 通知機能】お知らせを投稿で登録済みユーザー全てに通知が送られる(未読既読管理)

完成のイメージ

通知の一覧画面と通知があった場合は、各ユーザーの画面上に【新着】マークを付けておくようにします。

前提

  • 投稿機能を実装済み(この記事では、Informationモデル)
  • ログイン・ユーザー登録機能を実装済み(この記事では、Userモデル)

参考にした記事

今回は、以下のサイトを参考に、「お知らせを投稿したら、登録済みユーザー全てに通知が送られる」機能を実装したいと思います。

【Rails】通知機能を誰でも実装できるように解説する【いいね、コメント、フォロー】

これができると、色々な機能に通知を持たせることができます。

通知モデルの概要

今回のテーブルの情報としては、以下のようになります。

visitor_id visited_id information_id action checked
1 2 3 information false
1 1 3 information false
  • visitor_id : 通知を送ったユーザーのid
  • visited_id : 通知を送られたユーザーのid
  • information_id : 投稿のid
  • action : 通知の種類
  • checked : 通知を送られたユーザーが通知を確認したかどうか真偽値

必要な情報以外は、nilを格納するようにしても問題ありません。

通知モデルの作成

まずは、コマンドから通知モデルを作成します。

% rails g model Notification

出来上がったマイグレーションファイルを開き、以下のように変更します。

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 :information_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, :information_id
  end
end

idのところには、検索パフォーマンスを考えてインデックスを張っています。

また、必ず値が設定される列には、nilを設定できないよう制約を追加します。

通知を確認したかどうかの初期値は、false(通知未確認)にしておきましょう。

マイグレーションをして、DBにテーブルを作成します。

% rails db:migrate

通知テーブルの完成。

モデル関連付け

User => Notifications

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:相手からの通知

紐付ける名前とクラス名が異なるため、明示的にクラス名とIDを指定して紐付けます。

また、ユーザーを削除したとき、同時に通知も削除したいので、 dependent: :destroyを追加します。

information => Notifications

app/models/information.rb

has_many :notifications, dependent: :destroy

今度は、紐付ける名前とクラス名が一致しているため、明示的に指定する必要はありません。

Notifications => User, information

app/models/notification.rb

default_scope -> { order(created_at: :desc) }
belongs_to :information, 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

default_scopeでは、デフォルトの並び順を「作成日時の降順」で指定しています。

つまり、常に新しい通知からデータを取得することができるということです。

たとえば、Notification.firstを実行すると、一番古い通知ではなく、一番新しい通知が取得できます。

informationについているoptional: trueは、nilを許可するものです。

belongs_toで紐付ける場合はnilが許可されないのですが、今回はnilを設定したいので、付けています。

お知らせ通知作成のメソッドを作る

app/models/information.rb

def create_notification_information!(current_user)
  # 全ユーザーを取得
  temp_ids = User.all.select(:id).distinct
  temp_ids.each do |temp_id|
    save_notification_information!(current_user, temp_id['id'])
  end
end

def save_notification_information!(current_user, visited_id)
  notification = current_user.active_notifications.new(
      visited_id: visited_id,
      information_id: id,
      action: 'information'
    )
    notification.save if notification.valid?
end

お知らせ通知作成メソッドの呼び出し

お知らせを作成したときにメソッドを呼び出します。

def create
  @information = Information.new(information_params)

  if @information.save

    #ここから追加
    # 通知アクション
    @information.create_notification_information!(current_user)
    #ここまで

    flash[:success] = 'お知らせを投稿しました。'
    redirect_to information_index_path
  else
    flash.now[:danger] = 'お知らせの投稿に失敗しました。'
    render :new
  end
end

通知一覧画面の作成

あとは、通知一覧の画面を作っていきましょう。

まずは、通知コントローラーの作成からです。

% rails g controller notifications

通知一覧の画面はindexで作るので、ルーティングを追加しておきましょう。

他のメソッドは不要なので、onlyを追加しています。

config/routes.rb

resources :notifications, only: :index

app/controllers/notifications_controller.rb

class NotificationsController < ApplicationController
  def index
    @notifications = current_user.passive_notifications
    @notifications.where(checked: false).each do |notification|
      notification.update(checked: true)
    end
  end
end

app/views/notifications/index.html.erb

<h1>通知一覧</h1>
<div class="common-contents">
  <div class="informations">
    <% if @notifications.exists? %>
    <ul class="allnotifications-list">
      <%= render @notifications %>
    </ul>
    <% else %>
    <p class="allnotifications-no">通知はありません</p>
    <% end %>
  </div>
</div>

app/views/notifications/_notification.html.erb

<% visitor = notification.visitor %>
<% visited = notification.visited %>
<li>
  <% case notification.action %>
  <% when 'information' then %>
    <h2>【運営より】<%= notification.information.title %></h2>
    <p><%= safe_join(notification.information.content.split("\n"), tag(:br)) %></p>
  <% end %>
  <p class="n-time"><%= time_ago_in_words(notification.created_at).upcase %>前(<%= notification.set_created %>)</p>
</li>

これで、お知らせ通知機能の実装完了です。

お疲れ様でした。

完成系のテーブル確認

お知らせ投稿後に通知のテーブルを確認してみましょう。

+----+------------+------------+----------------+-------------+---------+---------------------------+---------------------------+
| id | visitor_id | visited_id | information_id | action      | checked | created_at                | updated_at                |
+----+------------+------------+----------------+-------------+---------+---------------------------+---------------------------+
| 12 | 2          | 4          | 31             | information | false   | 2021-07-15 00:31:33 +0900 | 2021-07-15 00:31:33 +0900 |
| 11 | 2          | 2          | 31             | information | true    | 2021-07-15 00:31:33 +0900 | 2021-07-15 00:31:36 +0900 |
| 10 | 2          | 1          | 31             | information | false   | 2021-07-15 00:31:33 +0900 | 2021-07-15 00:31:33 +0900 |
| 9  | 2          | 4          | 30             | information | false   | 2021-07-14 23:08:58 +0900 | 2021-07-14 23:08:58 +0900 |
| 8  | 2          | 2          | 30             | information | true    | 2021-07-14 23:08:58 +0900 | 2021-07-14 23:09:00 +0900 |
| 7  | 2          | 1          | 30             | information | true    | 2021-07-14 23:08:58 +0900 | 2021-07-14 23:13:22 +0900 |

1, 2, 4のユーザーしかいませんでしたので、しっかりと全てのユーザーに対して、通知が送られています。

既読か未読かは、checkedtruefalseかを確認しましょう。

他にもこんな記事があります!