メッセージ機能を読み解く
目標
しっかりと理解できていないメッセージ機能についてコードの意味を理解する。
参考サイト
「RailsでDM(ダイレクトメッセージ)を送れるようにしよう」
https://qiita.com/nojinoji/items/2b3f8309a31cc6d88d03
イメージ
deviseにてuserモデルを作成、user間でメッセージのやり取りができるようになる。
Userモデル: User情報
Roomモデル: 今回はチャットルームに2人のUserが入るイメージ
Entryモデル: どのUserがどのRoomに所属しているか
Messageモデル: UserがどのRoomでどんなMessageを送ったか
*すでにdeviseインストール、userモデル作成は行っていることとする。
内容
まずアソシエーションを行う。
user.rb
user.rb
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_many :messages, dependent: :destroy
has_many :entries, dependent: :destroy
end
entry.rb
entry.rb
class Entry < ApplicationRecord
belongs_to :user
belongs_to :room
end
room.rb
room.rb
class Room < ApplicationRecord
has_many :messages, dependent: :destroy
has_many :entries, dependent: :destroy
end
message.rb
message.rb
class Message < ApplicationRecord
belongs_to :user
belongs_to :room
end
ここはそのままなので割愛
続いてコントローラーの作成
$ rails g controller users index show
$ rails g controller rooms
$ rails g controller messages
users_controller.rb
class UsersController < ApplicationController
before_action :authenticate_user!, :only => [:show]
def index
@users=User.all
end
def show
@user=User.find(params[:id])
@currentUserEntry=Entry.where(user_id: current_user.id)
@userEntry=Entry.where(user_id: @user.id)
unless @user.id == current_user.id
@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
unless @isRoom
@room = Room.new
@entry = Entry.new
end
end
@user=User.find(params[:id])
@currentUserEntry=Entry.where(user_id: current_user.id)
@userEntry=Entry.where(user_id: @user.id)
showの部分を読み解く。
まずこの部分だが一番上はuser#showをリクエストした際に押したuser.idの情報をfindメソッドで検索し、インスタンス変数@userへ代入している。
2行目はwhereメソッドを使用してcurrent_userのidから検索した結果を@currentUserEntryへ代入している。
3行目whereメソッドを使用して1行目に検索したインスタンス変数userのidから検索した結果を@UserEntryへ代入している。
ここで2つの変数を定義している。そしてroomがcreateされた時に、現在ログインしているユーザーと、「チャットへ」を押されたユーザーの両方をEntriesテーブルに記録する必要があるので、whereメソッドでそのユーザーを探しているということである。
最初whereメソッドをなぜ使っているのか疑問であったがここはあくまでentryテーブルに指定された2つのuser.idが登録されているかを確認するためのものなので登録されていない場合はfindメソッドとかを使うと見つかりませんとエラーがでます。
unless @user.id == current_user.id
@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
unless @isRoom
@room = Room.new
@entry = Entry.new
end
end
ここではunless文を使用し@userとcurrent_userが同一でないかを確認。もし異なっていれば以下のコードが通るように記入している。@currentUserEntryと@userEntryの2つの変数の情報をブロック引数として渡し、2つの引数の中に同じRoom_idがあるかを確認する。もしあれば@isRoomにtrueを代入。current_userのroom_idを@roomIdに代入している。もし2つのEntryに同じroom_idが見つからない場合新しくRoomとEntryを作るような処理も追加している。
<% unless @user.id == current_user.id %>
<% if @isRoom == true %>
<p><a href="/rooms/<%= @roomId %>">メッセージを送る</a>
<% else %>
<%= form_for @room do |f| %>
<%= fields_for @entry do |e| %>
<%= e.hidden_field :user_id, :value=> @user.id %>
<% end %>
<%= f.submit "メッセージを送る" %>
<% end %>
<% end %>
<% end %>
続いてviewをみる。
コントローラーと同様に、すでにチャットルームが作成している時と作成されていない時の条件分岐をさせるため、@isRoomを使用。
@isRoomがtrueの時は、チャットへボタンを出現させ、すでに作成されたチャットへと移行することができる。
また、チャットルームがまだ作成されていない場合は
@room = Room.new
@entry = Entry.new
にパラメーターを送らなければならない。
ここには親モデルの@roomにform_forインスタンス変数、@entryにfields_forインスタンス変数とする。
ここまででRoomsテーブルに保存されるための準備が整う。
rooms_controller.rb
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 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
次にroomsコントローラーを理解する。
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
createでは新しくRoomとEntryを作成する。Entryはcurrent_userとshowのuserの2つ分を登録する。
@room = Room.createでRoom_idを作成。
@entry1 = Entry.create(room_id: @room.id, user_id: current_user.id)
でcurrent_userのidと新しくできたroomのidを保存(current_userのEntry情報)
@entry2=Entry.create(params.require(:entry).permit(:user_id, :room_id).merge(room_id: @room.id))
でuser/showで代入した@userの情報をidへ代入。そこに新しくできた:room_idをmergeして登録する。
そしてcreateと同時に、チャットルームが開くようにredirectをする。
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
showではまず該当するチャットルームを表示する必要があるため、findメソッドでparamsを検索する。検索したroomにEntriesテーブルに、現在ログインしているユーザーのidとそれにひもづいたチャットルームのidをwhereメソッドで探し、そのレコードがあるか確認する。もしその条件がfalseだったら、前のページに戻るための記述である、redirect_backを使う。
もしその条件がtrueだったら、Messageテーブルにそのチャットルームのidと紐づいたメッセージを表示させるため、@messagesにアソシエーションを利用した@room.messagesという記述を代入。
また、新しくメッセージを作成する場合は、メッセージのインスタンスを生成するために、Message.newをし、@messageに代入させる。
そしてrooms/show.html.erbでユーザーの名前などの情報を表示させるために、@room.entriesを@entriesというインスタンス変数に入れ、Entriesテーブルのuser_idの情報を取得します
messages_controller.rb
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
ここではメッセージをcreateする仕組みについて記述している。
form_forで送られてきたcontentを含む全てのメッセージの情報の:messageと:room_idのキーがちゃんと入っているかということを条件で確認する。
もしその条件がtrueだったら、メッセージを保存するためにMessage.createとし、Messagesテーブルにuser_id、:content、room_idのパラメーターとして送られてきた値を許可。
メッセージを送ったのは現在ログインしているユーザーなので、そのuser_idの情報をmergeさせます。
そしてその条件がfalseだった場合、フラッシュメーセージを出すための、flash.now[:alert]という記述をします。
最後にredirectで、どちらの条件の場合も元のページへとredirectさせれば完成です。