rails 通知機能

**はじめに**

今回は、投稿のいいね機能についての通知機能を実装します。

バージョン
rails 6.1.7.4
ruby 3.1.2
通知する機能いいね機能作成済みとします
________

### モデル作成
```
rails g modle Notification
```
```notification.rb
class Notification < ApplicationRecord
  # デフォルトの並び順を「作成日時の降順」で指定、に新しい通知からデータを取得することができる
  default_scope -> { order(created_at: :desc) }
  belongs_to :visitor, class_name: 'User', optional: true
  belongs_to :visited, class_name: 'User', optional: true
  # optional: trueは、nilを許可するもの
  belongs_to :post, optional: true 
  belongs_to :comment, optional: true
```
```user.rb
 has_many :active_notifications, class_name: 'Notification', foreign_key: 'visitor_id', dependent: :destroy, inverse_of: :visitor
 has_many :passive_notifications, class_name: 'Notification', foreign_key: 'visited_id', dependent: :destroy, inverse_of: :visited
```
``class_name: 'Notification'`` は、この関連付けがNotificationモデルを指していることを明示しています。

``dependent: :destroy``は、Userが削除されたとき、関連するNotificationも一緒に削除されることを示しています。

``inverse_of: :visitor``は、この関連付けがNotificationモデルのvisitorメソッドと対になるものであることを示しています。

要するに、active_notificationsは「このユーザーが行ったアクションによって生成された通知」を、passive_notificationsは「このユーザー宛に送られた通知」をそれぞれ参照するための関連付けです。

```post.rb
 # 通知に投稿IDがnilであることを許容する
has_many :notifications, dependent: :nullify
```

### コントローラー作成
通知表示用のコントローラーを作成します。
```
rails g controller Notifications
```
```notifications_controller.rb
class Public::NotificationsController < ApplicationController
  before_action :authenticate_user!
  
  def index
     # 現在ログインしているユーザーの通知を取得
    @notifications = current_user.passive_notifications.order(created_at: "DESC")

    # 未読の通知を「既読」に更新
    @notifications.where(checked: false).find_each do |notification|
      notification.update(checked: true)
    end
  end
end
```
### いいね通知用のメソッド作成
```post.rb
 def create_notification_like!(current_user)
    user_id = self.user_id # self はこの場合 Post オブジェクトを指す(self.user_idは投稿の作成者のID)
    
    # すでに「いいね」されているか検索
    temp = Notification.where(["visitor_id = ? and visited_id = ? and post_id = ? and action = ?", current_user.id, user_id, id, 'like'])
  
    # temp.blank?がtrue（すでに同じ通知がない）であり、かつcurrent_user.id != user_id（自分自身の投稿にいいねしていない）場合、新しい通知レコードを作成
    return unless temp.blank? && current_user.id != user_id

      notification = current_user.active_notifications.new(
        post_id: id,
        visited_id: user_id,
        action: 'like'
      )
      # 自分の投稿に対するいいねの場合は、通知済みとする
      notification.checked = true if notification.visitor_id == notification.visited_id
      # notificationオブジェクトが有効（valid?メソッドがtrueを返す）であれば、この通知を保存
      notification.save if notification.valid?
  end
  ```

### いいね通知作成
非同期で作成したいいねのコントローラーに通知の作成コードを追加。
```likes_controller.rb
 def create
    @like = current_user.likes.create(post_id: params[:post_id])
    @post = @like.post
    # いいね通知の作成
    @post.create_notification_like!(current_user)
    respond_to do |format|
     format.js
    end
  end
```
### ビューページ
投稿表示用の部分テンプレートを作成します。
```erb:_notifiction.html.erb
<!--通知に関連する2人のユーザー（通知を行ったユーザーと通知されるユーザー）を取得-->
<% visitor = notification.visitor %>
<% visited = notification.visited %>
<div class="col-md-6 mx-auto">
  <div class="form-inline">
   <span>
     <!--ユーザー情報を表示しない通知-->
    <% if !%w[reply post_updated post_destroyed].include?(notification.action) %>  
      
    <% show_user_info = true %> <!-- 最初はユーザー情報を表示するとして変数を初期化 -->
      <!-- いいね通知の場合 -->
      <% if notification.action == 'like' && !notification.post %>
        <span class="text-red">
          <%= "該当する投稿が削除されています" %>
        </span> 
        <% show_user_info = false %> <!-- ユーザー情報を表示しないように変数を設定 -->
      <% end %>
    <% end %>

<!-- 上記でユーザー情報を表示すると判断された場合のみ表示 -->
    <% if show_user_info %>
      <%= link_to user_path(visitor) do %>
          <% if visitor&.icon_image&.attached? %>
            <%= image_tag visitor.icon_image, class: 'img-fluid profile-icon' %>
          <% end %>
          <strong><%= visitor.name %></strong>
      <% end %>
      <%= 'が' %>
    <% end %>

 <!--いいね通知の場合-->
      <% when 'like' then %>
        <% if notification.post %>
          <%= link_to "あなたの投稿: #{notification.post.title}", notification.post, style: "font-weight: bold;" %>
          に<i class="fas fa-heart" style="color: red; margin-left: 5px;"></i><%= "いいねしました" %>
        <% end %>
  <% end %>
```
**通知の種類に応じたユーザー情報の表示判定**（今回のいいね機能では使用しません）
特定の通知アクション（reply, post_updated, post_destroyed）ではユーザー情報を表示しないようにしています。一方、likeアクションの場合、関連する投稿が存在しない場合は「該当する投稿が削除されています」というメッセージを表示して、ユーザー情報の表示はスキップします。

**ユーザー情報の表示**
ユーザー情報を表示する条件を満たしている場合、通知を行ったユーザー（visitor）のプロフィール画像と名前をリンクとして表示しています。

**「いいね」通知の内容の表示**
likeアクションの場合、関連する投稿のタイトルへのリンクと、「いいねしました」というメッセージ、そしてハートのアイコンを表示します。

_____

お知らせ一覧ページ
```erb:public/notification/index.html.erb
<h1 class="ml-4 mb-5">お知らせ</h1>`
 <!--通知のインスタンスの数だけ、部分テンプレートが呼び出される-->
  <% @notifications.each do |notification| %>
    <%= render 'notification', notification: notification %>
  <% end %>
```
```erb
<div class="notification-icon">
    <%= link_to notifications_path do %>
      <i class="fa fa-bell mr-4"></i> <!-- ベルのアイコン -->
      <% if unread_notifications? %>
        <span class="notification-mark"></span> <!-- 未読マーク -->
      <% end %>
    <% end %>
</div>
```
ヘッダーなどに通知一覧ページへのリンク（ベルマーク）を表示します。
以下のように通知がある場合のみ赤いバッチが表示されるようにします。
![スクリーンショット 2023-09-20 23.01.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3492906/d2b69e76-9606-0a6b-d95d-9eb10dfb3984.png)
__________

参考

https://qiita.com/nekojoker/items/80448944ec9aaae48d0a