Rails 〜メッセージ機能を読み解く〜

#メッセージ機能を読み解く
##目標
しっかりと理解できていないメッセージ機能についてコードの意味を理解する。
参考サイト
「RailsでDM(ダイレクトメッセージ)を送れるようにしよう」
https://qiita.com/nojinoji/items/2b3f8309a31cc6d88d03

#イメージ
deviseにてuserモデルを作成、user間でメッセージのやり取りができるようになる。

**Userモデル：　User情報
Roomモデル：　今回はチャットルームに２人のUserが入るイメージ
Entryモデル：　どのUserがどのRoomに所属しているか
Messageモデル：　UserがどのRoomでどんなMessageを送ったか**
*すでにdeviseインストール、userモデル作成は行っていることとする。

#内容
まずアソシエーションを行う。
**user.rb**

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

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

**room.rb**

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

**message.rb**

```
message.rb
class Message < ApplicationRecord
  belongs_to :user
  belongs_to :room
end
```
ここはそのままなので割愛

続いてコントローラーの作成

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

**users_controller.rb**

```
class UsersController < ApplicationController
  before_action :authenticate_user!, :only => [:show]
  def index
    @users=User.all
  end

  def show
    @user=User.find(params[:id])
    @currentUserEntry=Entry.where(user_id: current_user.id)
    @userEntry=Entry.where(user_id: @user.id)
unless @user.id == current_user.id
  @currentUserEntry.each do |cu|
    @userEntry.each do |u|
      if cu.room_id == u.room_id then
        @isRoom = true
        @roomId = cu.room_id
      end
    end
  end
  unless @isRoom
    @room = Room.new
    @entry = Entry.new
  end
end
   
```
```
    @user=User.find(params[:id])
    @currentUserEntry=Entry.where(user_id: current_user.id)
    @userEntry=Entry.where(user_id: @user.id)
```
showの部分を読み解く。
まずこの部分だが一番上はuser#showをリクエストした際に押したuser.idの情報をfindメソッドで検索し、インスタンス変数@userへ代入している。
2行目はwhereメソッドを使用してcurrent_userのidから検索した結果を@currentUserEntryへ代入している。
３行目whereメソッドを使用して1行目に検索したインスタンス変数userのidから検索した結果を@UserEntryへ代入している。
ここで２つの変数を定義している。そしてroomがcreateされた時に、現在ログインしているユーザーと、「チャットへ」を押されたユーザーの両方をEntriesテーブルに記録する必要があるので、whereメソッドでそのユーザーを探しているということである。
最初whereメソッドをなぜ使っているのか疑問であったがここはあくまでentryテーブルに指定された２つのuser.idが登録されているかを確認するためのものなので登録されていない場合はfindメソッドとかを使うと見つかりませんとエラーがでます。

```
unless @user.id == current_user.id
  @currentUserEntry.each do |cu|
    @userEntry.each do |u|
      if cu.room_id == u.room_id then
        @isRoom = true
        @roomId = cu.room_id
      end
    end
  end
  unless @isRoom
    @room = Room.new
    @entry = Entry.new
  end
end
```
ここではunless文を使用し@userとcurrent_userが同一でないかを確認。もし異なっていれば以下のコードが通るように記入している。@currentUserEntryと@userEntryの２つの変数の情報をブロック引数として渡し、２つの引数の中に同じRoom_idがあるかを確認する。もしあれば@isRoomにtrueを代入。current_userのroom_idを@roomIdに代入している。もし２つのEntryに同じroom_idが見つからない場合新しくRoomとEntryを作るような処理も追加している。

