🍎

【Rails】DM機能実装(相互フォロワーのみ利用可能)

2023/08/16に公開
7

はじめに

  • フォロー機能実装済
  • ユーザー同士で1対1のDMができるようにする(140字まで送信可能)
  • 相互フォロワーのみDM機能が利用できる
  • 完成イメージ(相互フォロワーだとDMボタンが表示される) ↓

altテキスト
チャットルーム ↓
altテキスト

1.テーブル設計確認

altテキスト

  • DM機能には4つのテーブルを使用「Usersテーブル」「Entriesテーブル」「Roomsテーブル」「Messagesテーブル」
  • DMは2人のユーザーがチャットルームにてメッセージをやりとりするイメージ
  • ユーザーの組み合わせ毎にチャットルームが必要なため、Usersテーブル:Roomsテーブルは多:多
    • そのため、中間テーブルとしてEntriesテーブルを用意
    • ユーザー同士は複数のメッセージをやりとりする可能性があるため、中間テーブルとしてMessagesテーブルを用意

2.モデル作成

ターミナル
$ rails g model Entry user:references room:references
$ rails g model Room user:references
$ rails g model Message user:references room:references message:text
ターミナル
$ rails db:migrate

3.アソシエーション設定

app/models/user.rb
has_many :entries, dependent: :destroy
has_many :messages, dependent: :destroy
app/models/entry.rb
belongs_to :user
belongs_to :room
app/models/room.rb
has_many :entries, dependent: :destroy
has_many :messages, dependent: :destroy
app/models/message.rb
belongs_to :user
belongs_to :room
  
validates :message, presence: true, length: { maximum: 140 }
#メッセージが空欄はNG、かつ140字以内

4.ルーティング設定

config/routes.rb
:
resources :users, only: [:index, :show, :edit, :update ] do
  member do
    get :follows, :followers
  end
    resource :relationships, only: [:create, :destroy]
end
:
resources :messages, only: [:create]
resources :rooms, only: [:create, :show]

1.相互フォロワーのユーザーページで「DMを送る」というボタンを表示させる
2.ボタンを押すとroomsがcreateされる
3.roomsのshowページへ遷移する
4.roomsのshowページでmessageがcreateされる

5.コントローラー作成

ターミナル
$ rails g controller rooms
$ rails g controller messages

6.Usesコントローラー編集

app/controllers/users_controller.rb
  def show
    @user = User.find(params[:id])
    @current_entry = Entry.where(user_id: current_user.id)
    @another_entry = Entry.where(user_id: @user.id)
    unless @user.id == current_user.id
      @current_entry.each do |current|
        @another_entry.each do |another|
          if current.room_id == another.room_id then
            @is_room = true
            @room_id = current.room_id
          end
        end
      end
      if @is_room
      else
        @room = Room.new
        @entry = Entry.new
      end
    end
    @book = Book.new
    @books = @user.books
  end

解説!

app/controllers/users_controller.rb
@user = User.find(params[:id])
@current_entry = Entry.where(user_id: current_user.id)
@another_entry = Entry.where(user_id: @user.id)
  • showページ用にレコードからユーザー1人1人の情報を取得する必要があるため、findメソッドを使用
  • roomがcreateされた際、ログインしているユーザーをEntriesテーブルに記録するためにwhereメソッドでcurrent_user.idを探す
  • roomがcreateされた際、「DMを送る」ボタンを押された側のユーザーをEntriesテーブルに記録するためにwhereメソッドでuser.idを探す
app/controllers/users_controller.rb
  unless @user.id == current_user.id
    @current_entry.each do |current|
      @another_entry.each do |another|
        if current.room_id == another.room_id then
          @is_room = true
          @room_id = current.room_id
        end
      end
    end
    if @is_room
    else
      @room = Room.new
      @entry = Entry.new
    end
  end
  @book = Book.new
  @books = @user.books
end
  • unlessを使用することでroomsが作成されている場合とされていない場合に条件分岐させる
  • 作成済みであれば、「@current_entry」と「@another_entry」をeachで一つずつ取り出し、それぞれEntriesテーブル内にあるroom_idが共通しているユーザー同士に対して「@room_id = current.room_id」という変数を指定
  • ここで、すでに作成されているroom_idを特定することができる
  • 「@is_room = true」は、これがfalseであるとき、else以降で新たにroomを作成する

7.ビュー編集

app/views/users/show.html.erb
<% unless @user.id == current_user.id %>
  <% if (current_user.following? @user) && (@user.following? current_user) %>
  <% if @is_room == true %>
    <p class="user-show-room"><a href="/rooms/<%= @room_id %>" class="btn btn-primary btn-sm">DMを送る</a></p>
  <% else %>
    <%= form_for @room do |f| %>
      <%= fields_for @entry do |e| %>
        <%= e.hidden_field :user_id, value: @user.id %>
      <% end %>
      <%= f.submit "DMをはじめる", class:"btn btn-primary btn-sm" %>
    <% end %>
  <% end %>
  <% end %>
