🍎
【Rails】DM機能実装(相互フォロワーのみ利用可能)
はじめに
- フォロー機能実装済
- ユーザー同士で1対1のDMができるようにする(140字まで送信可能)
- 相互フォロワーのみDM機能が利用できる
- 完成イメージ(相互フォロワーだとDMボタンが表示される) ↓
チャットルーム ↓
1.テーブル設計確認
- DM機能には4つのテーブルを使用「Usersテーブル」「Entriesテーブル」「Roomsテーブル」「Messagesテーブル」
- DMは2人のユーザーがチャットルームにてメッセージをやりとりするイメージ
- ユーザーの組み合わせ毎にチャットルームが必要なため、Usersテーブル:Roomsテーブルは多:多
- そのため、中間テーブルとしてEntriesテーブルを用意
- ユーザー同士は複数のメッセージをやりとりする可能性があるため、中間テーブルとしてMessagesテーブルを用意
2.モデル作成
ターミナル
$ rails g model Entry user:references room:references
$ rails g model Room user:references
$ rails g model Message user:references room:references message:text
ターミナル
$ rails db:migrate
3.アソシエーション設定
app/models/user.rb
has_many :entries, dependent: :destroy
has_many :messages, dependent: :destroy
app/models/entry.rb
belongs_to :user
belongs_to :room
app/models/room.rb
has_many :entries, dependent: :destroy
has_many :messages, dependent: :destroy
app/models/message.rb
belongs_to :user
belongs_to :room
validates :message, presence: true, length: { maximum: 140 }
#メッセージが空欄はNG、かつ140字以内
4.ルーティング設定
config/routes.rb
:
resources :users, only: [:index, :show, :edit, :update ] do
member do
get :follows, :followers
end
resource :relationships, only: [:create, :destroy]
end
:
resources :messages, only: [:create]
resources :rooms, only: [:create, :show]
1.相互フォロワーのユーザーページで「DMを送る」というボタンを表示させる
2.ボタンを押すとroomsがcreateされる
3.roomsのshowページへ遷移する
4.roomsのshowページでmessageがcreateされる
5.コントローラー作成
ターミナル
$ rails g controller rooms
$ rails g controller messages
6.Usesコントローラー編集
app/controllers/users_controller.rb
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 then
@is_room = true
@room_id = current.room_id
end
end
end
if @is_room
else
@room = Room.new
@entry = Entry.new
end
end
@book = Book.new
@books = @user.books
end
解説!
app/controllers/users_controller.rb
@user = User.find(params[:id])
@current_entry = Entry.where(user_id: current_user.id)
@another_entry = Entry.where(user_id: @user.id)
- showページ用にレコードからユーザー1人1人の情報を取得する必要があるため、findメソッドを使用
- roomがcreateされた際、ログインしているユーザーをEntriesテーブルに記録するためにwhereメソッドでcurrent_user.idを探す
- roomがcreateされた際、「DMを送る」ボタンを押された側のユーザーをEntriesテーブルに記録するためにwhereメソッドでuser.idを探す
app/controllers/users_controller.rb
unless @user.id == current_user.id
@current_entry.each do |current|
@another_entry.each do |another|
if current.room_id == another.room_id then
@is_room = true
@room_id = current.room_id
end
end
end
if @is_room
else
@room = Room.new
@entry = Entry.new
end
end
@book = Book.new
@books = @user.books
end
- unlessを使用することでroomsが作成されている場合とされていない場合に条件分岐させる
- 作成済みであれば、「@current_entry」と「@another_entry」をeachで一つずつ取り出し、それぞれEntriesテーブル内にあるroom_idが共通しているユーザー同士に対して「@room_id = current.room_id」という変数を指定
- ここで、すでに作成されているroom_idを特定することができる
- 「@is_room = true」は、これがfalseであるとき、else以降で新たにroomを作成する
7.ビュー編集
app/views/users/show.html.erb
<% unless @user.id == current_user.id %>
<% if (current_user.following? @user) && (@user.following? current_user) %>
<% if @is_room == true %>
<p class="user-show-room"><a href="/rooms/<%= @room_id %>" class="btn btn-primary btn-sm">DMを送る</a></p>
<% else %>
<%= form_for @room do |f| %>
<%= fields_for @entry do |e| %>
<%= e.hidden_field :user_id, value: @user.id %>
<% end %>
<%= f.submit "DMをはじめる", class:"btn btn-primary btn-sm" %>
<% end %>
<% end %>
<% end %>
<% end %>
解説!
app/views/users/show.html.erb
<% unless @user.id == current_user.id %>
<% if (current_user.following? @user) && (@user.following? current_user) %>
<% if @is_room == true %>
<p class="user-show-room"><a href="/rooms/<%= @room_id %>" class="btn btn-primary btn-sm">DMを送る</a></p>
<% else %>
:
:
<% end %>
<% end %>
<% end %>
- 現在ログインしているユーザーではない かつ 相互フォロー状態 の時、で条件分岐させる
- 「@is_room」を使用してroomsが作成されているか条件分岐させる
- 「@is_room」がtrueの時、「DMを送る」ボタンが表示され、チャットルームに移動できる
app/views/users/show.html.erb
<%= form_for @room do |f| %>
<%= fields_for @entry do |e| %>
<%= e.hidden_field :user_id, value: @user.id %>
<% end %>
<%= f.submit "DMをはじめる", class:"btn btn-primary btn-sm" %>
<% end %>
- 「@is_room」がfalseの時、form_forを使用してコントローラーにパラメーターを送る
- レコードには親モデルのRoomsテーブルと子モデルのEntriesテーブルの両方を保存する必要があるため、親モデルには「form_for インスタンス変数」, 子モデルには「fields_for インスタンス変数」としている
- Entriesテーブルのレコードにはuser_idを送る必要があるため、「hidden_field」で@user.idをvalueにおく
ここまでで、roomsテーブルに保存されるための準備が整った!
8.roomsコントローラー編集
app/controllers/rooms_controller.rb
class RoomsController < ApplicationController
before_action :authenticate_user!
def create
@room = Room.create(user_id: current_user.id)
@current_entry = Entry.create(user_id: current_user.id, room_id: @room.id)
@another_entry = Entry.create(params.require(:entry).permit(:user_id, :room_id).merge(:room_id => @room.id))
redirect_to room_path(@room)
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
@my_account = current_user.id
else
redirect_back(fallback_location: root_path)
end
end
end
解説!
app/controllers/rooms_controller.rb
class RoomsController < ApplicationController
def create
@room = Room.create(user_id: current_user.id)
@current_entry = Entry.create(user_id: current_user.id, room_id: @room.id)
@another_entry = Entry.create(params.require(:entry).permit(:user_id, :room_id).merge(:room_id => @room.id))
redirect_to room_path(@room)
end
:
end
- users/show.html.erbの「<%= form_for @room do |f| %>」で送られてきたパラメータを受け取り、createさせる
- また、このcreateメソッドではRoom以外にその子モデルのEntryもcreateさせる必要があるため、Entriesテーブルに入る相互フォロー同士のユーザーを保存させるための記述をする
- ログイン中のユーザーに対しては「@current_entry」とし、EntriesテーブルにRoom.createで作成された@roomにひもづくidと、ログイン中のユーザーのidを保存させる記述をする
- フォローされている側のユーザーは「@another_entry」とする。users/show.html.erbの「fields_for @entry」で保存したparamsの情報(:user_id, :room_id)を許可し、ログイン中のユーザーと同じく@roomにひもづくidを保存する記述をする
- そしてcreateと同時にチャットルームが開くようにredirectをする
app/controllers/rooms_controller.rb
class RoomsController < ApplicationController
:
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
@my_account = current_user.id
else
redirect_back(fallback_location: root_path)
end
end
end
- roomsのshowアクションでは、まず1つのチャットルームを表示させる必要があるため、findメソッドを使う
- Entriesテーブルにログイン中のユーザーのidと、それにひもづいたチャットルームのidをwhereメソッドで探し、そのレコードがあるかを確認する
- 条件がfalseだった場合、redirect_backで前のページに戻る
- 条件がtrueだった場合、Messagesテーブルにそのチャットルームのidとひもづいたメッセージを表示させるために@messagesにアソシエーションを利用した「@room.messages」という記述を代入する
- 新しくメッセージを作成する場合、メッセージのインスタンスを生成するためにMessage.newで「@message」に代入する
そしてrooms/show.html.erbでユーザーの情報を表示させるために@room.entriesを@entriesというインスタンス変数に入れ、Entriesテーブルのuser_idの情報を取得
- 「@my_account = current_user.id」はチャットルームで「〇〇さんとのメッセージ」と相手の名前を表示させるための記述
9.rooms/show ビュー作成
テーブルタグを使用してこのようなページを作成!
app/views/rooms/show.html.erb
<div class="container">
<div class="row">
<% @entries.each do |e| %>
<% if @my_account != e.user.id %>
<h4><strong><%= e.user.name %>さんとのメッセージ</strong></h4>
<% end %>
<% end %>
</div>
<div class="row my-3">
<%= link_to "Usersページに戻る", users_path, class:"btn btn-secondary btn-sm" %>
</div>
<div class="row">
<table class="table table-hover">
<% if @messages.present? %>
<% @messages.each do |m| %>
<tr>
<td><%= m.user.name %></td>
<td><%= m.message %></td>
</tr>
<% end %>
<% end %>
</table>
</div>
<div class="row">
<%= 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:"btn btn-success"%>
<% end %>
</div>
</div>
解説!
app/views/rooms/show.html.erb
<div class="row">
<% @entries.each do |e| %>
<% if @my_account != e.user.id %>
<h4><strong><%= e.user.name %>さんとのメッセージ</strong></h4>
<% end %>
<% end %>
</div>
- 「@entries」の相互フォロワー同士の情報をとりたいため、eachでフォロー・フォロワーの情報を取得している
- 「<% if @my_account != e.user.id %>」rommsコントローラーで@my_accountにログイン中のユーザーIDを代入しているため、「!=」とすることでDM送信相手の名前を表示させる
app/views/rooms/show.html.erb
<div class="row">
<table class="table table-hover">
<% if @messages.present? %>
<% @messages.each do |m| %>
<tr>
<td><%= m.user.name %></td>
<td><%= m.message %></td>
</tr>
<% end %>
<% end %>
</table>
</div>
- <% if @messages.present? %>でDMが存在するかを確認し、メッセージのやりとりがあればeachでメッセージを表示させる
app/views/rooms/show.html.erb
<div class="row">
<%= 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:"btn btn-success"%>
<% end %>
</div>
</div>
- DMを送信するためのフォームを、form_forで作成
- <%= form_for @message do |f| %>では、@messageの他にこのmessageがどのroomに所属しているかを判断するためにhidden_fieldにroomの情報を持たせる
- このformで送られたパラメーターがmessages_controllerに送られる
10.messagesコントローラー編集
app/controllers/messages_controller.rb
class MessagesController < ApplicationController
before_action :authenticate_user!, :only => [:create]
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, :message)
end
end
- rooms/show.html.erbから送られたパラメータをcreateする
ここまでで相互フォロワーであればDMを送信できるようになりました!!!
- 相互フォロワーではない状態
- 相互フォロワー、DM未送信状態
- DM送信後
- DMメッセージ用のページ
こちらの記事を参考にさせていただきました🙏
Discussion
コメント失礼します!
この記事であるER図って何を使って描かれているのでしょうか。
「draw.io」というツールを使いました!
通っていたオンラインスクールでおすすめされたので使ってます🙌
ありがとうございます😊
ER図かけるツール探していたので助かります🙇🏻♂️
コメント失礼します。この記事を参考にDM機能を実装しようとしたら、room_idにnilが入ってしまいました。何か対処法はありませんか?
以下はターミナルでのログです
初めまして!
原因か定かではないのですが、自分の投稿を確認していたところアソシエーションの記述に誤りがありました…すみません!
正しくは下記です🙇
それ以外ですとusers/show.html.erbのform_forで正しく情報が送られていないことでroom_idがnilになっているのかと思うのですが解決策がわからずです😥
アソシエーションを正しく記述したら、無事room_idに値が入りました。ありがとうございます。
返信遅くなり大変失礼しました。😞
よかったです🙌✨
コメントありがとうございました^^
記述ミス気を付けますっ!!