2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

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

Posted at

参考記事

1.RailsでややこしいDM機能を1万字でくわしく解説してみた
2.【Rails】通知機能を誰でも実装できるように解説する【いいね、コメント、フォロー】
3. [Rails] 同じmodelを参照する外部キーを一つのmodelでもつ方法

前提

・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モデルであるためそれを明示します。これは参考記事3の説明がわかりやすかったです。

  • 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レコードを作成するにはこの5つの値が必要になってきます。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レコードが2つ抽出されます。そのうち自分ではない方を選べば良いため.where.not(user_id: current_user.id)にします。
このときすでに得られたレコードは1つですが、whereはActiveRecord::Relationを返すメソッドであるためデータ出力のためにfind_byでもう一度特定しています
(参考: 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をいじるだけなのでここまでにしときます。

2
1
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
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?