DM（ダイレクトメッセージ）機能の考え方と実装 rails5(Ruby)

# 想定読者
- 既にrailsで作成してあるアプリにDM機能を追加する想定で書いてます。

# 完成イメージ
SNSでよくあるDMっぽい機能を実装する。以下完成イメージキャプ（デザインダサくてごめんなさい）
<img width="1657" alt="スクリーンショット 2019-08-31 9.51.47.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303093/824b3149-f108-90ac-5707-60e7426be6b2.jpeg">


# 画面遷移とModels

Userの詳細ページにメッセージ開始ボタンを設置してメッセージルーム(room/show.html)へ遷移します。
![DM機能.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303093/a5c0f1df-c855-eddd-4748-fbc3ea85bde8.png)


# テーブル設計
- 画面遷移図でも簡単に登場させたけど少し詳しく

 - `User`...名前の通りユーザ情報を格納する
 - `Room`...メッセージを送り合うUserが入る部屋のイメージ（idと名前くらいしか持たない）
 - `Entry`...上記2つのテーブルを結びつける中間テーブル
 - `Message`...メッセージの内容を管理

# Modelの内容

```ruby:user.rb

class User < ApplicationRecord 
    has_many :messages, dependent: :destroy
    has_many :entries, dependent: :destroy
    （他は省略）
```

```ruby:entry.rb
class Entry < ApplicationRecord
  belongs_to :user
  belongs_to :room
end
```

```ruby:room.rb
class Room < ApplicationRecord
    has_many :messages, dependent: :destroy
    has_many :entries, dependent: :destroy
end
```

```ruby:message.rb
class Message < ApplicationRecord
  belongs_to :user
  belongs_to :room
end
```

- 実際にテーブルにデータが入るとこんな感じになる。
<img width="1674" alt="スクリーンショット 2019-08-31 12.22.04.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/303093/e98cc545-3e44-35a7-4295-27f825ef09b0.jpeg">


# コントローラ
```ruby:users_controller.rb
def show
    @user = User.find(params[:id])
    #自分が参加しているメッセージルーム情報を取得する
    @currentUserEntry = Entry.where(user_id: current_user.id)
    #洗濯したユーザのメッセージルーム情報を取得する
    @userEntry = Entry.where(user_id: @user.id)

    #current_userと選択したユーザ間に共通のメッセージルームが存在すればフラグを立てる
    unless @user.id == current_user.id
      @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
      #無ければ作る
      unless @isRoom
        @room = Room.new
        @entry = Entry.new
      end
    end
  end
```

```ruby:rooms_controller.rb
class RoomsController < ApplicationController
    before_action :login_requered

    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 show
        @room = Room.find(params[:id])
        if Entry.where(:user_id => current_user.id, :room_id => @room.id).present?
          @messages = @room.messages.includes(:user)
          @message = Message.new
          @entries = @room.entries.includes(:user)
        else
          redirect_back(fallback_location: root_path)
        end
    end


end

```

```ruby:messages_controller.rb
class MessagesController < ApplicationController
    before_action :login_requered, :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
```

# ルーティング
```ruby:routes.rb
Rails.application.routes.draw do
  resources :users
  root "users#index" #この辺りは作成するアプリの要件により異なる。
  resources :messages, :only => [:create]
  resources :rooms, :only => [:create, :show, :index]
end
```

# ビューの作成

### `メッセージを始める`ボタンの設置

- ユーザの詳細画面に設置します。自分自身の時は表示したくないのでそのように組みます。

```ruby:views/users/show.html.slim
.row
  - if current_user == @user
      #自分自身の時は何かしらのユーザ情報を表示するだけ
      #省略（見にくくなるため）
  - else
      # メッセージRoomがすでにある場合はそこへのリンク(showアクション)
      - if @isRoom == true
        .ul.mt-4.ml-4[style="float: left;list-style: none;"]
          .li
           a.btn.btn-info.ml-2 href="/rooms/#{@roomId}"
      - else# メッセージを開始していない時はcreateアクションへ飛ぶ
        = form_with model: @room, local: true do |f|
          = fields_for @entry do |e|
            = e.hidden_field :user_id, :value=> @user.id
          = f.submit "メッセージを始める" ,class: 'btn btn-outline-info mt-4 ml-4'
      
```

### メッセージRoomのview
- 参加者とメッセージを表示している他、細かいところですが、自分以外の時は`さん`を外す分岐を入れています。

```ruby:views/rooms/show.html.slim
  h1 DM
  h4 参加者
  - @entries.each do |e|
    h5
      strong
        a href="/users/#{e.user.id}"
          - if e.user.name == current_user.name
            = e.user.name
          - else
            = e.user.name + "さん"
  hr
  - if @messages.present?
    - @messages.each do |m|
      - if m.user.name == current_user.name
        p.text-right
         = m.content
        p.text-right
          | by
          strong
            a href="/users/#{m.user_id}"
              = m.user.name
      - else
        p
         = m.content
        p
          | by
          strong
            a href="/users/#{m.user_id}"
              = m.user.name + "さん"
      hr
  - else
    h3.text-center メッセージはまだありません
= form_with model: @message, local: true do |f|
  = f.text_field :content, class: 'form-control', :placeholder => "メッセージを入力して下さい" , :size => 70
  = f.hidden_field :room_id, :value => @room.id
  br
  = f.submit "投稿する", class: 'btn btn-info mt-2'
= link_to "ユーザ一覧", users_path, class: 'btn btn-outline-primary mt-2'
```

# 参考記事
[RailsでDM(ダイレクトメッセージ)を送れるようにしよう](https://qiita.com/nojinoji/items/2b3f8309a31cc6d88d03)
- N + 1問題への対応やform_withへの変更など細かいところ以外はかなり参考にさせていただきました。ありがとうございました。