📨
相互フォローのDM機能
2人のユーザーが相互フォロー関係にある時のDM機能を実装します。
以下の機能が実装可能となります。
- ユーザー詳細画面のフォローボタンの横に相互フォローになるとメッセージルームへのリンクが出現する。
- メッセージルームで非同期でメッセージが投稿できる。
完成図
ER図
ここで難しいのはUser_roomテーブル、Chatテーブルの2つの中間テーブルがある点です。
それぞれ、多対多の関係を図解してみます。
User_roomテーブルの必要性
Chatテーブルの必要性
必要なモデルの作成
上記のER図の構造に沿って、Roomモデル、User_roomモデル、Chatモデルを作成します。
(Userテーブルはすでに作成済みという前提です)
rails g model Room
rails g model UserRoom user_id:integer room_id:integer
rails g model Chat user_id:integer room_id:integer message:string
アソシエーションの設定
user.rb
has_many :user_rooms
has_many :chats
room.rb
has_many :user_rooms
has_many :chats
user_room.rb
belongs_to :user
belongs_to :room
chat.rb
belongs_to :user
belongs_to :room
validates :message, presence: true, length: { maximum: 140 }
-
validates :message, presence: true, length: { maximum: 140 }
DMを140字までの文字制限をするための記述を入れてあります。
ルーティングの設定
routes.rb
resources :chats, only: [:show, :create]
コントローラーの設定
chats_controller.rb
class ChatsController < ApplicationController
before_action :reject_non_related, 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.new(room_id: @room.id)
end
def create
@chat = current_user.chats.new(chat_params)
render :validater unless @chat.save
end
private
def chat_params
params.require(:chat).permit(:message, :room_id)
end
def reject_non_related
user = User.find(params[:id])
unless current_user.following?(user) && user.following?(current_user)
redirect_to books_path
end
end
end
-
rooms = current_user.user_rooms.pluck(:room_id)
pluckメソッドを使って、ログインユーザーの持つroom_id
を全て取得し、rooms
に格納します。 -
user_rooms = UserRoom.find_by(user_id: @user.id, room_id: rooms)
@userで定義されているのは「チャットへ」のリンクを押された側のユーザーです。
find_byメソッドを使って、一致する最初の一件のデータをuser_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
-
[unless user_rooms.nil?](https://www.sejuku.net/blog/75155)
条件式を使って、user_roomsの値がnil
だった場合は新規にRoomページを作成しています。
def create
@chat = current_user.chats.new(chat_params)
render :validater unless @chat.save
end
- render :validater unless @chat.save
@chatが保存されなかった場合にvalidaterにrenderします。
before_action :reject_non_related, only: [:show]
・
・
def reject_non_related
user = User.find(params[:id])
unless current_user.following?(user) && user.following?(current_user)
redirect_to books_path
end
end
-
reject_non_related
でユーザー同士が相互フォローにない場合のリダイレクト先を指定しています。
ビューの設定
users/_info.html.erb
<% if current_user != user && current_user.following?(user) && user.following?(current_user) %>
<%= link_to 'chatを始める', chat_path(user.id), class: "ml-3" %>
<% end %>
チャットルームへのリンクボタンを実装します。
-
current_user != user
ログインユーザーではない - 'current_user.following?(user)'
相手方がログインユーザーをフォローしている - 'user.following?(current_user)'
相手方がログインユーザーをフォローしている
chats/show.html.erb
<h1 id="room"><%= @user.name %> さんとのチャット</h1>
<div class="message" style="width: 400px;">
<% @chats.each do |chat| %>
<% if chat.user_id == current_user.id %>
<p style="text-align: right;"><%= chat.message %></p>
<% else %>
<p style="text-align: left;"><%= chat.message %></p>
<% 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.hidden_field :room_id %>
<% end %>
チャットルームのshowページを作成します。
<% @chats.each do |chat| %>
<% if chat.user_id == current_user.id %>
<p style="text-align: right;"><%= chat.message %></p>
<% else %>
<p style="text-align: left;"><%= chat.message %></p>
<% end %>
<% end %>
- ログインユーザーのチャットを右側、相手方のチャットを左側に配置します。
<%= form_with model: @chat, data: {remote: true} do |f| %>
<%= f.text_field :message %>
<%= f.hidden_field :room_id %>
<% end %>
-<%= f.hidden_field :room_id %>
コントローラーで設定したchat_params
へデータを送るため、
ユーザーには見えない形でroom_idをコントローラーへ送ります。
Jsファイルの設定
非同期通信化についてはこちらの記事もご参照ください。
create.js.erb
$('.message').append("<p style='text-align: right;'><%= @chat.message %></p>");
$('input[type=text]').val("")
-
.append
でHTML要素をJsファイル内に記述しています。
この部分で、ログインユーザーのDM投稿は非同期通信化されます。 -
$('input[type=text]').val("")
input[type=text]
はf.text_field
を表しています。val("")
とすることで、投稿後の記入欄に空白を追加します。
validater.js.erb
$('.errors').html("<%= j(render 'layouts/errors', obj: @chat) %>");
- エラー部分の非同期通信かを行っています。
いかがだったでしょうか?
なんとか動きはしたものの、文法理解が難しかったです・・・
季節の変わり目もあって、くたくたになりながらコードを打ってました。
下記のような他にもやり方がありそうなので、
そちらのほうも試してみたいですね♪
それでは!
Discussion