RailsでDM機能を実装してみました

# RailsでDM機能を実装
ポートフォリオでRailsでアプリケーションを作成し、1対１でDMを送ることができる機能を実装しました。

## 実装後の挙動
[![Image from Gyazo](https://i.gyazo.com/1c7046ceab98fba31db588dd244b9a5f.gif)](https://gyazo.com/1c7046ceab98fba31db588dd244b9a5f)



## 使用技術
Ruby 2.6.5
Ruby on Rails 6.0.5

## テーブル設計

まずはテーブル設計です。

![9b3e78d84a15a03decc6f6cc9a06e02c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2727797/968e6019-39af-8247-906a-4bfbf884d19d.png)

usersテーブル　ユーザーを管理するテーブル

roomsテーブル　メッセージをする部屋

entriesテーブル　usersテーブルとroomsテーブルの中間テーブル。userとroomの情報が一つづつ入ります。

messagesテーブル　これもusersテーブルとroomsテーブルの中間テーブル。メッセージの内容とuserとroomの情報が入ります。

アソシエーションはこのようになります。

``` ruby:user.rb
  has_many :messages, dependent: :destroy
  has_many :entries, dependent: :destroy
```

``` ruby:rooms.rb
  has_many :messages, dependent: :destroy
  has_many :entries, dependent: :destroy
```

``` ruby:entry.rb
  belongs_to :user
  belongs_to :room
```

``` ruby:message.rb
  belongs_to :user
  belongs_to :room
```

## ルーティング

``` ruby:routes.rb
resources :users, only: [:edit, :update, :show]
resources :messages, only: [:create]
resources :rooms, only: [:create, :show, :index]
```

## userコントローラー

``` ruby:users_controller.rb
class UsersController < ApplicationController
 

  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
      # ユーザーのidとログインしているidが同じでなければ実行
    else
      @currentUserEntry.each do |cu|
        @userEntry.each do |u|
          if cu.room_id == u.room_id
            # 既にroomが作成されている場合
            @isRoom = true
            @roomId = cu.room_id
            # 既に作成されているroom_idを特定
          end
        end
      end
      if @isRoom
      else
        # roomを新しく作成する記述
        @room = Room.new
        @entry = Entry.new
      end
    end
  end



end

```

ここから理解が大変でした。まずはこの部分から解説します。

```ruby
    @user = User.find(params[:id])
    @currentUserEntry = Entry.where(user_id: current_user&.id)
    # ログインしているユーザーの情報取得
    @userEntry = Entry.where(user_id: @user.id)
    # メッセージ相手のユーザーの情報を取得
```
@userの所はuserの情報をfindでテーブルから取得しています。これによってuserの詳細ページでユーザーの名前を表示することができます。

 @currentUserEntryと@userEntryはログインしているユーザーの情報とメッセージ相手のユーザーの情報を取得しています。
```ruby
モデル名.where("条件")
```
whereを使うことでテーブル内の条件に一致したレコードを配列の形で取得することができます。

```ruby
    if @user.id == current_user&.id
      # ユーザーのidとログインしているidが同じでなければ実行
    else
      @currentUserEntry.each do |cu|
        @userEntry.each do |u|
          if cu.room_id == u.room_id
            # 既にroomが作成されている場合
            @isRoom = true
            @roomId = cu.room_id
            # 既に作成されているroom_idを特定
          end
        end
      end
      if @isRoom
      else
        # roomを新しく作成する記述
        @room = Room.new
        @entry = Entry.new
      end
    end
  end
```
ここではまず条件分岐でログインしているユーザーのidと同じでなければ実行するようにしています。意味的には自分のマイページではチャットを作ることはできないようにしています。

そして既にroomが作成されている場合はそのroomのidを特定する記述をし、まだroomが作成されていない場合は新しくroomを作るよう記述しています。

## users/show.html.erb

次にuserの詳細ページのviewファイルです。


``` :users/show.html.erb
<% if @user.id == current_user&.id %>

<div class="d-grid gap-2 d-md-flex justify-content-md-end">


  <%= link_to "DM一覧", rooms_path, class: "post btn btn-outline-info" %>


 </div>

<% else %>
  <% if @isRoom == true %>
  <%# 既に部屋は存在しているかどうか %>
    <div class="btn btn-secondary"><a class="text-decoration-none text-white" href="/rooms/<%= @roomId %>">チャットへ</div>
  <% 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-secondary chat_btn"%>
    <% end %>
  <% end %>
<% end %>
```
まず条件分岐では自分のマイページではDM一覧ページに遷移するボタンが表示されます。他の人のページではチャットを始めるボタンを表示するようにしています。その中での条件分岐で始めてチャットをする相手には「チャットを始めるボタン』、既にチャットをしている相手にはチャットへボタンを表示しています。

## roomsコントローラーとview

```ruby:rooms_controller
class RoomsController < ApplicationController
  before_action :authenticate_user!

  def create
    @room = Room.create
    @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 index
    # ログインユーザー所属ルームID取得
    current_entries = current_user.entries
    my_room_id = []
    current_entries.each do |entry|
      my_room_id << entry.room.id
    end
    # 自分のroom_idでuser_idが自分じゃないのを取得
    @another_entries = Entry.where(room_id: my_room_id).where.not(user_id: current_user.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
    else
      redirect_back(fallback_location: root_path)
    end
  end
end

```
まずcreateアクションについて解説します。
```ruby
@room = Room.create
```
これで新しいroomを保存しています。
```ruby
 @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}"
```
これはentryテーブルにuser_idとroom_idを保存しています。@entry1は現在ログインしているユーザーでentry2がDMする相手のデータになります。

次にshowアクションについて解説します。
まずroomのidをfindで見つけてきます。
```ruby
@room = Room.find(params[:id])
```
次に条件分岐でentryテーブルの中のログインしているユーザーのidとそれに紐づくroomのidを取得してpresent?で値が格納されているとtrueで値がないとfalseになります。

trueならそのroomのメッセージを表示するため@messagesのインスタンス変数の中に@room.messagesを代入しています。

また@message = Message.newで新しくメッセージを入れる箱を作っています。

@entries = @room.entriesはDMの画面で誰が参加しているかを表示するために記述しています。


```:rooms/index.html.erb
<div class="container">

<% @another_entries.each do |entry| %>
  <%= link_to room_path(entry.room) do %>
  <div class="card mb-4">

  <div class= "card-body">
  <p class="card-text"><%= entry.user.name%>
  さんとのメッセージ
  </p>
  </div>
  </div>

  <% end %>
<% end %>

</div>
```
ここでは自分がDMをした人との一覧を表示しています。

```:rooms/show.html.erb
<div class= "container">


<h1>DM</h1>

<h4>参加者</h4>
<% @entries.each do |e| %>
  <h5><strong><a href="/users/<%= e.user.id %>"><%= e.user.name%>さん</a></strong></h5>
<% end %>

<hr>
<% if @messages.present? %>
  <% @messages.each do |m| %>
    <%= m.user.name %>さん
    <div class="date_time text-secondary text-right">
     <%= m.created_at%>
     </div>

    <div class="message pt-3">
    <%= m.content %>
    </div>

    <hr>
  <% end %>
<% else %>
  <h3 class="text-center">メッセージはまだありません</h3>
<% end %>

<%= form_for @message do |f| %>
<div class="d-flex pb-5">
  <%= f.text_field :content, placeholder: "メッセージを入力して下さい" , rows: 15,class:"form-control"%>
  <%= f.hidden_field :room_id, value: @room.id %>
  <%= f.submit "投稿する",class:"btn btn-secondary" %>
  </div>
<% end %>

</div>

```

ここではDMの画面を表示しています。

```ruby:messages_controller.rb
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(: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
```

ここではmessageを保存する処理を書いています。条件分岐でログインしているユーザーのidとroomのidとmessageのデータが入っていると実行されます。

データが入っていないと、何も起こらずにページに戻る記述になっています。

以上になります。

## 参考記事
[RailsでややこしいDM機能を1万字でくわしく解説してみた](https://qiita.com/bindingpry/items/6790c91f374acc25bea2)
[RailsでDM(ダイレクトメッセージ)を送れるようにしよう](https://qiita.com/nojinoji/items/2b3f8309a31cc6d88d03)