DM機能実装

自分用にまとめます。


# 必要なテーブル
#### users
ユーザーテーブル

#### rooms
チャットルームをするための場所

#### entries
チャットルームに入るユーザーを管理するテーブル

#### messages
メッセージを管理するテーブル




# 実装

####モデル作成

```
$ rails g model room name:string
$ rails g model entry user:references room:references
$ rails g model message user:references room:references content:text
$ rails db:migrate
```

```ruby:user.rb
class User < ApplicationRecord
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable
  has_many :messages, dependent: :destroy
  has_many :entries, dependent: :destroy
end
```

```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
```
<br><br>

####コントローラ作成

```
$ rails g controller users index show
$ rails g controller rooms
$ rails g controller messages
```

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


```ruby:users_controller.rb
class UsersController < ApplicationController
  def show
    @user = User.find(params[:id])
    # current_userをEntriesテーブルから探す
    @currentUserEntry = Entry.where(user_id: current_user.id)
    # DMを送る対象のユーザーをEntriesテーブルから探す
    @userEntry = Entry.where(user_id: @user.id)

    if @user.id != current_user.id
      # currentUserと@userのEntriesをそれぞれ一つずつ取り出し、2人のroomが既に存在するかを確認
      @currentUserEntry.each do |cu|
        @userEntry.each do |u|
          # 2人のroomが既に存在していた場合
          if cu.room_id == u.room_id
            @isRoom = true
            #room_idを取り出す
            @roomId = cu.room_id
          end
        end
      end

      # 2人ののroomが存在しない場合
      unless @isRoom
        #roomとentryを新規に作成する
        @room = Room.new
        @entry = Entry.new
      end
    end
  end
end
```

```ruby:users/show.html.slim
  - if @user.id != current_user.id

    # 相互フォローしていることが条件
    - if (current_user.following? @user) && (@user.following? current_user)

      # 既にroomがある場合（以前にDMのやりとりしている）
      - if @isRoom == true
         # そのroomに入る
         =link_to room_path(@roomId) do
           i.fas.fa-envelope

      # roomがない場合（新規のDM）
      - else
        # RoomとEntry2つのモデルにデータを送る
        = form_with @room, local: true do |f|
          = fields_for @entry do |e|
            # Entryにはuser_idを送る必要があるため、@user.idを指定
            = e.hidden_field :user_id, value: @user.id
          # 今回はリンクにアイコン使用のためbutton_tagを使用。通常であればf.submitでOK
          = button_tag type: 'submit' do
            i.fas.fa-envelope
```

```ruby:rooms_controller
class RoomsController < ApplicationController

  def create
    @room = Room.create
    # current_userのEntry
    @entry1 = Entry.create(room_id: @room.id, user_id: current_user.id)
    # DMを受け取る側のEntry（users/showでuser_idは渡しているため、room_idを拾って、マージしている）
    @entry2 = Entry.create((entry_params).merge(room_id: @room.id))
    redirect_to room_path(@room)
  end

  def show
    @room = Room.find(params[:id])
    # entriesテーブルにcurrent_user.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

  private
    def entry_params
      params.require(:entry).permit(:user_id, :room_id)
    end

end
```
```ruby:rooms/show.html.slim
.row
  .col-lg-2
  .col-lg-8.frame-notification
    <hr>
    # メッセージがあれば、一覧を表示
    - if @messages.present?
      - @messages.each do |message|
        .row.balloon5
          # 今回は、自分のメッセージは右側に表示させたいため以下の条件分岐
          - if current_user.name == message.user.name
            .col-lg-12.text-right
              .chatting
                .say
                  = message.content
                <br>
                small= message.created_at.to_s(:datetime_jp)
          - else
            .col-lg-2.text-center
              = attachment_image_tag message.user, :image, format: 'jpeg', fallback: "noimage.png", size: "50x40"
              small= link_to message.user.name, user_path(message.user), class: "d-block mt-1"
              <br>
            .col-lg-10.text-left
              .chatting
                .says
                  p= message.content
                <br>
                small= message.created_at.to_s(:datetime_jp)
        <hr>
    - else
      p メッセージはありません。

    .row
      .col-lg-2
      .col-lg-8.text-center
        = form_with model: @message, local: true do |f|
          = f.text_field :content, placeholder: "メッセージを入力して下さい" , size: 50
          = f.hidden_field :room_id, value: @room.id
          = f.submit "投稿", class: 'btn btn-outline-secondary btn-sm ml-2'
```

```ruby:messages_controller.rb
class MessagesController < ApplicationController

  def create
    #formで送られてきたroom_idとcurrent_user.idを持ったデータがEntryにあるかを確認
    if Entry.where(user_id: current_user.id, room_id: params[:message][:room_id]).present?
      # contentとroom_idはformで送られてきているため、user_idをマージして新規メッセージを作成
      @message = Message.create((message_params).merge(user_id: current_user.id))
      redirect_back(fallback_location: root_path)
    else
      flash[:alert] = "メッセージ送信に失敗しました。"
      redirect_back(fallback_location: root_path)
    end
  end

  private
    def message_params
      params.require(:message).permit(:user_id, :content, :room_id)
    end

end
```

**完成！**