【Rails】初学者向け_基礎のみを活用しチャット機能を作る方法

## 1.バージョンを確認しましょう!

Rails 5.1.6
ruby 2.7.2p112

## 2.完成イメージ

ログイン済みのユーザーで一つのチャットルームを共有し、交流できる機能です。
同じ名前のグループは作成できないようにしてあります。
ぜひclosedな環境でグループを作成し、お話してみてください(^_^)v


## 3.実装の大きな流れ

>１．グループチャット機能に必要なModel、Controller、Viewの実装
>２．グループ系モデルとUserモデルの紐付け
>３．ちょっとしたフロントエンドデザイン

ちなみにUserモデルは既に作られている前提で記事を書いています。
筆者はdeviseというgemを使ってUser周りを整えております。

ちなみに本記事はこのような目次で進んで行きます！
一緒に楽しんでいきましょう ᕙ( ˙꒳​˙ )ᕗ

## 4. 必要ファイルを作成しましょう

```ターミナル
$ rails g controller groups  #groups_controller.rbを作成
$ rails g controller chats   #chats_controller.rbを作成
$ rails g model group        #Group.rbを作成
$ rails g model group_user   #Group_user.rbを作成
```

Groupsテーブルが**チャット周りの情報を格納する**テーブルになってます✊
このGroupには幾つかのUser情報が含まれるので、
`Group_user`という中間テーブルを作ってあげましょう。

上記3つのファイルの作成が終わったら、
少しマイグレーションファイルに記述を書き加えていきましょう✊

```~~_create_groups.rb
class CreateGroups < ActiveRecord::Migration[6.1]
  def change
    create_table :groups do |t|

      # 追記ここから
      t.string :name, null: false
      t.index :name, unique: true
      # 追記ここまで

      t.timestamps
    end
  end
end
```

ちょっとコードの確認をしましょうか。
`t.string :name, null: false`はよく見ますよね。groupsテーブルにnameカラムを追加しています。
`null: false`は「データがない状態は許さへんで！」ってことです！

`t.index :name, unique: true`というこの行。これは**ユニーク制約**と呼ばれるものです。
簡単に述べると「同じデータ」を保存しないということです。

同じグループ名を作ることはできないということですね。

```~~_create_group_users.rb
class CreateGroupUsers < ActiveRecord::Migration[6.1]
  def change
    create_table :group_users do |t|

      # 追記ここから
      t.references :group, foreign_key: true
      t.references :user, foreign_key: true
      # 追記ここまで

      t.timestamps
    end
  end
end
```

モデルを作る際に`references`を追記することで、指定したモデルとのアソシエーション関係を自動で作ることができます。これはとても便利なのです。

ただ実はこんな感じでターミナルでまとめて記述することもできます。
Groupだけでなく、その中でお話するメッセージもテーブル設計したいので、下記を記述しましょう。

```ターミナル
$ rails g model Chat content:string user:references group:references 
```
後はGroupとUserが紐付いているいる必要があるため、下記のようにGroupsテーブルにuser_idを格納できるようにしましょう。

```ターミナル
$ rails generate migration AddUserIdToGroups user_id:integer
```

最後に忘れないうちにマイグレーションをしておきましょう〜！^^

```ターミナル

$ rails db:migrate

```

# 4.モデルファイルに追記

下記のモデルをこんな感じで編集していきましょう ᕙ( ˙꒳​˙ )ᕗ

こんな感じでカラムは入っていませんか？？
下記のように直していきましょう！！

```app/models/user.rb
  has_many :group_users, dependent: :destroy #追加
  has_many :groups, through: :group_users, dependent: :destroy #追加
  has_many :chats, dependent: :destroy #追加
```
`through`は中間テーブルを通して(through)その先を参照するという事です。
つまり中間テーブルを介してgroupモデル、ないしはuserモデルにアクセスするという意味ですね。

```app/models/group.rb
class Group < ApplicationRecord
    belongs_to :user
    has_many :chats, dependent: :destroy #追加
    has_many :group_users, dependent: :destroy #追加
    has_many :users, through: :group_users, dependent: :destroy #追加
    validates :name, presence: true, uniqueness: true
end
```

`validates :name, presence: true, uniqueness: true`は先程の同じ名前のグループを生成しないようにしています。`uniqueness`は**独自性**という意味ですからね。

```app/models/group_user.rb
class GroupUser < ApplicationRecord
    belongs_to :group
    belongs_to :user
end
```

ちなみに、ここまでの作業で自然と**Userモデル**と**Groupモデル**のアソシエーションが完了しています。
ぜひ自分の目を駆使しながら、どこでアソシエーションを使っていたか見抜いてみてください👀

# 5.ルーティングについて

ルーティングは下記を記載しましょう✊

```app/config/routes.rb
    resources :groups, only: [:index, :new, :create, :show, :edit, :update, :destroy] do
      resources :chats, only: [:create]
    end
```

ルーティングについて少しおさらいしましょう。
アプリケーション開発していくとRoutingの記述がかなり増えてきて、とても見辛くなってしまいます。
そこでresourcesが登場するのでした。

