[Rails]ダイレクトメッセージ(DM)機能の実装

#目標

![ezgif.com-video-to-gif (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/579893/27e4afa7-4a02-a9ff-8fb4-e169d2fb9720.gif)

# 開発環境
・Ruby: 2.5.7
・Rails: 5.2.4
・Vagrant: 2.2.7
・VirtualBox: 6.1
・OS: macOS Catalina

# 前提

下記実装済み。

・[Slim導入](https://qiita.com/matsubishi5/items/b639ffddecc626856039)
・[Bootstrap3導入](https://qiita.com/matsubishi5/items/b117c95f4dd71340cc80)
・[Font Awesome導入](https://qiita.com/matsubishi5/items/a4c72f436e64ad106ee8)
・[ログイン機能実装](https://qiita.com/matsubishi5/items/5bd8fdd45af955cf137d)

#ダイレクトメッセージ(DM)機能を実装

### 1.モデル

```terminal:ターミナル
$ rails g model room
```

```terminal:ターミナル
$ rails g model entry user:references room:references
```

```terminal:ターミナル
$ rails g model message user:references room:references body:text
```

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

```ruby:schema.rb
ActiveRecord::Schema.define(version: 2020_04_05_115005) do

  create_table "entries", force: :cascade do |t|
    t.integer "user_id"
    t.integer "room_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["room_id"], name: "index_entries_on_room_id"
    t.index ["user_id"], name: "index_entries_on_user_id"
  end

  create_table "messages", force: :cascade do |t|
    t.integer "user_id"
    t.integer "room_id"
    t.text "body"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["room_id"], name: "index_messages_on_room_id"
    t.index ["user_id"], name: "index_messages_on_user_id"
  end

  create_table "rooms", force: :cascade do |t|
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end
end
```

```ruby:user.rb
class User < ApplicationRecord
  has_many :entries, dependent: :destroy
  has_many :messages, dependent: :destroy
end
```

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

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

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

### 2.コントローラー

```terminal:ターミナル
$ rails g controller rooms index show
```

```terminal:ターミナル
$ rails g controller messages
```

```users_controller.rb
class UsersController < ApplicationController
  def index
    @users = User.all
  end

  def show
    @user = User.find(params[:id])
    # Entryモデルからログインユーザーのレコードを抽出
    @current_entry = Entry.where(user_id: current_user.id)
    # Entryモデルからメッセージ相手のレコードを抽出
    @another_entry = Entry.where(user_id: @user.id)
    unless @user.id == current_user.id
      @current_entry.each do |current|
        @another_entry.each do |another|
          # ルームが存在する場合
          if current.room_id == another.room_id
            @is_room = true
            @room_id = current.room_id
          end
        end
      end
      # ルームが存在しない場合は新規作成
      unless @is_room
        @room = Room.new
        @entry = Entry.new
      end
    end
  end
end
```

```rooms_controller.rb
  def create
    room = Room.create
    # Entryモデルにログインユーザーのレコードを作成
    current_entry = Entry.create(user_id: current_user.id, room_id: room.id)
    # Entryモデルにメッセージ相手のレコードを作成
    another_entry = Entry.create(user_id: params[:entry][:user_id], room_id: room.id)
    redirect_to room_path(room)
  end

  def index
    # ログインユーザーが属しているルームのIDを全て抽出して配列化
    current_entries = current_user.entries
    my_room_ids = []
    current_entries.each do |entry|
      my_room_ids << entry.room.id
    end
    # さらにuser_idがログインユーザーでは無いレコードを抽出
    @another_entries = Entry.where(room_id: my_room_ids).where.not(user_id: current_user.id)
  end

  def show
    @room = Room.find(params[:id])
    @message = Message.new
    # メッセージ相手を抽出
    @another_entry = @room.entries.find_by('user_id != ?', current_user.id)
  end
```

```messages_controller.rb
class MessagesController < ApplicationController
  def create
    message = Message.new(message_params)
    message.user_id = current_user.id
    if message.save
      redirect_to room_path(message.room)
    else
      redirect_back(fallback_location: root_path)
    end
  end

  def destroy
    message = Message.find(params[:id])
    message.destroy
    redirect_back(fallback_location: root_path)
  end

  private

    def message_params
      params.require(:message).permit(:room_id, :body)
    end
end
```

### 3.ルーティング

```ruby:routes.rb
Rails.application.routes.draw do
  resources :messages, only: [:create, :destroy]
  resources :rooms, only: [:create, :index, :show]
end
```

### 4.ビュー

```slim:users/index.show.slim
.row
  .col-xs-2

  .col-xs-8
    table.table
      thead
        tr
          th
            | 氏名
          th
            | 自己紹介
          th
          th

      tbody
        tr
          td
            = @user.name
          td
            = @user.introduction
          td
            - unless @user.id == current_user.id
              - if @is_room == true
                = link_to 'メッセージ', room_path(@room_id), class: 'btn-sm btn-success'

              - else
                = form_with model: @room, url: rooms_path, local: true do |f|
                  = fields_for @entry do |e|
                    = e.hidden_field :user_id, value: @user.id
                  = f.submit 'メッセージ', class: 'btn-sm btn-success'
          td
            - if @user == current_user
              = link_to '編集', edit_user_path(@user), class: 'btn-sm btn-primary'

  .col-xs-2
```

```slim:rooms/show.html.slim
.row
  .col-xs-3

  .col-xs-6
    .page-header
      h3
        = link_to @another_entry.user.name, user_path(@another_entry.user)

    table.table
      thead
        tr
          th
            | 送信者
          th
            | メッセージ
          th

      tbody
        - @room.messages.each do |message|
          tr
            td
              = link_to user_path(message.user) do
                = message.user.name
            td
              = message.body
            td
              - if message.user == current_user
                = link_to '削除', message, method: :delete, data: { confirm: '本当に削除してもよろしいですか？' }, class: 'btn-sm btn-danger'
            br

  .col-xs-3

.row
  .col-xs-3

  .col-xs-6
    = form_with model: @message, url: messages_path, local: true do |f|

      .col-xs-10
        = f.text_field :body, class: 'form-control'
        = f.hidden_field :room_id, value: @room.id

      .col-xs-2
        = f.submit '送信', class: 'btn btn-info btn-block'

  .col-xs-3
```

```slim:rooms/index.html.erb
.row
  .col-xs-3

  .col-xs-6
    table.table
      thead
        tr
          th
            | メッセージ相手
          th
            | メッセージ内容

      tbody
        - @another_entries.each do |entry|
          tr
            td
              = link_to entry.user.name, user_path(entry.user)

            td
              = link_to room_path(entry.room) do
                / 最後に送られたメッセージを表示
                = Message.find_by(id: entry.room.message_ids.last)&.body

  .col-xs-3
```