【Rails】いいね/コメント/フォロー/DMの通知機能

#はじめに
通知機能の復習のため残しておきます。

#手順

###前提
いいね/コメント/フォロー/DM機能はそれぞれ作成済みとします。

今回は、

- post(投稿)に対するいいね、コメント
- user同士のフォロー
- フォローユーザー同士であればdm可能

という条件で機能を作成しています。

###やったこと

まず、通知の流れとして、
1.いいね/コメント/フォロー/DMのcreate時に、独自のメソッドで通知を作成
2.独自のメソッドが定義されているモデルへ行き、指定された処理を行う

いいね、コメント、フォロー、DMの順で記します。

###いいね通知

```ruby:favorite_controller.rb
@post = Post.find(params[:post_id]) #いいねをする投稿を探す
favorite = current_user.favorites.new(post_id: @post.id) #いいねを作成
favorite.save
@post.create_notification_favorite!(current_user) #いいねの通知を作成
```

```ruby:post.rb
def create_notification_favorite!(current_user)
  favorite_exist = Notification.where("visiter_id = ? and visited_id = ? and post_id = ? and action = ? ", current_user.id, user.id, id, 'favorite') # いいねしているか検索
  if favorite_exist.blank? # いいねしていない場合は通知を作成
    notification = current_user.active_notifications.new(
      post_id: id,
      visited_id: user_id, #通知相手に相手のidを指定
      action: 'favorite', #helperで後ほど使うためわかりやすい名前をつける
      checked: false #通知確認済み=true,　未確認=falseであり、defaultでfalseを設定しておく　
      )
    notification.save if notification.valid?
  end
end
```

###コメント通知
```ruby:post_comment_controller.rb
def create
    @post = Post.find(params[:post_id])
    @post_comment = PostComment.new(post_comment_params)
    @post_comment.post_id = @post.id
    @post_comment.user_id = current_user.id
    @comment_post = @post_comment.post
    if @post_comment.save
      @comment_post.create_notification_comment!(current_user, @post_comment.id) #コメントの通知を作成
      @post_comment = PostComment.new(post_comment_params)
    end
  end
```

```ruby:post.rb
def create_notification_comment!(current_user, comment_id)
    comment_users = PostComment.select(:user_id).where(post_id: id).where.not(user_id: current_user.id).distinct # 自分以外にコメントしている人をすべて取得し、全員に通知を送る
    comment_users.each do |comment_user|
      save_notification_comment!(current_user, comment_id, comment_user['user_id']) if comment_users.blank?
    end
      save_notification_comment!(current_user, comment_id, user_id) if comment_users.blank? # まだ誰もコメントしていない場合は、投稿者に通知を送る

  end

  def save_notification_comment!(current_user, comment_id, visited_id) 
    # 複数回のコメントを考慮。１つの投稿に複数回通知する
    notification = current_user.active_notifications.new(
      post_id: id,
      comment_id: comment_id,
      visited_id: visited_id,
      action: 'comment',
      checked: false
    )
    notification.save! if notification.valid?
  end
```

###フォロー通知
```ruby:relationships_controller.rb
def create
    @user = User.find(params[:user_id])
    current_user.follow(params[:user_id])
    @user.create_notification_follow!(current_user) #フォロー通知を作成
end
```

```ruby:user.rb
def create_notification_follow!(current_user)
    follow_exist = Notification.where(["visiter_id = ? and visited_id = ? and action = ? ", current_user.id, id, 'follow'])
    if follow_exist.blank?
      notification = current_user.active_notifications.new(
        visited_id: id,
        action: 'follow',
        checked: false
        )
      notification.save if notification.valid?
  end
end
```

###DM通知

usersとrooms（チャットルーム）を繋ぐ中間テーブルとして、
chats(メッセージとuserを紐付けて保管)
user_rooms(チャットルームに入るユーザー2名を紐付けて保管)
としています。

今回は、roomsで行うべき処理をchatsで行なっているので少し違和感ですが、とりあえず機能はしています。

```ruby:chats_controller.rb
    @chat = current_user.chats.new(chat_params)
    @room = @chat.room
    @chat.save
    @chat.room.create_notification_dm!(current_user, @chat.room_id, @chat.id)
    @chats = @room.chats
    @chat = Chat.new(room_id: chat_params[:room_id])
```

