【Rails】DM機能の実装※自己学習用


### 要件

1. **ユーザー同士が1対1のチャットルームでDM（ダイレクトメッセージ）を送れるようにする**
2. **相互フォローしているユーザーのみチャットルームにアクセス可能にする**
3. **相互フォローしていないユーザーにはチャットルームのリンクを表示しない**

---

### 実装の全体的な流れ

1. **チャットルームとダイレクトメッセージのモデルを作成**
2. **チャットルームの作成時に、重複しないように処理を追加**
3. **相互フォローしていない場合、チャットルームのリンクを表示しない**
4. **チャットルーム内でメッセージを送る処理を追加**
5. **エラー修正と要件変更の対応**

---

### 1. **チャットルームとダイレクトメッセージのモデルを作成**

まず、チャットルームとダイレクトメッセージを管理するためのモデルを作成します。モデルとは、データベースのテーブルとプログラムの橋渡しをする部分です。

#### モデルの作成

以下のコマンドを実行して、チャットルームとメッセージのモデルを作成します。

```bash
rails generate model ChatRoom user1_id:integer user2_id:integer
rails generate model DirectMessage user:references chat_room:references content:text
```

これで、チャットルームに参加する2人のユーザーのIDを保存するための `ChatRoom` モデルと、メッセージの内容を保存するための `DirectMessage` モデルが作成されました。

#### マイグレーションの実行

次に、データベースにテーブルを作成するために、以下のコマンドでマイグレーションを実行します。

```bash
rails db:migrate
```

---

### 2. **チャットルームの作成時に、重複しないように処理を追加**

2人のユーザーが同じ組み合わせでチャットルームを作成する場合、**重複したチャットルームが作られないように**します。つまり、同じ2人のユーザーが何度も別のチャットルームを作成しないようにする仕組みです。


`ChatRoom` モデルに、2人のユーザーでチャットルームを一意に管理するメソッドを追加します。

```ruby
class ChatRoom < ApplicationRecord
  belongs_to :user1, class_name: 'User'
  belongs_to :user2, class_name: 'User'

  # 2人のユーザー間のチャットルームを探し、なければ新規作成するメソッド
  def self.find_or_create_by_users(user1, user2)
    # IDの小さい方をuser1、大きい方をuser2にして順序を統一
    user1, user2 = [user1, user2].sort_by(&:id)
    
    # 既存のチャットルームがあるか検索し、なければ作成する
    chat_room = ChatRoom.find_by(user1: user1, user2: user2) || ChatRoom.create(user1: user1, user2: user2)
    chat_room
  end
end
```

#### 解説
- **`self.find_or_create_by_users(user1, user2)`**: 2人のユーザーの間にチャットルームがあるかを確認し、なければ新しいチャットルームを作成するメソッドです。
- **`user1, user2 = [user1, user2].sort_by(&:id)`**: 2人のユーザーのIDを比較し、小さい方を `user1`、大きい方を `user2` に設定することで、チャットルームの順序が一意になります。
- **`ChatRoom.find_by(user1: user1, user2: user2)`**: すでに同じ2人のユーザー間でチャットルームがあるかを探します。見つからない場合は新しく作成します。

---

### 3. **相互フォローしていない場合、チャットルームのリンクを表示しない**

ユーザー同士がフォローし合っていないと、チャットルームのリンクを表示しないようにします。

#### `User` モデルに相互フォロー確認メソッドを追加

```ruby
class User < ApplicationRecord
  # 相手のユーザーと自分が相互フォローしているか確認するメソッド
  def mutual_follow?(other_user)
    self.following?(other_user) && other_user.following?(self)
  end
end
```

#### 解説
- **`mutual_follow?`**: これは相手のユーザー（`other_user`）と自分が相互にフォローしているかを確認するメソッドです。お互いがフォローし合っている場合にのみ `true` を返します。

#### ビューでのリンク表示制御

次に、チャットルームへのリンクを表示する部分で、相互フォローかどうかを確認してリンクを表示しないようにします。

```erb
<!-- 相互フォローしている場合のみ、チャットルームリンクを表示 -->
<% if current_user.mutual_follow?(user) %>
  <%= link_to "チャットルーム", chat_room_path(ChatRoom.find_or_create_by_users(current_user, user)), class: "btn btn-outline-primary" %>
<% else %>
  <p>相互フォローしていないため、チャットルームにアクセスできません。</p>
<% end %>
```

---

### 4. **チャットルーム内でメッセージを送る処理を追加**

次に、チャットルーム内でメッセージを送信できる機能を実装します。

#### `DirectMessagesController` の作成

```ruby
class DirectMessagesController < ApplicationController
  before_action :set_chat_room
  before_action :check_mutual_follow, only: [:create]

  def create
    @direct_message = @chat_room.direct_messages.build(direct_message_params)
    @direct_message.user = current_user

    if @direct_message.save
      redirect_to @chat_room
    else
      render 'chat_rooms/show'
    end
  end

  private

  def set_chat_room
    @chat_room = ChatRoom.find(params[:chat_room_id])
  end

  def direct_message_params
    params.require(:direct_message).permit(:content)
  end

  # 相互フォローしていない場合、メッセージ送信を制限
  def check_mutual_follow
    other_user = @chat_room.other_user(current_user)
    unless current_user.mutual_follow?(other_user)
      flash[:alert] = "相互フォローしていないため、DMを送信できません。"
      redirect_to chat_rooms_path
    end
  end
end
```

#### 解説
- **`before_action :set_chat_room`**: メッセージを送信する前に、そのメッセージがどのチャットルームに属するかを設定します。
- **`before_action :check_mutual_follow`**: 相互フォローしていない場合、メッセージ送信を制限します。

---

### 5. **エラー修正と要件変更の対応**

#### エラー内容: `undefined method 'other_user' for nil:NilClass`

- エラーが発生したのは、`@chat_room` が正しくセットされていなかったためです。この問題を解決するために、`set_chat_room` を正しく `before_action` で呼び出すように修正しました。
  
```ruby
before_action :set_chat_room  # これを一番先に呼び出すようにした
before_action :check_mutual_follow, only: [:create]
```

#### 要件変更: **相互フォローしていない場合、チャットルームのリンクを表示しない**

- チャットルームリンクの表示を、相互フォローのチェックを行って非表示にするように修正しました。

---

### まとめ

この流れで、チャットルームの機能を実装しました。以下が再現できるようになっています。

1. **ユーザー同士が1対1のチャットルームでメッセージを送信できる**。
2. **相互フォローしていない場合はチャットルームのリンクが表示されない**。
3. **相互フォローでない場合、メッセージ送信を制限する処理を追加**。