```
<% unless @user.id == current_user.id %>
          <% if @isRoom == true %>
            <p><a href="/rooms/<%= @roomId %>">メッセージを送る</a>
          <% else %>
            <%= form_for @room do |f| %>
              <%= fields_for @entry do |e| %>
                <%= e.hidden_field :user_id, :value=> @user.id %>
              <% end %>
            <%= f.submit "メッセージを送る" %>
            <% end %>
       <% end %>
  <% end %>
```
続いてviewをみる。
コントローラーと同様に、すでにチャットルームが作成している時と作成されていない時の条件分岐をさせるため、@isRoomを使用。
@isRoomがtrueの時は、チャットへボタンを出現させ、すでに作成されたチャットへと移行することができる。
また、チャットルームがまだ作成されていない場合は
@room = Room.new
@entry = Entry.new
にパラメーターを送らなければならない。
ここには親モデルの@roomにform_forインスタンス変数、@entryにfields_forインスタンス変数とする。
ここまででRoomsテーブルに保存されるための準備が整う。

**rooms_controller.rb**

```
class RoomsController < ApplicationController
  before_action :authenticate_user!
  def create
    @room = Room.create
    @entry1 = Entry.create(:room_id => @room.id, :user_id => current_user.id)
    @entry2 = Entry.create(params.require(:entry).permit(:user_id, :room_id).merge(:room_id => @room.id))
    redirect_to "/rooms/#{@room.id}"
  end

  def show
    @room = Room.find(params[: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
end
```

次にroomsコントローラーを理解する。

```
def create
    @room = Room.create
    @entry1 = Entry.create(room_id: @room.id, user_id: current_user.id)
    @entry2 = Entry.create(params.require(:entry).permit(:user_id, :room_id).merge(room_id: @room.id))
    redirect_to "/rooms/#{@room.id}"
end
```

createでは新しくRoomとEntryを作成する。Entryはcurrent_userとshowのuserの２つ分を登録する。
@room = Room.createでRoom_idを作成。

@entry1 = Entry.create(room_id: @room.id, user_id: current_user.id)
でcurrent_userのidと新しくできたroomのidを保存（current_userのEntry情報）

@entry2=Entry.create(params.require(:entry).permit(:user_id, :room_id).merge(room_id: @room.id))
でuser/showで代入した@userの情報をidへ代入。そこに新しくできた:room_idをmergeして登録する。
そしてcreateと同時に、チャットルームが開くようにredirectをする。

```
def show
  @room = Room.find(params[: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
```
showではまず該当するチャットルームを表示する必要があるため、findメソッドでparamsを検索する。検索したroomにEntriesテーブルに、現在ログインしているユーザーのidとそれにひもづいたチャットルームのidをwhereメソッドで探し、そのレコードがあるか確認する。もしその条件がfalseだったら、前のページに戻るための記述である、redirect_backを使う。
もしその条件がtrueだったら、Messageテーブルにそのチャットルームのidと紐づいたメッセージを表示させるため、@messagesにアソシエーションを利用した@room.messagesという記述を代入。

また、新しくメッセージを作成する場合は、メッセージのインスタンスを生成するために、Message.newをし、@messageに代入させる。

そしてrooms/show.html.erbでユーザーの名前などの情報を表示させるために、@room.entriesを@entriesというインスタンス変数に入れ、Entriesテーブルのuser_idの情報を取得します

```
messages_controller.rb
class MessagesController < ApplicationController
  before_action :authenticate_user!, :only => [:create]

  def create
    if Entry.where(:user_id => current_user.id, :room_id => params[:message][:room_id]).present?
      @message = Message.create(params.require(:message).permit(:user_id, :content, :room_id).merge(:user_id => current_user.id))
      redirect_to "/rooms/#{@message.room_id}"
    else
      redirect_back(fallback_location: root_path)
    end
  end
end
```

ここではメッセージをcreateする仕組みについて記述している。
form_forで送られてきたcontentを含む全てのメッセージの情報の:messageと:room_idのキーがちゃんと入っているかということを条件で確認する。
もしその条件がtrueだったら、メッセージを保存するためにMessage.createとし、Messagesテーブルにuser_id、:content、room_idのパラメーターとして送られてきた値を許可。
メッセージを送ったのは現在ログインしているユーザーなので、そのuser_idの情報をmergeさせます。
そしてその条件がfalseだった場合、フラッシュメーセージを出すための、flash.now[:alert]という記述をします。
最後にredirectで、どちらの条件の場合も元のページへとredirectさせれば完成です。