RailsでDM機能を実装
ポートフォリオでRailsでアプリケーションを作成し、1対1でDMを送ることができる機能を実装しました。
実装後の挙動
使用技術
Ruby 2.6.5
Ruby on Rails 6.0.5
テーブル設計
まずはテーブル設計です。
usersテーブル ユーザーを管理するテーブル
roomsテーブル メッセージをする部屋
entriesテーブル usersテーブルとroomsテーブルの中間テーブル。userとroomの情報が一つづつ入ります。
messagesテーブル これもusersテーブルとroomsテーブルの中間テーブル。メッセージの内容とuserとroomの情報が入ります。
アソシエーションはこのようになります。
has_many :messages, dependent: :destroy
has_many :entries, dependent: :destroy
has_many :messages, dependent: :destroy
has_many :entries, dependent: :destroy
belongs_to :user
belongs_to :room
belongs_to :user
belongs_to :room
ルーティング
resources :users, only: [:edit, :update, :show]
resources :messages, only: [:create]
resources :rooms, only: [:create, :show, :index]
userコントローラー
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
@currentUserEntry = Entry.where(user_id: current_user&.id)
# ログインしているユーザーの情報取得
@userEntry = Entry.where(user_id: @user.id)
# メッセージ相手のユーザーの情報を取得
if @user.id == current_user&.id
# ユーザーのidとログインしているidが同じでなければ実行
else
@currentUserEntry.each do |cu|
@userEntry.each do |u|
if cu.room_id == u.room_id
# 既にroomが作成されている場合
@isRoom = true
@roomId = cu.room_id
# 既に作成されているroom_idを特定
end
end
end
if @isRoom
else
# roomを新しく作成する記述
@room = Room.new
@entry = Entry.new
end
end
end
end
ここから理解が大変でした。まずはこの部分から解説します。
@user = User.find(params[:id])
@currentUserEntry = Entry.where(user_id: current_user&.id)
# ログインしているユーザーの情報取得
@userEntry = Entry.where(user_id: @user.id)
# メッセージ相手のユーザーの情報を取得
@userの所はuserの情報をfindでテーブルから取得しています。これによってuserの詳細ページでユーザーの名前を表示することができます。
@currentUserEntryと@userEntryはログインしているユーザーの情報とメッセージ相手のユーザーの情報を取得しています。
モデル名.where("条件")
whereを使うことでテーブル内の条件に一致したレコードを配列の形で取得することができます。
if @user.id == current_user&.id
# ユーザーのidとログインしているidが同じでなければ実行
else
@currentUserEntry.each do |cu|
@userEntry.each do |u|
if cu.room_id == u.room_id
# 既にroomが作成されている場合
@isRoom = true
@roomId = cu.room_id
# 既に作成されているroom_idを特定
end
end
end
if @isRoom
else
# roomを新しく作成する記述
@room = Room.new
@entry = Entry.new
end
end
end
ここではまず条件分岐でログインしているユーザーのidと同じでなければ実行するようにしています。意味的には自分のマイページではチャットを作ることはできないようにしています。
そして既にroomが作成されている場合はそのroomのidを特定する記述をし、まだroomが作成されていない場合は新しくroomを作るよう記述しています。
users/show.html.erb
次にuserの詳細ページのviewファイルです。
<% if @user.id == current_user&.id %>
<div class="d-grid gap-2 d-md-flex justify-content-md-end">
<%= link_to "DM一覧", rooms_path, class: "post btn btn-outline-info" %>
</div>
<% else %>
<% if @isRoom == true %>
<%# 既に部屋は存在しているかどうか %>
<div class="btn btn-secondary"><a class="text-decoration-none text-white" href="/rooms/<%= @roomId %>">チャットへ</div>
<% else %>
<%= form_for @room do |f| %>
<%= fields_for @entry do |e| %>
<%= e.hidden_field :user_id, :value=> @user.id %>
<% end %>
<%= f.submit "チャットを始める" ,class: "btn btn-secondary chat_btn"%>
<% end %>
<% end %>
<% end %>
まず条件分岐では自分のマイページではDM一覧ページに遷移するボタンが表示されます。他の人のページではチャットを始めるボタンを表示するようにしています。その中での条件分岐で始めてチャットをする相手には「チャットを始めるボタン』、既にチャットをしている相手にはチャットへボタンを表示しています。
roomsコントローラーとview
class RoomsController < ApplicationController
before_action :authenticate_user!
def create
@room = Room.create
@entry1 = Entry.create(room_id: @room.id, user_id: current_user.id)
@entry2 = Entry.create(params.require(:entry).permit(:user_id, :room_id).merge(room_id: @room.id))
redirect_to "/rooms/#{@room.id}"
end
def index
# ログインユーザー所属ルームID取得
current_entries = current_user.entries
my_room_id = []
current_entries.each do |entry|
my_room_id << entry.room.id
end
# 自分のroom_idでuser_idが自分じゃないのを取得
@another_entries = Entry.where(room_id: my_room_id).where.not(user_id: current_user.id)
end
def show
@room = Room.find(params[:id])
if Entry.where(user_id: current_user.id, room_id: @room.id).present?
@messages = @room.messages
@message = Message.new
@entries = @room.entries
else
redirect_back(fallback_location: root_path)
end
end
end
まずcreateアクションについて解説します。
@room = Room.create
これで新しいroomを保存しています。
@entry1 = Entry.create(room_id: @room.id, user_id: current_user.id)
@entry2 = Entry.create(params.require(:entry).permit(:user_id, :room_id).merge(room_id: @room.id))
redirect_to "/rooms/#{@room.id}"
これはentryテーブルにuser_idとroom_idを保存しています。@entry1は現在ログインしているユーザーでentry2がDMする相手のデータになります。
次にshowアクションについて解説します。
まずroomのidをfindで見つけてきます。
@room = Room.find(params[:id])
次に条件分岐でentryテーブルの中のログインしているユーザーのidとそれに紐づくroomのidを取得してpresent?で値が格納されているとtrueで値がないとfalseになります。
trueならそのroomのメッセージを表示するため@messagesのインスタンス変数の中に@room.messagesを代入しています。
また@message = Message.newで新しくメッセージを入れる箱を作っています。
@entries = @room.entriesはDMの画面で誰が参加しているかを表示するために記述しています。
<div class="container">
<% @another_entries.each do |entry| %>
<%= link_to room_path(entry.room) do %>
<div class="card mb-4">
<div class= "card-body">
<p class="card-text"><%= entry.user.name%>
さんとのメッセージ
</p>
</div>
</div>
<% end %>
<% end %>
</div>
ここでは自分がDMをした人との一覧を表示しています。
<div class= "container">
<h1>DM</h1>
<h4>参加者</h4>
<% @entries.each do |e| %>
<h5><strong><a href="/users/<%= e.user.id %>"><%= e.user.name%>さん</a></strong></h5>
<% end %>
<hr>
<% if @messages.present? %>
<% @messages.each do |m| %>
<%= m.user.name %>さん
<div class="date_time text-secondary text-right">
<%= m.created_at%>
</div>
<div class="message pt-3">
<%= m.content %>
</div>
<hr>
<% end %>
<% else %>
<h3 class="text-center">メッセージはまだありません</h3>
<% end %>
<%= form_for @message do |f| %>
<div class="d-flex pb-5">
<%= f.text_field :content, placeholder: "メッセージを入力して下さい" , rows: 15,class:"form-control"%>
<%= f.hidden_field :room_id, value: @room.id %>
<%= f.submit "投稿する",class:"btn btn-secondary" %>
</div>
<% end %>
</div>
ここではDMの画面を表示しています。
class MessagesController < ApplicationController
before_action :authenticate_user!, only: [:create]
def create
if Entry.where(user_id: current_user.id, room_id: params[:message][:room_id]).present?
@message = Message.create(params.require(:message).permit(:user_id, :content, :room_id).merge(user_id: current_user.id))
redirect_to "/rooms/#{@message.room_id}"
else
redirect_back(fallback_location: root_path)
end
end
end
ここではmessageを保存する処理を書いています。条件分岐でログインしているユーザーのidとroomのidとmessageのデータが入っていると実行されます。
データが入っていないと、何も起こらずにページに戻る記述になっています。
以上になります。
参考記事
RailsでややこしいDM機能を1万字でくわしく解説してみた
RailsでDM(ダイレクトメッセージ)を送れるようにしよう