【Rails6】ActionCableを用いたリアルタイムグループチャット機能② ~グループ機能導入→完成まで~

#はじめに
こんにちは、とあるプログラミングスクールでメンターをしている者です。
今回Actioncableを用いたリアルタイムグループチャット機能の第二部となっております。
第一部をまだ読んでいない方はぜひそちらの方を読んでいただければと思います。
[【Rails6】ActionCableを用いたリアルタイムグループチャット機能① ~リアルタイムチャット完成まで~](https://qiita.com/kengoyam72/private/a66965e826c46a3d0a0c)

#参考記事
[Railsでグループチャット機能を実装](https://qiita.com/hexanitrobenzen/items/1673a5ae218a2013fcd4)

#本題
今回作成していくために使っている機能は以下の通りです。

* ユーザー認証・・・Devise
* リアルタイムチャットの実現・・・Actioncable

また第一部の実装が完了していることが前提となっております。

##作成順序
以下の手法で作成していきます。

1. チャット機能の作成
2. グループに参加するメンバーのみでチャットができるよう改良

第二部では、グループ作成→完成まで行います。

##開発環境
macOS Big Sur 11.4
Ruby2.7.4
Rails6.1.4.1
DB:sqlite3

##必要なファイルの作成
アソシエーション用に必要なファイルを作成していきます。

```:ターミナル
$ rails g controller room_users
$ rails g model room_user
```

```rb:db/migrate/マイグレーションファイル
class CreateRoomUsers < ActiveRecord::Migration[6.1]
  def change
    create_table :room_users do |t|
      t.references :room, foreign_key: true
      t.references :user, foreign_key: true
      t.timestamps
    end
  end
end
```

```:ターミナル
$ rails db:migrate
```

##モデルファイルの修正
モデルファイルの修正を行います。

```rb:app/models/user.rb
has_many :room_users, dependent: :destroy
has_many :rooms, through: :room_users, source: :room, dependent: :destroy
```

```rb:app/models/room.rb
class Room < ApplicationRecord
    has_many :messages
    has_many :room_users, dependent: :destroy
    has_many :users, through: :room_users, source: :user
end
```

```rb:app/models/room_user.rb
class RoomUser < ApplicationRecord
    belongs_to :room
    belongs_to :user
end
```
参考：外部キー周りでエラーが出ることがあるのでご注意ください。
参考：アソシエーションはroomとuserが1:N、userとroomが1:N
![スクリーンショット 2021-09-10 16.24.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1898687/8453ea3b-4ffa-ea94-90c0-0d537125061d.png)

##ルーティング
第一部のまま。

##コントローラーの作成
以下のようにコントローラーを書きます。

```rb:app/controllers/rooms_controller.rb
class RoomsController < ApplicationController
    before_action :set_group, only: [:edit, :update]

    def index
        @room_lists = Room.all.order(:id)
        @room_joining = RoomUser.where(user_id: current_user.id)
        @room_lists_none = "You don't join any groups."
    end

    def new
        @room = Room.new
        @room.users << current_user
    end

    def create
        @room = Room.new(room_params)
        if @room.save
            redirect_to rooms_path, notice: 'make a group.'
        else
            render :new
        end
    end

    def show
        @room = Room.find(params[:id])
        @messages = @room.messages
    end

    def edit
        # @group = Group.find(params[:id])
    end

    def update
        # @group = Group.find(params[:id])
        if @room.update(room_params)
            redirect_to rooms_path, notice: 'update your group.'
        else
            render :edit
        end
    end

    def destroy
        delete_room = Room.find(params[:id])
        if delete_room.destroy
            redirect_to rooms_path, notice: 'delete your group.'
        end
    end

    private
    def set_group
        @room = Room.find(params[:id])
    end

    def room_params
        params.require(:room).permit(:roomname, user_ids: [])
    end

end
```
before_actionの仕様に関しては自分で調べていただければと思います。

##ビューの作成
第一部とかぶっている所はありますが、以下のように記述しました。
デザインの導入で一部Bootstrapの機能を使っています。導入はしてもらった方が便利なのでぜひしてみてください。
[Bootstrap公式記事](https://getbootstrap.jp/)

```erb:app/views/rooms/index.html.erb
<h1>Real part</h1>
<div>
  <ul>
    <% @room_lists.each do |room| %>
      <li><%= link_to "ROOM#{room.id}", room_path(room) %></li>
    <% end %>
  </ul>
</div>

<div class="chat-group">
　  <h1>All chat groups</h1>
    <div class="group-search">
        <div class="group-search-form">
            <%= form_with(url: rooms_path, local: true) do |f| %>
                <%= f.text_field :keyword, placeholder: "search for group", class: "group-search-form-input" %>
                <%= f.submit 'search' %>
            <% end %>
        </div>
        <div class="group-new">
            <%= link_to 'make a group', new_room_path %>
        </div>

        <% if @room_joining == [] %>
            <%= @room_lists_none %>
        <% else %>
            <div class="group-list">
                <ul class="group-list-table">
                    <% @room_lists.each do |list| %>
                        <% if list.user_ids.include?(current_user.id) %>
                            <li class="group-list-table-each-group">
                                <%= link_to "/rooms/#{list.id}" do %>
                                    <%= list.roomname %>(<%= list.user_ids.count %>)
                                    <%= link_to 'edit', edit_room_path(list.id), method: :get %>
                                    <%= link_to 'delete', room_path(list.id), method: :delete %>
                                <% end %>
                            </li>
                        <% end %>
                    <% end %>
                </ul>
            </div>
        <% end %>

    </div>
    <div class="user-page">
        <%#= link_to 'ユーザーページへ', current_user %>
    </div>
</div>
```

```erb:app/views/rooms/new.html.erb
<h1>Create new group</h1>

<%= form_with(model: @room, local: true) do |f| %>
  <div class="group-new-form">
    <div class="gtoup-new-form-name">
      <%= f.label :roomname, 'group name' %>
      <%= f.text_field :roomname, class: 'form-control' %>
      <%= f.collection_check_boxes :user_ids, User.all, :id, :username %>
    </div>
    <%= f.submit '新規作成', class: "btn btn-primary" %>
  </div>
<% end %>
```

```erb:app/views/rooms/edit.html.erb
<h1>edit group</h1>

<%= form_with(model: @room, local: true) do |f| %>
  <div class="group-new-form">
    <div class="gtoup-new-form-name">
      <%= f.label :roomname, 'group name' %>
      <%= f.text_field :roomname, class: 'form-control' %>
      <%= f.collection_check_boxes :user_ids, User.all, :id, :username %>
    </div>
    <%= f.submit 'update', class: "btn btn-primary" %>
  </div>
<% end %>
```

```erb:app/views/rooms/show.html.erb
<h1>Real Chat room</h1>
<div id='messages' data-room_id="<%= @room.id %>">
  <%= render @messages %>
</div>

<form>
  <%= label_tag :content, 'Say something:' %>
  <input type="text" data-behavior="room_speaker">
</form>
```

#完成
これでリアルタイムグループチャットの完成となります。
まだデザインを整えたらどのように動くか確認は出来ていないので、デザイン時にはあまりコードをいじらずにデザインしていただければと思います。

#おまけ
今回が私が実装したコードをgithubの方に載せています。
もし、エラーがでて詰まったという場合は参考にしてください。→[参考コード](https://github.com/Kengo-Matsumoto-k/realtimechat)
また今回第二部で載せさせていただいた参考記事とコメント機能を合わせると、リアルタイムではないですがグループチャットが完成できます。そちらのコードも載せているのでぜひ参考にしてみてください。