【Rails】相互フォロー同士のDM機能(非同期通信)

## 初めに
 私は普段、適当にプログラミングを勉強している@takakouと申します。今回作成した「相互フォロー同士の「DM機能(非同期通信)」は自分のアプリに、相互フォローをしているユーザ同士の1対1のDM機能を実装したい方向けです。色々DM機能の作成方法について記事を漁ってみたのですが、相互フォローしているユーザ同士の1対1のDM機能に特化した記事が少ないと感じたため、今回の記事の執筆に至りました。

## 前提

　フォロー・フォロワー機能が実装されているという前提のもと今回の記事を執筆していきます。前提としての機能が完成していない場合は下記の@Hbk__17さんの執筆された記事を参考にしていただき、フォロー・フォロワーの機能を実装していただくことを推奨いたします。

https://qiita.com/Hbk__17/items/a6b96009154542d04269


## 動作環境
・PC : **MacBook Air(M1,2020)**
・RAM : **8GB**
・OS : **macOS Monterey(ver12.1)**
・Ruby : **3.1.2**
・Rails : **6.1.6.1**
・Bootstrap  : **4.5**

:::note info
レイアウトを整えるのはメインを**Bootstrap**、サブで**style**オプション等用います。流行りのCSSフレームワーク等は取り入れてません。レイアウトは割と適当なので完成後にアレンジすることをお勧めいたします。
:::

:::note info
静的コード解析ツール**Rubocop**を用いてコードの修正をしてあります。
:::

## 構成
 DM機能でよくみる構成が下記になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524017/f4e89d3b-d2c1-ec32-9df7-58e979c9d843.png)
上記のテーブル構成でも問題なくDM機能を実装することができますが、roomを作り、そこに人が入ることによりDM機能を作成しているのでLINEで言うグループ機能っぽい構成になってます。しかし、1対1のDM機能を実装するだけだったらこんなにテーブルは要らないので、今回は別の構成でDM機能の作成を行います。


今回作成するDM機能の構成が下記になります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524017/071b7b1b-e807-70e7-bcbf-8d38d0876cde.png)

usersテーブルと、中間テーブルのmessagesテーブルを用いて実装をしていきます。


### モデルの作成
Messageモデルを作成するにあたりまずは下記のコマンドを実行していただくようにお願いいたします。
```terminal:terminal
rails g model Message send_user_id:integer receive_user_id:integer chat:string
```

migrationを行う。
```ter:terminal
rails db:migrate
```
### モデルの記述

リレーションを設定する。今回はsend_user_idとreceive_user_idの親modelが両方ともUserになっているのでそれぞれの指定を行います。class_nameなどの記述がわからない方は下記の記事を参考にしてみてください。

https://techtechmedia.com/class-name-association/

```ruby:message.rb
class Message < ApplicationRecord
  belongs_to :send_user, class_name: 'User'
  belongs_to :receive_user, class_name: 'User'
  validates :chat, presence: true
end

```

:::note warn
UserやRelationshipのモデルの作成や記述は終わっている前提ですので記述はしません。
:::

### リレーションの確認
**1.データ作成**

```ruby:terminal
@message=Message.new(send_user_id:1,receive_user_id:2,chat:"こんにちは")
@message.save
```
:::note warn
事前にユーザの登録などをしておいてください。
:::

**2.作成したデータの検索**

```ruby:terminal
@message=Messgae.find(1)
```
**3.リレーションの確認**
```ruby:terminal
 pry(main)> @message.send_user
  User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, email: "a@a.a", name: "koki", introduction: "eee", created_at: "2022-08-20 12:01:32.485377000 +0000", updated_at: "2022-08-20 17:40:23.711492000 +0000">
[9] pry(main)> @message.receive_user
  User Load (0.3ms)  SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT ?  [["id", 2], ["LIMIT", 1]]
=> #<User id: 2, email: "jjj@jjj", name: "aaa", introduction: "", created_at: "2022-08-20 17:40:44.220449000 +0000", updated_at: "2022-12-17 09:52:31.508726000 +0000">
```

リレーションが通っていることが確認できたらモデルの記述は以上になります。

### messagesコントローラの作成
```ruby:terminal
rails g controller messages
```


