はじめに
今回紹介するのはRails6を使ったDM機能の実装方法です。
開発環境
- AWS Cloud9
- Ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux]
- Rails 6.1.7.3
使用中のGem
- gem "devise"
- gem "jquery-rails"
前提環境
- Deviseのインストールが完了していて、ユーザーの新規登録やログインができる状態。
- フォローやフォロワー機能がある状態(なくてもいいです)
実装
モデルの作成
rails g model Chat user_id:integer room_id:integer message:text
rails g model Room
rails g model UserRoom user_id:integer room_id:integer
RoomモデルはDMをする為の部屋番号を管理し、ChatモデルはChatの内容を管理。
UserRoomモデルはUserとRoomを結ぶ中間テーブルになっています。
では、いつも通り、migrateしましょう。
rails db:migrate
アソシエーションの設定
続いてアソシエーションの設定を行います。
まずはUserから
has_many :user_rooms
has_many :chats
has_many :rooms, through: user_rooms
続いてRoom
class Room < ApplicationRecord
has_many :user_rooms
has_many :chats
end
続いてUserRoom
class UserRoom < ApplicationRecord
belongs_to :user
belongs_to :room
end
最後はChat
class Chat < ApplicationRecord
balongs_to :user
belongs_to :room
validates :message, length: { in: 1..140 }
送信するメッセージは1~140字以内でないといけない、というバリデーションをつけてみました。アプリの企画によって変更してください。
ユーザーは多くの部屋に参加することができ、ルームは多くのユーザーを持つことができるため、多対多になります。ユーザーとチャットの関係性はユーザーは多くのチャットを投稿することができますが、一つのチャットにかかわるユーザーは一人なので多対一になります。
ルーティングの設定
resources :chats, only:[:show, :create]
コントローラの作成
rails g controller chats
それではコントローラに記述していきましょう
class ChatsController < ApplicationController
def show
@user = User.find(params[:id:) #チャットする相手
rooms = current_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 #変数@roomにユーザー(自分と相手)と紐づいているroomを代入
else #ユーザールームが無かった場合
@room = Room.new #新しくRoomを作成
@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)
@room = @chat.room
@chats = @room.chats
render :validater unless @chat.save
end
private
def chat_params
params.require(:chat).permit(:message, :room_id)
end
ここでもし、相互フォローしてないとDMできないようにする為の記述を追加します。また、これを追加しないとURLに打ち込むと別のDMに行けてしまうのでその対策をします。
class ChatsController < ApplicationController
before_action :reject_non_related, only: [:show] #追加
def show
@user = User.find(params[:id:) #チャットする相手
rooms = current_user_rooms.pluck(:room_id)
user_rooms = UserRoom.find_by(user_id: @user_id, room_id; roomd) #チャットする相手とのルームがあるかの確認
unless user_rooms.nil? #ユーザールームがある場合
@room = user_rooms.room #変数@roomにユーザー(自分と相手)と紐づいているroomを代入
else #ユーザールームが無かった場合
@room = Room.new #新しくRoomを作成
@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)
@room = @chat.room
@chats = @room.chats
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 user_path
end
end
end
レイアウトも整えてみよう
.chat-container {
display: flex;
flex-direction: column;
height: 100%;
max-width: 800px;
margin: 0 auto;
}
.chat-topbar {
display: flex;
align-items: center;
justify-content: center;
background-color: #1E90FF;
color: #fff;
height: 50px;
padding: 0 20px;
}
.chat-messages {
flex: 1;
overflow-y: scroll;
padding: 20px;
}
.message {
display: flex;
flex-direction: column;
margin-bottom: 10px;
}
.message-sender {
margin-bottom: 5px;
font-size: 0.8em;
color: #666;
}
.message-body {
padding: 10px;
border-radius: 10px;
max-width: 70%;
}
.self .message-body {
align-self: flex-end;
background-color: #1E90FF;
color: #fff;
}
.other .message-body {
align-self: flex-start;
background-color: #f5f5f5;
color: #333;
}
.chat-form {
background-color: #f5f5f5;
padding: 20px;
}
.chat-form form {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
}
.chat-form input[type="text"] {
flex: 1;
margin-right: 10px;
padding: 10px;
font-size: 1em;
border: none;
border-radius: 5px;
background-color: #fff;
}
.chat-form input[type="submit"] {
flex: 0 0 auto;
padding: 10px;
font-size: 1em;
border: none;
border-radius: 5px;
background-color: #29b6f6;
color: #fff;
cursor: pointer;
}
続いてビューの作成
非同期通信にしたいので部分テンプレートを作ります。
app/viewa/chat
に新しく[_message.js.erb]というファイルを作成してください
<div class="chat-messages">
<% chats.each do |chat| %>
<% if chat.user_id == current_user.id %>
<div class="message self">
<div class="message-body">
<%= chat.message %>
</div>
</div>
<% else %>
<div class="message other">
<div class="message-sender"><%= chat.user.name %></div>
<div class="message-body">
<%= chat.message %>
</div>
</div>
<% end %>
<% end %>
</div>
さらに、app/viewa/chat
に新しく[_message.html.erb]を作成。
<div class="chat-messages">
<% chats.each do |chat| %>
<% if chat.user_id == current_user.id %>
<div class="message self">
<div class="message-body">
<%= chat.message %>
</div>
</div>
<% else %>
<div class="message other">
<div class="message-sender"><%= chat.user.username %></div>
<div class="message-body">
<%= chat.message %>
</div>
</div>
<% end %>
<% end %>
</div>
続いて
app/viewa/chat
に新しく[show.html.erb]を作成。
<div class="chat-container">
<div class="chat-topbar">
<h2><%= @user.username %> さんとのチャット</h2>
</div>
<div class="message">
<%= render "chats/messages", chats: @chats %>
</div>
<div class="chat-form">
<%= form_with model: @chat, data: {remote: true} do |f| %>
<%= f.text_field :message, placeholder: "メッセージを入力してください", autocomplete: "off" %>
<%= f.hidden_field :room_id, value: @room.id %>
<%= f.submit "送信" %>
<% end %>
</div>
</div>
続いて
app/viewa/chat
に新しく[create.js.erb]を作成。
$('.message').html("<%= j(render 'chats/messages', chats: @chats) %>");
$('input[type=text]').val("")
続いて
app/viewa/chat
に新しく[validator.js.erb]を作成。
$('.errors').html("<%= j(render 'layouts/errors', obj: @chat) %>");
続いて
app/javasprict/chats
に新しく[validator.js]を作成。
$(document).ready(function() {
$('.errors').html("<%= j(render 'layouts/errors', obj: @chat) %>");
});
最後に
app/viewa/chat
に新しく[_info.html.erb]を作成
<% if current_user != @user && current_user.following?(@user) && @user.following?(current_user) %>
<%= link_to 'チャットを始める', chat_path(@user.id), class: "ml-3" %>
<% end %>
長くなりましたが以上で実装の完了です!
お疲れ様でした。
もしよければコメント、いいねお待ちしてます!