<% end %>

解説!

app/views/users/show.html.erb
<% unless @user.id == current_user.id %>
  <% if (current_user.following? @user) && (@user.following? current_user) %>
  <% if @is_room == true %>
    <p class="user-show-room"><a href="/rooms/<%= @room_id %>" class="btn btn-primary btn-sm">DMを送る</a></p>
  <% else %>
:
:
  <% end %>
  <% end %>
<% end %>
  • 現在ログインしているユーザーではない かつ 相互フォロー状態 の時、で条件分岐させる
  • 「@is_room」を使用してroomsが作成されているか条件分岐させる
  • 「@is_room」がtrueの時、「DMを送る」ボタンが表示され、チャットルームに移動できる
app/views/users/show.html.erb
<%= form_for @room do |f| %>
  <%= fields_for @entry do |e| %>
    <%= e.hidden_field :user_id, value: @user.id %>
  <% end %>
  <%= f.submit "DMをはじめる", class:"btn btn-primary btn-sm" %>
<% end %>
  • 「@is_room」がfalseの時、form_forを使用してコントローラーにパラメーターを送る
  • レコードには親モデルのRoomsテーブルと子モデルのEntriesテーブルの両方を保存する必要があるため、親モデルには「form_for インスタンス変数」, 子モデルには「fields_for インスタンス変数」としている
  • Entriesテーブルのレコードにはuser_idを送る必要があるため、「hidden_field」で@user.idをvalueにおく

ここまでで、roomsテーブルに保存されるための準備が整った!

8.roomsコントローラー編集

app/controllers/rooms_controller.rb
class RoomsController < ApplicationController
  before_action :authenticate_user!

  def create
    @room = Room.create(user_id: current_user.id)
    @current_entry = Entry.create(user_id: current_user.id, room_id: @room.id)
    @another_entry = Entry.create(params.require(:entry).permit(:user_id, :room_id).merge(:room_id => @room.id))
    redirect_to room_path(@room)
  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
      @my_account = current_user.id
    else
      redirect_back(fallback_location: root_path)
    end
  end
end

解説!

app/controllers/rooms_controller.rb
class RoomsController < ApplicationController
  def create
    @room = Room.create(user_id: current_user.id)
    @current_entry = Entry.create(user_id: current_user.id, room_id: @room.id)
    @another_entry = Entry.create(params.require(:entry).permit(:user_id, :room_id).merge(:room_id => @room.id))
    redirect_to room_path(@room)
  end
:
end
  • users/show.html.erbの「<%= form_for @room do |f| %>」で送られてきたパラメータを受け取り、createさせる
  • また、このcreateメソッドではRoom以外にその子モデルのEntryもcreateさせる必要があるため、Entriesテーブルに入る相互フォロー同士のユーザーを保存させるための記述をする
    • ログイン中のユーザーに対しては「@current_entry」とし、EntriesテーブルにRoom.createで作成された@roomにひもづくidと、ログイン中のユーザーのidを保存させる記述をする
    • フォローされている側のユーザーは「@another_entry」とする。users/show.html.erbの「fields_for @entry」で保存したparamsの情報(:user_id, :room_id)を許可し、ログイン中のユーザーと同じく@roomにひもづくidを保存する記述をする
  • そしてcreateと同時にチャットルームが開くようにredirectをする
app/controllers/rooms_controller.rb
class RoomsController < ApplicationController
:
  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
      @my_account = current_user.id
    else
      redirect_back(fallback_location: root_path)
    end
  end
end
  • roomsのshowアクションでは、まず1つのチャットルームを表示させる必要があるため、findメソッドを使う
  • Entriesテーブルにログイン中のユーザーのidと、それにひもづいたチャットルームのidをwhereメソッドで探し、そのレコードがあるかを確認する
    • 条件がfalseだった場合、redirect_backで前のページに戻る
    • 条件がtrueだった場合、Messagesテーブルにそのチャットルームのidとひもづいたメッセージを表示させるために@messagesにアソシエーションを利用した「@room.messages」という記述を代入する
    • 新しくメッセージを作成する場合、メッセージのインスタンスを生成するためにMessage.newで「@message」に代入する
      そしてrooms/show.html.erbでユーザーの情報を表示させるために@room.entriesを@entriesというインスタンス変数に入れ、Entriesテーブルのuser_idの情報を取得
  • 「@my_account = current_user.id」はチャットルームで「〇〇さんとのメッセージ」と相手の名前を表示させるための記述

9.rooms/show ビュー作成

