LoginSignup
27
29

More than 1 year has passed since last update.

【Rails】非同期通信でチャット/DM機能を実装する

Last updated at Posted at 2021-04-11

ユーザー同士1対1のチャット機能を非同期通信(Ajax)で実装する方法をまとめています!
お気づきの点などあれば、コメント頂戴できれば幸いです。

前提

  • deviseでUserモデルを作成している
  • 各ユーザーの詳細画面(show)が作成されている(ユーザーの詳細画面にチャットへのリンクを作ります)

 👆以上の前提で進めていきます

使用するモデル

  • User = ユーザーの定義
  • Chat = チャットの定義
  • Room = チャットルームの定義
  • UserRoom = ユーザーとチャットルームの関連付けの定義(中間テーブル)

大まかな流れ

  1. モデル生成とアソシエーション
  2. コントローラー作成 & ルーティング設定
  3. chats_controllerに記述
  4. チャットのビューを記述
  5. create.js.erbファイルを作成&記述

STEP1: モデル生成とアソシエーション

まずUserモデル以外に必要なモデルを作成していきましょう!

terminal
$ rails g model Room
$ rails g model Chat user_id:integer room_id:integer message:text
$ rails g model UserRoom user_id:integer room_id:integer

$ rails db:migrate

モデルが生成できたら、アソシエーションを設定します。
(それぞれ前後の記述は省略しています)

app/models/user.rb

has_many :user_rooms, dependent: :destroy
has_many :chats, dependent: :destroy
app/models/user_room.rb
belongs_to :user
belongs_to :room
app/models/room.rb
has_many :chats
has_many :user_rooms  #1つのルームにいるユーザ数は2人のためhas_manyになる
app/models/chat.rb
 belongs_to :user
 belongs_to :room

👆user_roomsテーブルはusersテーブルとroomsテーブルの中間テーブルです。
外部キーとしてuser_idroom_idを持っています。

 user_roomsテーブルは、ユーザーとルームの紐づけを行います:
 - ユーザーにどのroom_idが紐づいているか
 - ルームにどのuser_idが紐づいているか

例えば、AさんとBさんのチャットの場合、
- Aさん(user_id=3)がroom_id=1
- Bさん(user_id=4)がroom_id=1

であれば、AさんとBさんは共通のroom_idを持ちます。

STEP2: コントローラー作成 & ルーティング設定

では、chats_controllershowアクションを一緒に作成します👇

terminal
$ rails g controller chats show

今回は、chats_controllershowアクションでチャットを行うので、以下のようにルーティングを設定しておきましょう。
ちなみにcreateアクションは後でコントローラに定義するように、コメントの投稿に必要になってきます。

config/routes.rb
get 'chat/:id', to: 'chats#show', as: 'chat'
resources :chats, only: [:create]

STEP3: chats_controllerに記述

まずはコメントなしの記述内容がこちら👇

app/controllers/chats_controller.rb
class ChatsController < ApplicationController

 def show
  @user = User.find(params[:id])
  rooms = current_user.user_rooms.pluck(:room_id)
  user_rooms = UserRoom.find_by(user_id: @user.id, room_id: rooms)

  if user_rooms.nil?
   @room = Room.new
   @room.save
   UserRoom.create(user_id: @user.id, room_id: @room.id)
   UserRoom.create(user_id: current_user.id, room_id: @room.id)
  else
   @room = user_rooms.room
  end

  @chats = @room.chats
  @chat = Chat.new(room_id: @room.id)
 end

 def create
  @chat = current_user.chats.new(chat_params)
  @chat.save
 end

 private

 def chat_params
  params.require(:chat).permit(:message, :room_id)
 end

end

それぞれ何をしているかコメントを入れたものがこちらです👇
※ちなみに、どちらのユーザーに関する記述なのかがわかるように「Aさん」「Bさん」がコメントに出てきますが、「AさんがBさんに対してチャットする」想定です。つまり、Aさんがcurrent_userになります。