```ruby:room.rb
def create_notification_dm!(current_user, room_id, chat_id)
    dm_user = UserRoom.select(:user_id).where(room_id: room_id).where.not(user_id: current_user.id).distinct
    dm_user.each do |dm_user|
      save_notification_dm!(current_user, chat_id, dm_user['user_id'], room_id) 
    end
    # 初めてメッセージを送った場合
    if dm_user.blank?
      visited_id = UserRoom.where(room_id: room_id).where.not(user_id: current_user.id).distinct.first.user_id
      save_notification_dm!(current_user, chat_id, visited_id, room_id)
    end
  end
  def save_notification_dm!(current_user, chat_id, visited_id, room_id)
    # 複数回のコメントを考慮。１つの投稿に複数回通知する
    notification = current_user.active_notifications.new(
      room_id: room_id,
      chat_id: chat_id,
      visited_id: visited_id,
      action: 'dm',
      checked: false
    )
    notification.save! if notification.valid?
  end
```

###通知一覧を表示
```ruby:notifications_helper.rb
def notification_form(notification)
      @visiter = notification.visiter
      @comment = nil
      @chat = nil
      #notification.actionがfollowか、favoriteか、commentか、dmか
      case notification.action
        when "follow" then
          tag.a(notification.visiter.nickname, href:user_path(@visiter), style:"font-weight: bold;")+"があなたをフォローしました"
        when "favorite" then
          tag.a(notification.visiter.nickname, href:user_path(@visiter), style:"font-weight: bold;")+"が"+tag.a('あなたの投稿', href:post_path(notification.post_id), style:"font-weight: bold;")+"にいいねしました"
        when "comment" then
          @comment = PostComment.find_by(id: notification.comment_id)&.comment
          tag.a(@visiter.nickname, href:user_path(@visiter), style:"font-weight: bold;")+"が"+tag.a('あなたの投稿', href:post_path(notification.post_id), style:"font-weight: bold;")+"にコメントしました"
        when "dm" then
          @chat = Chat.find_by(id: notification.chat_id)&.message
          tag.a(@visiter.nickname, href:user_path(@visiter), style:"font-weight: bold;")+"があなたに"+tag.a('メッセージ',href:chat_path(notification.visiter), style:"font-weight: bold;")+"を送りました"
      end
    end
    def unchecked_notifications
      @notifications = current_user.passive_notifications.where(checked: false)
    end
```

```notifications_controller.rb
  def index
    @notifications = Notification.where(visited_id: current_user.id, checked: false).where.not(visiter_id: current_user.id).to_a
      # 通知の確認済みをtrueに変更して保存したいため、.to_aで配列とし、ビューでは表示可能に。処理としてはchecked: trueにするところまで行っており、リロードすると確認ずみとなる。
    @notifications.each do |notification|
      notification.update_attributes(checked: true)
      @comment = PostComment.find_by(id: notification.comment_id)
    end
  end

  def destroy
    notification = Notification.find(params[:id])
    notification.destroy
    redirect_back(fallback_location: root_path)
  end

  def destroy_all
    @checked_notifications = Notification.where(visited_id: current_user.id, checked: true).where.not(visiter_id: current_user.id)
    @checked_notifications.destroy_all
    redirect_back(fallback_location: root_path)
  end
```

```ruby:notifications/index.html.erb
<div id="tab-contents">
  <div id="tab1" class="tab notifications-index">
    <% if @notifications.present? %>
      <table class="table table-borderless">
        <% @notifications.each do |notification| %> #@notifications(未確認の通知たち)があればeachで表示
          <tr>
            <td>
              <%= link_to user_path(notification.visiter) do %>
                <%= attachment_image_tag notification.visiter, :profile_image, :fill,65,65, format: "jpeg", fallback: "no_image.png", size: "65x65", class: "rounded-circle" %>
              <% end %>
            </td>
            <td>
              <p><%= notification_form(notification) %><span class="moderate-font"><%= " (#{time_ago_in_words(notification.created_at)} 前)" %></span></p>
            </td>
          </tr>
        <% end %>
      </table>
    <% else %>
      <p>未読の通知はありません</p>
    <% end %>
  </div>
</div>
```

お好みで、@notificationsの中身を既読済みに変えてアレンジすると使いやすくなるかと。

#終わりに

記述量は多いですが、一つひとつ理解することで応用できるように。
とはいえまだ疑問点は残るため、解消できるよう頑張るのみ。