テーブルタグを使用してこのようなページを作成!
altテキスト

app/views/rooms/show.html.erb
<div class="container">
  <div class="row">
    <% @entries.each do |e| %>
      <% if @my_account != e.user.id %>
        <h4><strong><%= e.user.name %>さんとのメッセージ</strong></h4>
      <% end %>
    <% end %>
  </div>
  <div class="row my-3">
    <%= link_to "Usersページに戻る", users_path, class:"btn btn-secondary btn-sm" %>
  </div>

  <div class="row">
    <table class="table table-hover">
    <% if @messages.present? %>
      <% @messages.each do |m| %>
        <tr>
          <td><%= m.user.name %></td>
          <td><%= m.message %></td>
        </tr>
      <% end %>
    <% end %>
    </table>
  </div>

  <div class="row">
  <%= form_for @message do |f| %>
    <%= f.text_field :message, placeholder: "メッセージを入力して下さい" , size: 50, class:"form-text-field" %>
      <%= f.hidden_field :room_id, value: @room.id %>
      <%= f.submit "送信",class:"btn btn-success"%>
  <% end %>
  </div>
</div>

解説!

app/views/rooms/show.html.erb
<div class="row">
  <% @entries.each do |e| %>
    <% if @my_account != e.user.id %>
      <h4><strong><%= e.user.name %>さんとのメッセージ</strong></h4>
    <% end %>
  <% end %>
</div>
  • 「@entries」の相互フォロワー同士の情報をとりたいため、eachでフォロー・フォロワーの情報を取得している
  • 「<% if @my_account != e.user.id %>」rommsコントローラーで@my_accountにログイン中のユーザーIDを代入しているため、「!=」とすることでDM送信相手の名前を表示させる
app/views/rooms/show.html.erb
  <div class="row">
    <table class="table table-hover">
    <% if @messages.present? %>
      <% @messages.each do |m| %>
        <tr>
          <td><%= m.user.name %></td>
          <td><%= m.message %></td>
        </tr>
      <% end %>
    <% end %>
    </table>
  </div>
  • <% if @messages.present? %>でDMが存在するかを確認し、メッセージのやりとりがあればeachでメッセージを表示させる
app/views/rooms/show.html.erb
  <div class="row">
  <%= form_for @message do |f| %>
    <%= f.text_field :message, placeholder: "メッセージを入力して下さい" , size: 50, class:"form-text-field" %>
      <%= f.hidden_field :room_id, value: @room.id %>
      <%= f.submit "送信",class:"btn btn-success"%>
  <% end %>
  </div>
</div>
  • DMを送信するためのフォームを、form_forで作成
  • <%= form_for @message do |f| %>では、@messageの他にこのmessageがどのroomに所属しているかを判断するためにhidden_fieldにroomの情報を持たせる
  • このformで送られたパラメーターがmessages_controllerに送られる

10.messagesコントローラー編集

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

  def create
    message = Message.new(message_params)
    message.user_id = current_user.id
    if message.save
      redirect_to room_path(message.room)
    else
      redirect_back(fallback_location: root_path)
    end
  end
  
  private
  
  def message_params
    params.require(:message).permit(:room_id, :message)
  end

end

  • rooms/show.html.erbから送られたパラメータをcreateする

ここまでで相互フォロワーであればDMを送信できるようになりました!!!

  • 相互フォロワーではない状態
    altテキスト
  • 相互フォロワー、DM未送信状態
    altテキスト
  • DM送信後
    altテキスト
  • DMメッセージ用のページ
    altテキスト

こちらの記事を参考にさせていただきました🙏

https://qiita.com/bindingpry/items/6790c91f374acc25bea2

https://qiita.com/aaaasahi_17/items/9e7f344488c720aaf116

Discussion

大学生だった.大学生だった.

コメント失礼します!
この記事であるER図って何を使って描かれているのでしょうか。

花

「draw.io」というツールを使いました!
通っていたオンラインスクールでおすすめされたので使ってます🙌

ファミリーファミリー

コメント失礼します。この記事を参考にDM機能を実装しようとしたら、room_idにnilが入ってしまいました。何か対処法はありませんか?
以下はターミナルでのログです

花

初めまして!
原因か定かではないのですが、自分の投稿を確認していたところアソシエーションの記述に誤りがありました…すみません!

正しくは下記です🙇
altテキスト

それ以外ですとusers/show.html.erbのform_forで正しく情報が送られていないことでroom_idがnilになっているのかと思うのですが解決策がわからずです😥

ファミリーファミリー

アソシエーションを正しく記述したら、無事room_idに値が入りました。ありがとうございます。
返信遅くなり大変失礼しました。😞

花

よかったです🙌✨
コメントありがとうございました^^
記述ミス気を付けますっ!!