app/controllers/chats_controller.rb
class ChatsController < ApplicationController

 def show
  #BさんのUser情報を取得
   @user = User.find(params[:id])
  #user_roomsテーブルのuser_idがAさんのレコードのroom_idを配列で取得
   rooms = current_user.user_rooms.pluck(:room_id)
  #user_idがBさん(@user)で、room_idがAさんの属するroom_id(配列)となるuser_roomsテーブルのレコードを取得して、user_room変数に格納
  #これによって、AさんとBさんに共通のroom_idが存在していれば、その共通のroom_idとBさんのuser_idがuser_room変数に格納される(1レコード)。存在しなければ、nilになる。
   user_room = UserRoom.find_by(user_id: @user.id, room_id: rooms)

  #user_roomでルームを取得できなかった(AさんとBさんのチャットがまだ存在しない)場合の処理  
   room = nil
   if user_room.nil?
   #roomのidを採番
    room = Room.new
    room.save
    #採番したroomのidを使って、user_roomのレコードを2人分(Aさん用、Bさん用)作る(=AさんとBさんに共通のroom_idを作る)
    #Bさんの@user.idをuser_idとして、room.idをroom_idとして、UserRoomモデルのがラムに保存(1レコード)
     UserRoom.create(user_id: @user.id, room_id: room.id)
    #Aさんのcurrent_user.idをuser_idとして、room.idをroom_idとして、UserRoomモデルのカラムに保存(1レコード)
     UserRoom.create(user_id: current_user.id, room_id: room.id)
   else
    #user_roomに紐づくroomsテーブルのレコードをroomに格納
     room = user_room.room
   end

   #roomに紐づくchatsテーブルのレコードを@chatsに格納
    @chats = room.chats
   #form_withでチャットを送信する際に必要な空のインスタンス
   #ここでroom.idを@chatに代入しておかないと、form_withで記述するroom_idに値が渡らない
    @chat = Chat.new(room_id: room.id)
  end

  def create
    @chat = current_user.chats.new(chat_params)
    @chat.save
  end

  private

  def chat_params
    params.require(:chat).permit(:message, :room_id)
  end

end

STEP4: チャットのビューを記述

チャット画面を記述していきましょう👇
なおform_withは非同期通信処理のためremote: trueにしています(省略可)。


以下の記述でチャット画面はこんな感じになります
image.png

※ビューの記述は練習用ということで最低限にしてあります。
 スタイリッシュな感じにぜひ仕上げてください。

app/app/views/chats/show.html.erb
<div class="container">
    <div class="row">
        <div class="col-xs-6">
            <h2>CHAT WITH <%= @user.name %></h2>

            <table class="message table">
              <thead>
                <tr>
                  <th style="text-align: left; font-size: 20px;"><%= current_user.name %></th>
                  <th style="text-align: right; font-size: 20px;"><%= @user.name %></th>
                </tr>
              </thead>
              <% @chats.each do |chat| %>
                <% if chat.user_id == current_user.id %>
                <tbody>
                  <tr>
                    <th>
                      <p style="text-align: left;"><%= chat.message %></p>
                    </th>
                    <th></th>
                  </tr>
                <% else %>
                  <tr>
                    <th></th>
                    <th>
                      <p style="text-align: right;"><%= chat.message %></p>
                    </th>
                  </tr>
                </tbody>
                <% end %>
              <% end %>
            </table>

            <%= form_with model: @chat, remote: true do |f| %>
              <%= f.text_field :message %>
              <%= f.hidden_field :room_id %>
              <%= f.submit "SEND", class:"btn btn-sm btn-success chat-btn" %>
            <% end %>
        </div>
    </div>
</div>

さて、ユーザーの詳細画面(show)から、そのユーザーとチャットができるように、ビューに記述を加えましょう👇

app/app/views/chats/show.html.erb
<!-- current_userと@userが一致していない場合に`Begin Chat`を表示 -->

<% if current_user != @user %>
  <%= link_to 'Begin Chat', chat_path(@user.id) %>
<% end %> 

もしusers_controllershowアクションに、@userが定義されていない場合は、忘れず記述してください👇

app/controllers/users_controller.rb
def show
  @user = User.find(params[:id])
end

STEP5. create.js.erbファイルを作成&記述

最後に、非同期通信の設定をしていきましょう。
といってもすでにform_withにremote: trueを記述したので、あとはcreate.js.erbファイルを作成&記述するだけです。

手動で構わないので、views/chatsフォルダ直下にcreate.js.erbファイルを作成して、以下を記述しましょう。

app/views/chats/create.js.erb
/*.messageを部分更新。appendメソッドで要素を追加 */
$('.message').append("<p style='text-align: left;'><%= @chat.message %></p>");

/*チャット投稿後、フォームの値に前回の投稿内容が残らないようにする */
$('input[type=text]').val("")

これで完成です!おつかれさまでした。

参考資料

27
29
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
27
29