Rails 相互フォロー時にDM機能によってメッセージを送信可能にする

# はじめに
- ユーザーが相互フォローをしている際に、お互いのみが参照できるDM機能を実装する
- 学習内容の備忘録です

# 前提条件
- ユーザー機能が実装できている
- フォロー、フォロワー機能が実装できている
- Bootstrap4を導入している

# 手順
1. テーブル設計
1. アソシエーション
1. ルーティングとコントローラー
1. ビュー

# 1.テーブル設計
- まず、元々あるUsersテーブルとDMをするための部屋RoomsテーブルはN:Nの関係
- 次に、中間テーブルであるEntriesテーブルを作成→Users:Entries=1:N
- さらに、中間テーブルMessagesテーブルを作成→Users:Messages=1:N

# 2.アソシエーション
まず、新しいモデルを作成。
```:ターミナル
rails g model Room user:references
rails g model Entry user:references room:references
rails g model Message user:references room:references
rails db:migrate
```
次に、それぞれのモデルへ記述。

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

```:room.rb
has_many :messages, dependent: :destroy
has_many :entries, dependent: :destroy
```

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

```:message.rb
validates :message, length: { maximum: 140 }
belongs_to :user
belongs_to :room
```
>補足
「validates :message, length: { maximum: 140 }」によって、message属性がnullでないことを検証し、さらに文字列の長さが140文字以下であることを検証。つまり、空欄はダメ、141文字以上もダメ。

# 3.ルーティングの設定とコントローラーの記述
まず、ルーティングにこちらの記述を追加。
```:routes.rb
get 'message/:id' => 'messages#show', as: 'message'
resources :messages, only: [:create]
```

続いて、コントローラーを作成。
```:ターミナル
rails g controller messages
```

さらに、messagesコントローラーへ記述。
showアクションとcreateアクションを記述。

```:messages_controller.rb
# showアクションとcreateアクションを実行する前に、ユーザーの認証（ログイン）を確認
before_action :authenticate_user!, only: [:show, :create]

  # showアクションは、特定のユーザーの詳細ページを表示するため
  def show
    @user = User.find(params[:id])

    # 現在のユーザーが参加しているすべてのルームのIDを取得
    rooms = current_user.entries.pluck(:room_id)
    
    # 特定のユーザーと現在のユーザーの間で共有されているルームのエントリーを見つける
    entries = Entry.find_by(user_id: @user.id, room_id: rooms)

　　　　　　　　# エントリーが存在する場合、関連するルームを@roomに代入
    unless entries.nil?
      @room = entries.room

    # エントリーが存在しない場合、新しいルームを作成し、保存し、お互いのエントリーを作成
    else
      @room = Room.new(user_id: current_user.id)
      @room.save
      Entry.create(user_id: current_user.id, room_id: @room.id)
      Entry.create(user_id: @user.id, room_id: @room.id)
    end

    # @roomに関連するメッセージを@messagesに、新しいメッセージを作成したものは@messageに代入
    @messages = @room.messages
    @message = Message.new(room_id: @room.id)
  end

  # createアクションは、新しいメッセージを作成するため
  def create

    # 現在のユーザーに関連付けられた新しいメッセージを作成
    @message = current_user.messages.new(message_params)

    # メッセージを保存し、元のURLへリダイレクト
    @message.save
    redirect_to request.referer
  end

  private

  # フィルタリングも忘れずに
  def message_params
    params.require(:message).permit(:message, :room_id)
  end
```

# 4.ビュー
まずは、messageを始めるためのリンクを、ユーザーの詳細ページへ。
ここでは、部分テンプレートに記述。
```:_info.html.erb
<div class='row mt-2'>
  <% unless user.id == current_user.id %>
    <% if current_user.is_followed_by?(user) && user.is_followed_by?(current_user) %>
      <% if current_user != @user %>
        <%= link_to 'messageを始める', message_path(@user.id)%>
      <% end %>
    <% end %>
  <% end %>
</div>
```
>補足
モデルに「is_followed_by?」メソッドを定義しているため、「<% if current_user.is_followed_by?(user) && user.is_followed_by?(current_user) %>」で相互フォロー状態であることを確認できる。つまり、相互フォロー状態で、自分のユーザー詳細出なければ、「messageを始める」ボタンが出現！

>補足2
「is_followed_by?」メソッドについては以下の通り
ちなみに、当時はこのように書いてしまったが、「reverse_of_relationships（反対からの）」よりも、「passive_relationships（受け身の）」の方がリーダブルだと思われる。
```:user.rb
def is_followed_by?(user)
    reverse_of_relationships.find_by(follower_id: user.id).present?
end
```

あとは、DMを送り合う部屋への記述！
```:messages/show.html.erb
<h4 class="rooms-title text-center mb-3">チャットルーム</h4>

<div class="row justify-content-center mb-3">
  <div class="left-button">
    <%= link_to "ユーザー一覧に戻る", users_path, class:"edit-link" %>
  </div>
</div>

<div class="row justify-content-center mb-3">
  <div class="posts ">
    <%= form_with model: @message do |f| %>
      <%= f.text_field :message, placeholder: "メッセージを入力して下さい" , size: 70, class:"form-text-field" %>
      <%= f.hidden_field :room_id %>
      <%= f.submit "投稿",class: 'form-submit' %>
    <% end %>
  </div>
</div>

<div class="row justify-content-center text-center">
  <div class="chat">
    <% if @messages.present? %>
      <% @messages.each do |m| %>
        <div class="chat-box">
          <div class="chat-face">
            <%= link_to user_path(m.user.id) do %>
              <%= image_tag m.user.get_profile_image, size: "50x50", class: "user-image" %>
            <% end %>
            <%= m.user.name %>
            <strong><%= m.message %></strong>
            <%= m.created_at.strftime("%Y-%m-%d %H:%M")%>
          </div>
        </div>
      <% end %>
    <% end %>
  </div>
</div>
```
あまり、見た目が美しくないので、今後、改善を重ねていきます。

# 参考サイト
[RailsでややこしいDM機能を1万字でくわしく解説してみた](https://qiita.com/bindingpry/items/6790c91f374acc25bea2#users_controller)
とても、わかりやすく、理解が深まりました。ありがとうございます。

# まとめ
- DMを送るためには、EntriesとMessagesの2つの中間テーブルにそれぞれ役割がある

フラッシュメッセージや、レイアウトなどは未完ですが、とりあえずDMは送り合うことができるようになりました。

⚠️学習4ヶ月目の初学者による投稿です。
⚠️間違いがあるかもしれません。ご容赦ください。
⚠️ご指導、ご教授いただけると幸いです。