【Rails】DM機能(非同期通信化)
本の投稿サイトにDM機能を実装します!
実装要件
-
ユーザー同士で1対1のDMができるようにする(140字まで可)
-
相互フォローしている人限定でDMが使える
-
完成イメージ:
モデル作成
考え方
上図のように、ユーザー(users)とルーム(rooms)の関係は「多対多」の関係です。
そのため中間テーブルが必要です。
よって下図のようにします。
user_roomsテーブルとchatsテーブルの2つの中間テーブルを作ることで、1つのユーザーが複数のルームに参加し、各ルームで複数のチャットができるようにします。
テーブル設計
テーブルは以下の通り。
モデルの作成
- Roomモデル
rails g model Room
- Chatモデル
rails g model Chat user:references room:references message:string
messageカラムを入れます。
- User_roomモデル
rails g model UserRoom user:references room:references
アソシエーション記述
has_many :user_rooms, dependent: :destroy
has_many :chats, dependent: :destroy
has_many :rooms, through: :user_rooms
has_many :user_rooms, dependent: :destroy
has_many :chats, dependent: :destroy
belongs_to :user
belongs_to :room
validates :message, presence: true, length: { maximum: 140 }
メッセージが空でなく、140字以下であることを検証するバリデーションを記載。
belongs_to :user
belongs_to :room
コントローラの作成
以下のコントローラを作成します。
rails g controller chats
ルーティングの設定
以下の記述を追加します。
resources :chats, only: [:show, :create, :destroy]
実装要件にはありませんが、メッセージの削除ができるようにdestroyアクションも実装します。
コントローラの記述
解説はコメントアウトで記述します。
class ChatsController < ApplicationController
# showアクションにおいて、関連のないユーザーをブロックする
before_action :block_non_related_users, only: [:show]
# チャットルームの表示
def show
# チャット相手のユーザーを取得
@user = User.find(params[:id])
# 現在のユーザーが参加しているチャットルームの一覧を取得
rooms = current_user.user_rooms.pluck(:room_id)
# 相手ユーザーとの共有チャットルームが存在するか確認
user_rooms = UserRoom.find_by(user_id: @user.id, room_id: rooms)
unless user_rooms.nil?
# 共有チャットルームが存在する場合、そのチャットルームを表示
@room = user_rooms.room
else
# 共有チャットルームが存在しない場合、新しいチャットルームを作成
@room = Room.new
@room.save
# チャットルームに現在のユーザーと相手ユーザーを追加
UserRoom.create(user_id: current_user.id, room_id: @room.id)
UserRoom.create(user_id: @user.id, room_id: @room.id)
end
# チャットルームに関連付けられたメッセージを取得
@chats = @room.chats
# 新しいメッセージを作成するための空のChatオブジェクトを生成
@chat = Chat.new(room_id: @room.id)
end
# チャットメッセージの送信
def create
# フォームから送信されたメッセージを取得し、現在のユーザーに関連付けて保存
@chat = current_user.chats.new(chat_params)
# バリデーションに合格しない場合はエラーを表示
render :validate unless @chat.save
end
# チャットメッセージの削除
def destroy
# ログイン中のユーザーに関連するチャットメッセージを削除
@chat = current_user.chats.find(params[:id])
@chat.destroy
end
private
# フォームから送信されたパラメータを安全に取得
def chat_params
params.require(:chat).permit(:message, :room_id)
end
# 関連のないユーザーをブロックする
def block_non_related_users
# チャット相手のユーザーを取得
user = User.find(params[:id])
# ユーザーがお互いにフォローしているか確認し、していない場合はリダイレクト
unless current_user.following?(user) && user.following?(current_user)
redirect_to books_path # リダイレクト先は適切なものに変更しましょう
end
end
end
ビューの記述
チャット開始ボタン追加
必要箇所に開始ボタンを追加してあげます。
<% if current_user != user && current_user.following?(user) && user.following?(current_user) %>
<%= link_to 'chatを始める', chat_path(user.id), class: "ml-3" %>
<% end %>
解説:
<% if current_user != user && current_user.following?(user) && user.following?(current_user) %>
:
-
current_user != user
は、現在のユーザーがチャット相手と異なる場合。つまり、自分自身にはチャットを始めることはできないということ。 -
current_user.following?(user)
とuser.following?(current_user)
は、ユーザー同士がお互いにフォローしている状態を確認しています。
チャットルーム
部分テンプレート作成
非同期化するために、自分の送るメッセージ部を部分テンプレートにしてあげます。
<div class="right-container d-flex justify-content-end">
<p id="chat_<%= chat.id %>" style="background-color: rgba(0, 185, 0, 0.6); padding: 5px; border-radius: 10px; "><%= chat.message %></p>
<%= button_to "削除", chat_path(chat), method: :delete, data: { confirm: "本当に削除しますか?" }, class: "btn btn-danger btn-sm ml-2", remote: true, id: "delete_chat_#{chat.id}" %>
</div>
解説:
自身のチャットメッセージは右寄せで表示できるようにします。
id="chat_<%= chat.id %>
とid: "delete_chat_#{chat.id}"
でメッセージごとに異なるIDをつけてあげることができます。そうすることで、消したいメッセージを非同期で削除できるようにします。
チャットルーム(show)の作成
<h2 id="room" data-room="<%= @room.id %>" data-user="<%= current_user.id %>"><%= @user.name %> さんとのチャット</h2>
<div class="message" style="width: 400px;">
<% @chats.each do |chat| %>
<% if chat.user_id == current_user.id %>
<%= render "chats/chat", chat: chat %>
<% else %>
<div class="left-container d-flex">
<%= image_tag @user.get_profile_image, size: '30x30', style: 'border-radius: 50%' %>
<p class=ml-1 style="text-align: left;"><span style="background-color: #F5F5DC; padding: 5px; border-radius: 10px;"><%= chat.message %></span></p>
</div>
<% end %>
<% end %>
</div>
<div class="errors">
<%= render "layouts/errors", obj: @chat %>
</div>
<%= form_with model: @chat, data: {remote: true} do |f| %>
<%= f.text_field :message %>
<%= f.submit "送信", class: "btn btn-dark btn-sm" %>
<%= f.hidden_field :room_id %>
<% end %>
解説:
相手側のメッセージは左寄せで表示するようにします。
また、部分テンプレートも呼び出してあげます。
ポイント
<%= f.hidden_field :room_id %>
はフォーム内に隠しフィールドを作っています。これを使うことで、ユーザーには表示したくない情報も一緒に送ることができます。メッセージを送信する際、、どのチャットルームにメッセージを送るのかという情報が必要です。そのため、チャットルームIDを一緒に送信してあげます。
create.jsの作成
$('.message').append('<%= j(render "chats/chat", chat: @chat) %>');
$('input[type=text]').val("")
$('input[type=text]').val("")
で、メッセージ送信後にテキストボックスを空にできます。
destroy.jsの作成
$('#chat_<%= @chat.id %>').remove();
$('#delete_chat_<%= @chat.id %>').remove();
IDを指定することで、消したいメッセージと削除ボタンを消すことができます。
validater.jsの作成
$('.errors').html("<%= j(render 'layouts/errors', obj: @chat) %>");
空のメッセージ等を送るとエラー表示を出すようにします。
完成!
やり残していた記事でした。何個かあるのでこれからアップしていきます!
続きはこちら。
Discussion