コントローラにおいて、相互フォローの確認の処理も行っています。

```ruby:messages_controller
class MessagesController < ApplicationController
  before_action :check, only: [:message]
  def message
    @message = Message.new
    @messages = Message.where(send_user_id: current_user.id,
                              receive_user_id: params[:id]).or(@receive_messages = Message.where(send_user_id: params[:id],
                                                                                                 receive_user_id: current_user.id)).order(:created_at)
  end

  def create
    @message = Message.new(message_params)
    @message.send_user_id = current_user.id
    if @message.save!
      @messages = Message.where(send_user_id: current_user.id,
                                receive_user_id: params[:message][:receive_user_id]).or(@receive_messages = Message.where(
                                  send_user_id: params[:message][:receive_user_id], receive_user_id: current_user.id
                                )).order(:created_at)
    else
      @message = Message.new
      @messages = Message.where(send_user_id: current_user.id,
                                receive_user_id: params[:message][:receive_user_id]).or(@receive_messages = Message.where(
                                  send_user_id: params[:message][:receive_user_id], receive_user_id: current_user.id
                                )).order(:created_at)
      render :message
    end
  end

  private

  def message_params
    params.require(:message).permit(:receive_user_id, :chat)
  end

  def check
    # #自分自身とのDMを防ぐ
    if params[:id] == current_user.id
      redirect_back fallback_location: root_path
    else
      ## 相互にフォローしているかの確認。
      check1 = Relationship.exists?(followed_id: params[:id], follower_id: current_user.id)
      check2 = Relationship.exists?(follower_id: params[:id], followed_id: current_user.id)
      redirect_back fallback_location: root_path if !check1 || !check2
    end
  end
end

```
### routeの記述
routes.rbに下記の文を追加
```ruby:routes.rb
  get 'messages/:id' => 'messages#message', as: 'message'
  post 'messages' => 'messages#create', as: 'messages'
```

### viewファイルの作成
views/messagesフォルダ配下にmessage.html.erbを作成
```erb:message.html.erb
<div class="container bg-dark">
    <div id="messages">
      <%= render 'messages',messages:@messages%>
    </div>
  <div class="row">
    <%= form_with model:@message ,url:messages_path,method: :post,local:false ,class:"w-100",html: { id: 'message_form' } do |f| %>
      <div class="d-flex justify-content-center">
        <%= f.text_field :chat ,class:"w-75",placeholder:"ここにチャットを入力してください",id:"message_field" %>
        <%= f.hidden_field :receive_user_id,value: params[:id]%>
        <%= f.submit "送信" ,class:"btn btn-secondary w-25",id:"message-btn"%>
      </div>
    <% end %>
  </div>
</div>
```

非同期処理でチャット欄を更新するために一部テンプレートファイルも作成しておきます。

```erb:_message.html.erb
<div class="row d-block bg-dark pt-1" style="height:800px;overflow: scroll;" >
  <div>
    <% messages.each do |message|%>
      <% if message.send_user.id==current_user.id%>
        <div class="mb-1 d-flex">
          <%= image_tag message.send_user.get_profile_image(50,50),class:"rounded ml-1 mr-2"%>
          <div class="rounded bg-success text-black text-break" style="max-width:50%"><%= message.chat %></div>
        </div>
      <% else %>
        <div class="d-flex flex-row-reverse mb-1">
          <div><%= image_tag message.send_user.get_profile_image(50,50),class:"rounded ml-2 mr-1"%></div>
          <div class="rounded bg-secondary text-white text-break mw-100" style="max-width:50%;"><%= message.chat %></div>
        </div>
      <% end %>
    <% end %>
  </div>
</div>
```

非同期通信でメッセージを更新する用のjsファイルも作成します。

```erb:create.js.erb
$("#messages").html("<%= j(render 'messages/messages',messages: @messages) %>");
$("#message_field").val('');
```

### 完成形
完成すると下記の図のようなレイアウトでDM機能ができるようになっているはずです！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2524017/6ac3e5af-e677-ef99-f3f2-92c3c5b0b3b1.png)




## 終わりに
　「もっとこうすると良い」等の修正依頼は随時受け付けておりますので指摘をお願いいたします。少しでも、どこかの誰かの役に立ってくれれば、とても嬉しいです。