Rails DM機能の実装
DM機能の作成
-
ユーザ同士で 1 対 1 の DM ができるようにする
※ 140 字まで送信可能にする -
相互フォローしている人限定で DM 機能を使えるようにする
こちらの記事を参考させていただきました!
テーブル設計
ユーザーを管理するUsersテーブル
ユーザーはRoomsテーブルに属していて、相互フォロワー同士で1つずつチャットルームが作られていく。(1つのチャットルームに入るユーザーは複数2人というわけ)
1人1人のユーザーは他のユーザーと相互フォロー関係になり、たくさんのチャットルームを持つ可能性がある。そのため、UsersテーブルとRoomsテーブルは多対多の関係になる!
この様な多対多の関係をテーブルにする場合に、中間テーブルというテーブルが必要になる!!
Entriesテーブルでチャットルームに入っているユーザー情報を管理
また、Roomsでは複数(2人)のユーザーが複数のメッセージを送るので多対多の関係になる。
Messagesテーブルでユーザーが送ったメッセージ情報を管理
rails g model room user:references
rails g model Entry user:references room:references
rails g model message user:references room:references message:text
👇
rails db:migrate
アソシエーションの記述
user.rb
has_many :messages, dependent: :destroy
has_many :entries, dependent: :destroy
room.rb
has_many :messages, dependent: :destroy
has_many :entries, dependent: :destroy
entry.rb
belongs_to :user
belongs_to :room
message.rb
belongs_to :user
belongs_to :room
validates :message, presence: true, length: { maximum: 140 }
空でない&最大140文字以下であるバリデーションも追加しておく!
コントローラの作成
rails g controller rooms
rails g controller messages
ルーテイングの設定
routes.rb
resources :users, only: [:show,:edit,:update]
resources :messages, only: [:create]
resources :rooms, only: [:create,:show]
流れの整理
相互フォローになったら、相手のusers/showページで「チャットへ」のボタンが表示される。
👇
ボタンを押すと、roomsがcreateされる。
👇
rooms/showページに移る。
👇
room/showページでmessageがcreateされる。
コントローラーとビューの設定
users_controller
class UsersController < ApplicationController
before_action :authenticate_user!, only: [:show]
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
else
@currentUserEntry.each do |cu|
@userEntry.each do |u|
if cu.room_id == u.room_id then
@isRoom = true
@roomId = cu.room_id
end
end
end
if @isRoom
else
@room = Room.new
@entry = Entry.new
end
end
end
〜解説〜
@user = User.find(params[:id])
は、showページのためにレコードからユーザー1人1人の情報を持ってくる必要があるため、findメソッドを使っている。
@currentUserEntry=Entry.where(user_id: current_user.id)
@userEntry=Entry.where(user_id: @user.id)
は、roomがcreateされた時に現在ログインしているユーザーと、
「チャットへ」ボタンを押されたユーザーの両方をEntriesテーブルに記録する必要があるので、
whereメソッドを使いもう1人のユーザーを探している。
whereメソッドとは、テーブル内の条件に一致したレコードを取得することができるメソッド。
if @user.id == current_user.id
はじめに、現在ログインしているユーザーではないという条件をつけます。
そして、すでにroomが作成されている場合とされていない場合に分岐させます。
作成されている場合、 @currentUserEntry
と@userEntry
をeachで一つずつ取り出し、
それぞれEntriesテーブル内にあるroom_id
が共通しているユーザー同士に対して
@roomId = cu.room_id
という変数を指定する。
これですでに作成されているroom_id
を特定できる!
@isRoom = true
は、falseのとき(Roomを作成するとき)の条件を分岐するための記述。
そのため、
if @isRoom
内では、新しくインスタンスを生成するために.newを記述する。
users/show.html.erb
<% unless @user.id == current_user.id %>
<% if (current_user.following? @user) && (@user.following? current_user) %>
<% if @isRoom == true %>
<p class="user-show-room"><a href="/rooms/<%= @roomId %>" class="btn btn-primary btn-lg">チャットへ</a>
<% 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-primary btn-lg user-show-chat"%>
<% end %>
<% end %>
<% end %>
<% end %>
<% unless @user.id == current_user.id %>
まず、現在ログインしているユーザーではないという条件式をつけ、
<% if (current_user.following? @user) && (@user.following? current_user) %>
相互フォロー状態のときの条件式も付け足す。
そして、コントローラーと同様にすでにチャットルームが作成されている時とされていない時の条件分岐をさせるため、@isRoom
を使用。
@isRoomがtrueの時は、チャットボタンを出現させすでに作成されたチャットへと移行することができる。
<%= form_for @room do |f| %>
<%= fields_for @entry do |e| %>
<%= e.hidden_field :user_id, value: @user.id %>
<% end %>
<%= f.submit "チャットを始める", class:"btn btn-primary btn-lg user-show-chat"%>
<% end %>
falseの場合、form_forを使って、コントローラーにパラメーターを送るための記述をする。
form_forの使い方
レコードには親モデルのRoomsテーブルと、子モデルの Entriesテーブルの両方を保存する必要があるので、親モデルにform_forインスタンス変数、子モデルにfields_forインスタンス変数としている。
fields_forについて
そして、Entriesテーブルのレコードにはuser_idを送る必要があるので、hidden_field(隠しフィールドの作成)で@user.id
をvalueにおく。
これで、roomsテーブルに保存されるための準備が整った!
rooms_controller
class RoomsController < ApplicationController
before_action :authenticate_user!
def create
@room = Room.create(user_id: current_user.id)
@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 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
#Roomで相手の名前表示するために記述
@myUserId = current_user.id
else
redirect_back(fallback_location: root_path)
end
end
end
まずcreate部分について!
users/show.html.erbのform_forの@room
で送られてきたパラメータを、ここで受け取りcreateさせる。
ここでは、Room以外にその子モデルのEntryもcreateさせなければいけないので、
Entriesテーブルに入る相互フォロー同士のユーザーを保存させるための記述を行う。
現在ログインしているユーザーに対しては、@entry1
として、
EntriesテーブルにRoom.create
で作成された@room
に紐づくidと、
現在ログインしているユーザーのidを保存させる記述をする。
@entry2
では、フォローされている側の情報をEntriesテーブルに保存するための記述。
users/show.html.erbのfields_for @entry
で保存したparamsの情報(:user_id, :room_id)を許可し、現在ログインしているユーザーと同じく@room
にひもづくidを保存する記述をしている。
mergeメソッドについて
そして、チャットルームが開くようにredirectをしている。
showアクションでは、まず1つのチャットルームを表示させる必要があるので、findメソッドを使用。
条件でまず、Entriesテーブルに、現在ログインしているユーザーのidと
それにひもづいたチャットルームのidをwhereメソッドで探し、そのレコードがあるか確認。
もしその条件がfalseだったら、前のページに戻るための記述である、redirect_backを使う。
trueだったら、Messageテーブルにそのチャットルームのidと紐づいたメッセージを表示させるため@messages
にアソシエーションを利用した@room.messages
という記述を代入。
また、新しくメッセージを作成する場合は、メッセージのインスタンスを生成するために、
Message.newをし、@message
に代入させる。
そしてrooms/show.html.erbmでユーザーの名前などの情報を表示させるために、
@room.entries
を@entries
というインスタンス変数に入れ、
Entriesテーブルのuser_id
の情報を取得する。
rooms/show.html.erb
デザインはまだ整えていないのですが、こんな感じで
簡単に名前とメッセージを表示できるように実装!
<% @entries.each do |e| %>
<% if @myUserId != e.user.id %>
<h2><%= e.user.name %>さんとのトークルーム</h2>
</div>
<% end %>
<% end %>
<%= link_to "ユーザー一覧に戻る", users_path %>
<% if @messages.present? %>
<% @messages.each do |m| %>
<% @myUserId == m.user.id %>
<p><%= m.user.name %></p>
<p><%= m.message %></p>
<% end %>
<% end %>
<%= form_for @message do |f| %>
<%= f.text_field :message, placeholder: "メッセージを入力して下さい" , size: 50, class:"form-text-field" %>
<%= f.hidden_field :room_id, value: @room.id %>
<%= f.submit "投稿",class: 'form-submit'%>
<% end %>
@entriesの相互フォロワー同士に情報をとってきたいため、eachでフォロ・フォロワーの情報を取得している。
<% if @myUserId != e.user.id %>
ここは相手の名前だけを表示するための記述。
room_controllerで定義した@myUserId = current_user.id を使用。
そしてEntriesテーブルはアソシエーションを組んでいるので、Entriesテーブルのuser_idのレコードを参照し、Usersテーブルのidnameの情報をとってきている。
<% if @messages.present? %>
<% @messages.each do |m| %>
<% @myUserId == m.user.id %>
<p><%= m.user.name %></p>
<p><%= m.message %></p>
<% end %>
<% end %>
メッセージの表示の部分は、rooms_controllerで記述したインスタンス変数@messages
を使い、メッセージが入っているかどうかif文で確認している!
もしメッセージが入っていたら、そのメッセージの情報ごとに表示するため、eachメソッドを使う。
ここでも先ほど同じようにアソシエーションを組んでいるので、Messagesテーブルに紐づいたuser_id
から、nameとmessageを表示させる。
<%= form_for @message do |f| %>
<%= f.text_field :message, placeholder: "メッセージを入力して下さい" , size: 50, class:"form-text-field" %>
<%= f.hidden_field :room_id, value: @room.id %>
<%= f.submit "投稿",class: 'form-submit'%>
<% end %>
そして、新しく作成するメッセージを保存させるためにform_forを使って、パラメーターとして送る記述を書く。
form_for
ではユーザーが入力したメッセージの内容を取得するため、f.text_field :message
と置いている。
また、どのチャットルームのメッセージが判断するために、f.hidden_fieldで:room_id
のバリューとして、そのチャットルームでのidを取得している!
f.submit
とすることで、投稿ボタンが押された時点で、messages_controller
にパラメータが飛んでいく!!
messages_controller
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(:message,: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
rooms/show.html.erbで送られてきた、form_for
のパラメータを実際に保存させるための記述。
form_for
で送られてきたmessage
を含む全てのメッセージの情報の:messageと:room_id
のキーがちゃんと入っているかということを条件で確認する。
もしその条件がtrueだったら、メッセージを保存するためにMessage.create
とし、Messagesテーブルに:message、user_id、room_id
のパラメーターとして送られてきた値を許可。
メッセージを送ったのは現在ログインしているユーザーなので、そのuser_idの情報をmergeさせる。
最後にredirectで、どちらの条件の場合も元のページへとredirectさせれば完成。
falseの場合はredirect_back
bootstrapで最後にレイアウト調整しました💪🏻
今までの中で一番難しかった気がするけど、ひとつひとつしっかりコードを分解して理解しようと努力して何とかできた!
わからないメソッドなどもっと細かく調べたかったが今日は時間がないのでここまで><
Discussion