🍼

Rails DM機能の実装

2023/04/23に公開

DM機能の作成

  • ユーザ同士で 1 対 1 の DM ができるようにする
    ※ 140 字まで送信可能にする

  • 相互フォローしている人限定で DM 機能を使えるようにする

こちらの記事を参考させていただきました!
https://qiita.com/bindingpry/items/6790c91f374acc25bea2#users_controller

テーブル設計

ユーザーを管理する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の使い方
https://pikawaka.com/rails/form_for

レコードには親モデルのRoomsテーブルと、子モデルの Entriesテーブルの両方を保存する必要があるので、親モデルにform_forインスタンス変数、子モデルにfields_forインスタンス変数としている。

fields_forについて
https://qiita.com/mi-1109/items/82bde86493072e2dd2a4

そして、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メソッドについて
https://zenn.dev/igaiga/books/rails-practice-note/viewer/ar_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