DM、チャットにおける通知機能

## 参考記事
1.[RailsでややこしいDM機能を1万字でくわしく解説してみた](https://qiita.com/bindingpry/items/6790c91f374acc25bea2)
2.[【Rails】通知機能を誰でも実装できるように解説する【いいね、コメント、フォロー】](https://qiita.com/nekojoker/items/80448944ec9aaae48d0a)
3. [[Rails] 同じmodelを参照する外部キーを一つのmodelでもつ方法
](https://qiita.com/takeoverjp/items/bb56d6a8eae191cd3732)

## 前提
・DM機能（テーブル設計に関しては参考記事1と全く同じ）
## Roadmap
・モデルの作成
・モデルのリレーション
・通知機能の作成
・モデルに切り出し
・読んだ際に既読になる機能の作成

## モデルの作成
```
$ rails g model Notification
```
```migration_file.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.references  :room, foreign_key: true, null: false
      t.references  :message, foreign_key: true, null: false
      t.boolean :checked, default: false, null: false
      t.timestamps
    end
  end
end
```
visitor_idにDMを送ったUserのIDが入り、visited_idにDMを受け取ったUserのIDが入ります。boolean型のcheckedカラムには未読の場合false,既読の場合trueが入るようにします。初期の状態では未読なのでdefault: falseにしておきます。

## モデル間のリレーション
- UserとNotificationのリレーション

```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
```
DMを送る側も受け取る側もどちらもUserモデルであるためそれを明示します。これは参考記事３の説明がわかりやすかったです。

- RoomとNotificationのリレーション

```room.rb
    has_many :nofifications, dependent: :destroy
```

- MessageとNotificationのリレーション

```message.rb
    has_many :notifications, dependent: :destroy
```
- Notificationから見たリレーション

```notification.rb
    belongs_to :visitor, class_name: 'User', foreign_key: 'visitor_id'
    belongs_to :visited, class_name: 'User', foreign_key: 'visited_id'
    belongs_to :room
    belongs_to :message
```

## 通知機能の作成
メッセージを送信した際に通知機能が動くようにしたいので、messages_controllerのcreateアクションが実行されるときに同時にNotificationレコードが作成されるようにします。

```messages_controller.rb
def create
    if Entry.where(user_id: current_user.id, room_id: params[:message][:room_id])
        @message = Message.new(message_params)
        #ここから
        @room = @message.room
        #ここ
        if @message.save
            #ここから
            @receiver = Entry.where(room_id: @room.id).where.not(user_id: current_user.id).find_by(room_id: @room.id)
            notification = current_user.active_notifications.new(
                    visited_id: @receiver.user_id,
                    room_id: @room.id,
                    message_id: @message.id
            )
            notification.save if notification.valid?
            #ここ
            redirect_to(room_path(@message.room_id))
        else
            flash[:alert] = "メッセージ送信に失敗しました。"
            redirect_to(room_path(@message.room_id))
        end
    else
        flash[:alert] = "メッセージ送信に失敗しました。"
        redirect_to(room_path(@message.room_id))
    end
end
```
| visitor_id | visited_id | room_id | message_id | checked |
|:-:|:-:|:-:|:-:|:-:|
Notificationsテーブルの構成は上のようになっていて、Notificationレコードを作成するにはこの５つの値が必要になってきます。visitor_id(current_user.id), message_id(@message.id), checkedは既知であるためvisited_id, room_idに入る値を考えます。
room_idに関してはmessageとroomのリレーションがあるので@message.roomで得ることができます。(4行目)
visited_idには相手のuser_idが入ればいいのでEntriesテーブルから探します。EntriesテーブルはUsersテーブルとRoomsテーブルの中間テーブルであり、Entry.where(room_id: @room.id)の時点ではEntryレコードが２つ抽出されます。そのうち自分ではない方を選べば良いため.where.not(user_id: current_user.id)にします。
このときすでに得られたレコードは１つですが、whereはActiveRecord::Relationを返すメソッドであるためデータ出力のためにfind_byでもう一度特定しています
（参考: [find、find_by、whereなどモデルを取得する方法まとめ](https://rails-study.net/find-find_by-where))

## モデルに切り出し
で、これでも動きますがこれだと太っちょなcontroller？（この辺はよくわかっていません）になってしまうので、controllerに記述したロジックをモデルに切り出します。

```room.rb

    def create_notification_comment(current_user, message_id)
        receiver = Entry.where(room_id: id).where.not(user_id: current_user.id).find_by(room_id: id)
        save_notification_comment(current_user, message_id, receiver.user_id)
    end

    def save_notification_comment(current_user, message_id, visited_id)
        notification = current_user.active_notifications.new(
            visited_id: visited_id,
            room_id: id,
            message_id: message_id
        )
        notification.save if notification.valid?
    end
```

```message_controller.rb
    def create
        if Entry.where(user_id: current_user.id, room_id: params[:message][:room_id])
            @message = Message.new(message_params)
            @room = @message.room
            if @message.save
                @room.create_notification_comment(current_user, @message.id)  #<-メソッドとして呼び出す
                redirect_to(room_path(@message.room_id))
            else
                flash[:alert] = "メッセージ送信に失敗しました。"
                redirect_to(room_path(@message.room_id))
            end
        else
            flash[:alert] = "メッセージ送信に失敗しました。"
            redirect_to(room_path(@message.room_id))
        end
    end
```


## 読んだ際に既読になる機能の作成
メッセージを受け取る側がトークルームに入った時にNotificationのcheckedの値がfalseからtrueになるように実装します。今回の場合ではrooms_controllerのshowアクションが実行されたときにcheckedの値をfalseからtrueにアップデートするようにします。

```rooms_controller.rb
    def show
        @room = Room.find_by(id: params[:id])
        if Entry.where(user_id: current_user.id, room_id: @room.id)
            @messages = @room.messages
            @message = Message.new
            @entries = @room.entries
        else
            redirect_to(root_path)
        end
        @notifications = current_user.passive_notifications.where(room_id: @room.id)
        @notifications.where(checked: false).each do |notification|
            notification.update(checked: true)
        end
    end
```
ここではログインしているユーザー(current_user)が受け取っているが、まだ未読であるメッセージの通知を取得してそれをeach文で回しています。each文で回すことによって、メッセージ通知が複数件の場合でも全てのNotificationレコードのcheckedの値をfalseからtrueにすることができます。
あとはviewをいじるだけなのでここまでにしときます。