routingにて特定のコントローラーに対してresoucesを記述することによりindex、create、new、edit、show、update、destroyの標準的な機能を想定したルーティングを設定してくれます。

しかしいま実装したいのは、`group_idが1`のURL内で複数のチャット投稿をしていく場合です。
つまり`https://×××××××/group/1/chat/2`のようなURLになるのでした。

このようなときに上記のように`do~end`で付帯するresourcesを囲んであげるのですが、このような書き方をRoutingのネストと言います。

# 6.Controllerの記述

長く書きすぎたので、Controllerはあっさり書こうと思います。
まずchats_controllerから！

```app/controllers/chat_controller.rb
class ChatsController < ApplicationController
    before_action :authenticate_user!

    def create
      group = Group.find(params[:group_id])
      chat = group.chats.build(chat_params) #buildを使い、contentとtweet_idの二つを同時に代入
      chat.user_id = current_user.id
      if chat.save
        flash[:success] = "コメントしました"
        redirect_back(fallback_location: root_path)
      else
        flash[:success] = "コメントできませんでした"
        redirect_back(fallback_location: root_path)
      end
    end
  
    private
  
      def chat_params
        params.require(:chat).permit(:content)
      end
end

```

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

    def index
        @groups = Group.all
        @group_none = "グループに参加していません。"
    end

    def new
        @group = Group.new
    end

    def create
        @group = Group.new(group_params)
        @group.user_id = current_user.id
        if @group.save
            redirect_to groups_url, notice: 'グループを作成しました。'
        else
            render :new
        end
    end

    def show
        @group = Group.find(params[:id])
        @chats = @group.chats
        @chat = Chat.new
    end

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

    def update
        @group = Group.find(params[:id])
        if @group.update(group_params)
            redirect_to groups_path, notice: 'グループを更新しました。'
        else
            render :edit
        end
    end

    def destroy
        delete_group = Group.find(params[:id])
        if delete_group.destroy
            redirect_to groups_path, notice: 'グループを削除しました。'
        end
    end

    private
        def group_params
            params.require(:group).permit(:name, user_ids: [])
        end

end
```
`@group_none = "グループに参加していません。"`というコードにより、グループがない際はメッセージを出すように変数化しています。

# 7.Viewの記述


```html:app/views/group/index.html.erb
<%= stylesheet_link_tag 'groups', :media => "all" %>

 <li><%= link_to "グループを作成する", new_group_path %></li>

<div class="groups-container"> 
    <h3>OpenしているGroup一覧</h3>

    <% if @groups == [] %>
        <%= @group_none %>
    <% else %>
        <% @groups.each do |t| %>
          <div class="group">
            <div class="main-box">
                <div class="left-container">
                 <span>グループ名：<a href="/groups/<%= t.id %>"><%= t.name %></a></span>
                 <br>
                 <span>作成時刻： <%= t.created_at %></span>
                 </div>
                 <br>            
                 <br>
            <% if user_signed_in? && current_user.id == t.user_id %>  
              <%= button_to "グループを閉じる", group_path(t.id), method: :delete %>
            <% end %>
          </div>
       <% end %>
    <% end %>
< /div>
```

こちらは`index.html.erb`でした。if文を用いて、グループが無い際は文章が表示されるように仕込んであります。

```html:app/views/group/new.html.erb
<%= stylesheet_link_tag 'group', :media => "all" %>


<div class="post-container">
    <p class="title">チャット作成ツール</p>
    <%= form_for(@group, :url => { controller:'groups', action:'create'})do |f| %>
    <%= f.label :チャット名 %>
    <br>
    <%= f.text_field :name,size: 140%>
    <br>
    <br>
    <%= f.submit "チャット作成"%>
    <br>
<% end %>
</div>
```
これはグループ作成ページですね。

```html:app/views/group/show.html.erb
<%= stylesheet_link_tag 'group', :media => "all" %>

<h3>グループでチャットしよう！</h3>
<div class="groupinfo">
  <p>グループ名：<%= @group.name %></p>
  <p>グループ作成日：<%= @group.created_at %></p>
</div>

<div class="comment-wrapper">
  <p>チャットルーム</p>
  <% @chats.each do |c| %>
    <div>
     <span>ユーザー名：<%= c.user.name unless c.user.blank? %></span>
      <br>
      <%= c.content %>
    </div>
      <br>
  <% end %>

  <% if user_signed_in? %>
    <%= form_with(model: [@group, @chat], local: true) do |f| %>
      <%= f.text_area :content %>
      <%= button_tag type: "submit" do %>
        <i class="far fa-comments"></i> チャットする
      <% end %>
    <% end %>
  <% end %>
</div>
```
とりあえずなんとか最低限は完成や、、、

# 8.デザイン
coming soon

# 9.まとめ 
そこまで複雑な機能設計はないので、もっと複雑なものを実装次第Qiitaに取り入れようと思っております。
デザインも記載しますので、ぜひお楽しみに！