ポートフォリオに実装した機能を解説していきます!
今回はDM(ダイレクトメッセージ)機能。
目標
機能としては、
ユーザー詳細ページからメッセージルームに飛んで個別にメッセージ、
またやりとりしたメッセージルームは一覧でマイページから取得できる。
ざっくりですがこんな感じ
では行こう!
前提
前提としてdeviseの導入を忘れずに!
deviseの導入はこちらの記事を参考にしてください。
設計
4つのモデルを使って実装します。
Userモデル => Userの情報(devise)
Roomモデル => 自分と相手の2人のユーザーが入ります
Entryモデル => どのUserがどのRoomに所属しているかを判断
Messageモデル => UserがどのRoomでどんなMessageを送ったか
ER図はこんな感じ
User1人1人は他のUserに対して沢山のRoomを持っていることになるのでUserとRoomの関係性は多対多になり中間テーブルとしてEntryテーブルを用意。
またRoom内で2人のUserが沢山のMessageでやりとりするのでこちらも多対多で中間テーブルにMassegeを用意します
実装
では行こう!!
モデルの準備
$ rails g model room
$ rails g model entry user:references room:references
$ rails g model message user:references room:references body:text
$ rails db:migrate
リレーション
has_many :entries, dependent: :destroy
has_many :messages, dependent: :destroy
has_many :entries, dependent: :destroy
has_many :messages, dependent: :destroy
belongs_to :user
belongs_to :room
belongs_to :user
belongs_to :room
routes
resources :messages, only: [:create]
resources :rooms, only: [:create, :index, :show]
Controller & View
users_controller.rb
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
@current_entry = Entry.where(user_id: current_user.id)
@another_entry = Entry.where(user_id: @user.id)
unless @user.id == current_user.id
@current_entry.each do |current|
@another_entry.each do |another|
if current.room_id == another.room_id
@is_room = true
@room_id = current.room_id
end
end
end
unless @is_room
@room = Room.new
@entry = Entry.new
end
end
end
解説
@current_entry = Entry.where(user_id: current_user.id)
@another_entry = Entry.where(user_id: @user.id)
ログインしてるユーザーとメッセージ相手のユーザー情報をEntryテーブルから検索して取得します。
unless @user.id == current_user.id
@current_entry.each do |current|
@another_entry.each do |another|
if current.room_id == another.room_id
@is_room = true
@room_id = current.room_id
end
end
end
unless @is_room
@room = Room.new
@entry = Entry.new
end
end
end
unlessでログインしていないユーザーという条件をつけます。
そしてさっき取得した2つのユーザー情報をそれぞれeachで取り出してEntryテーブル内に同じroom_idが存在するかどうかを調べます。
同じroom_idが存在する場合は既にroomが存在しているということなのでroom_idの変数とroomが存在するかどうかの条件であるis_roomを渡します。
同じroom_idが存在しない場合は新しくインスタンスを作成します。
users/show.html.haml
続いてviewです。
viewはhamlで書いてます。
- unless @user.id == current_user.id
- if @is_room == true
= link_to 'メッセージへ', room_path(@room_id)
- else
= form_for @room do |f|
= fields_for @entry do |e|
- e.hidden_field :user_id, value: @user.id
= f.button type: :submit do
メッセージを送る
ここではログインしているユーザーではないという条件をつけてis_roomの条件を使って既に部屋が存在しているかどうかで分岐させます。
既に部屋が存在していればその部屋に、していなければformでコントローラーにパラメーターを送っています。
rooms_controller.rb
class RoomsController < ApplicationController
before_action :authenticate_user!
def create
room = Room.create
current_entry = Entry.create(user_id: current_user.id, room_id: room.id)
another_entry = Entry.create(user_id: params[:entry][:user_id], room_id: room.id)
redirect_to room_path(room)
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])
@messages = @room.messages.all
@message = Message.new
@entries = @room.entries
@another_entry = @entries.where.not(user_id: current_user.id).first
end
end
解説
def create
room = Room.create
current_entry = Entry.create(user_id: current_user.id, room_id: room.id)
another_entry = Entry.create(user_id: params[:entry][:user_id], room_id: room.id)
redirect_to room_path(room)
end
ここでusers/show.html.hamlで部屋が存在しなかった場合にformで送られてきたパラメーターきます。
やっていることとしては現在ログインしているユーザーとメッセージ相手のユーザーそれぞれの情報をroom_idで紐付けてEntryテーブルにcreateしています。
def index
current_entries = current_user.entries
my_room_id = []
current_entries.each do |entry|
my_room_id << entry.room.id
end
@another_entries = Entry.where(room_id: my_room_id).where.not(user_id: current_user.id)
end
続いてindex
indexでは現在やりとりしているroom一覧を取得します。
current_entries = current_user.entries
my_room_id = []
current_entries.each do |entry|
my_room_id << entry.room.id
end
まずここでログインユーザーがやりとりしているroomのIDをすべて取得しそれを配列化してmy_room_idとします。
@another_entries = Entry.where(room_id: my_room_id).where.not(user_id: current_user.id)
そしてEntryテーブルからmy_room_idでuser_idが自分のIDじゃないレコードを取り出します。
こうすることで現在自分が参加中のroomを相手の情報で取得しviewで表示することができます。
rooms/index.html.haml
- @another_entries.each do |entry|
= link_to room_path(entry.room) do
.card
.card-body
%div
.d-flex
.chat-avatar
%object
= link_to user_path(entry.user) do
= image_tag entry.user.avatar_image
.chat-user-name
= entry.user.display_name
chat-text
= Message.find_by(id: entry.room.message_ids.last)&.body
= Message.find_by(id: entry.room.message_ids.last)&.body
ここではroomの最後のメッセージを表示しています。
def show
@room = Room.find(params[:id])
@messages = @room.messages.all
@message = Message.new
@entries = @room.entries
@another_entry = @entries.where.not(user_id: current_user.id).first
end
end
ここでは@roomで1つのroomを表示
@messagesで過去のやりとりを全て表示し@messageで新しいメッセージを作るためのインスタンスを作成。
@@entriesと@another_entryでview側に相手の名前を表示させています。
rooms/show.html.haml
.title
%h2
= ("#{@another_entry.user.email}さんとのメッセージ")
- @messages.each do |m|
- if m.user_id == current_user.id
.mycomment
%p
= m.body
- else
.fukidasi
.faceicon
= image_tag m.user.email
.chatting
.says
%p
= m.body
.chat-form-box
= form_for @message do |f|
.chat-form-group
= f.text_field :body
= f.hidden_field :room_id, :value => @room.id
= f.submit "送信する"
@anoter_entryで相手の名前やメールアドレスで表示させることができます。
@messagesをそれぞれIDで分岐させることでLineっぽく自分のメッセージは右、相手のメッセージは左みたいなスタイルを作ることができます。
form部分では@messageの他にこのmessageがどのroomに所属しているかを判断するためにhidden_fieldにroomの情報を持たせてます。
そしてこのformで送られたパラメーターがmessages_controllerに渡ります。
messages_controller.rb
class MessagesController < ApplicationController
before_action :authenticate_user!
def create
message = Message.new(message_params)
message.user_id = current_user.id
if message.save
redirect_to room_path(message.room)
else
redirect_back(fallback_location: root_path)
end
end
private
def message_params
params.require(:message).permit(:room_id, :body)
end
end
最後にmessage部分。
rooms/show.html.hamlから送られたパラメータをcreateします。
やっていることは、自分のmessageかどうかで分岐させてtrueならroom_idとbodyをパラメーターで許可してcreateしています。
この辺はいつものcreate actionと同じですね。
完成
中間テーブルが2つあって複雑ですが1つ1つ取り出してみるとやっていることは初心者の僕でも理解できるような内容でした。