0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

相互フォロー間のユーザー同士でDMを送る方法

Posted at

忘備録、並びに考え方の整理として投稿します
初学者なので効率的なコードの書き方などは出来ていないかと思います。

今回の目的
相互フォローをしているユーザー間でDMを送り合いたい

前提条件
ruby 3.1.2
Rails 6.1.7
device、bootstrap導入済み(bootstrapは見た目の問題なので機能には関係なし)
User及びフォロー・フォロワー機能作成済み

ER図は下記のようになります
作成するのはこの内UserRoom, Room, Chatの3つ
ER図_8a.png

1.モデルの作成

integer型か、既存のテーブルを参照するreferences型のどちらかで記述
references型では若干書き方が違う( 〜_idが不要になる)ので注意

ターミナルの書き方.rb
rails g model Room
rails g model UserRoom user_id:integer room_id:integer
rails g model Chat user_id:integer room_id:integer message:text

references書く場合はこちら
referencesでRoomを参照するために先にRoomを作成

ターミナルの書き方.rb
rails g model Room
rails g model UserRoom user:references room:references
rails g model Chat user:references room:references message:text

完了したらmigrateも忘れずに

rails db:migrate

2.アソシエーションの設定

1人のユーザーはたくさんの投稿が出来るので
ユーザー:チャット=1対N

1人のユーザーはたくさんのRoomに所属できる
1つのRoomは2人のユーザーを持つ
ユーザー:ルーム=多対多の関係
今回はuser_roomを中間テーブルとします
よって下記のように記載

user.rb
class User < ApplicationRecord
  #下記を追記
  has_many :user_rooms
  has_many :rooms, through: :user_rooms
  has_many :chats
end
room.rb
class Room < ApplicationRecord
  has_many :user_rooms
  has_many :chats
end
user_room.rb
class UserRoom < ApplicationRecord
  #referencesで書いた場合はすでに入力されているかと思います
  belongs_to :user
  belongs_to :room
end
chat.rb
class Chat < ApplicationRecord
  belongs_to :user
  belongs_to :room
  validates :message, presence: true, length: {maximum: 140}
  #validatesで、空の投稿と、文字の制限
end

3.コントローラの作成、ルーティング

rails rails g controller chats show
routes.rb
Rails.application.routes.draw do
  get 'chats/show' #←削除する
  resources :chats, only: [:show, :create] #←こちらを記述 
end

相互フォローの確認

chats_controller.rb
class ChatsController < ApplicationController
  before_action :following_check, only: [:show]
  
  def show
  end

  private
  def following_check
    user = User.find(params[:id])
    unless current_user.following?(user) && user.following?(current_user)
      redirect_to books_path
    end
  end
end

before_action でshowページにアクセスしたときのみfollowing_checkを行います。

current_user.following?(user) && user.following?(current_user)とは?
自分はUser.findで探してきたidのユーザーをフォローしているか、かつUser.findで探してきたidのユーザーは自分をフォローしているかを確認する

例として下記のようなRelationshipテーブルがあった時
・自分のuser_idが1、相手のuser_idが2である時 = true
・自分のuser_idが1、相手のuser_idが3である時 = false ※自分しかフォローしていない
・自分のuser_idが1、相手のuser_idが4である時 = false ※相手しかフォローしていない
Relationshipテーブル.png
但し、通常のifとは違い今回は unless なので、falseの時に本の一覧画面にリダイレクトします。

※following?はフォロー・フォロワー機能作成時に記述した中間テーブル及びメソッドです。
名称は違うかもしれません、各自確認ください。

user.rb
has_many :active_relationships,  class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy
has_many :followings, through: :active_relationships,  source: :followed

  def following?(user)
   followings.include?(user)#followingsは上記のfollowingsを指す
  end

showの作成

chats_controller.rb
def show
    @user = User.find(params[:id])#① 送られてきたidでUserを検索
    rooms = current_user.user_rooms.pluck(:room_id) #② current_userの持つroom_idを取得
    user_rooms = UserRoom.find_by(user_id: @user.id, room_id: rooms) #③ ①と②を用いて合致するUserRoomが有るか探す
    if user_rooms.nil? # ③がnil、つまり見つからなかった場合
      chat_room = Room.new # 新しくRoomを作成
      chat_room.save
      UserRoom.create(user_id: current_user.id, room_id: chat_room.id)
      UserRoom.create(user_id: @user.id, room_id: chat_room.id)
      # 新しくUserRoomを作成(createは保存も同時に行われるのでsave不要)
    else
      chat_room = user_rooms.room #user_roomのroomの情報を抜き出す = roomのidを取得 
      #roomはuserモデルで記載したhas_many :room, through: :user_rooms のroom
    end
    @chats = @room.chats #roomのidに合致するchatの内容を取得する
    #chatsはuserモデルで記載したhas_many :chats のchats
    @chat = Chat.new(room_id: chat_room.id) #空のインスタンスの作成
  end

解説

rooms = current_user.user_rooms.pluck(:room_id)
自分のuser_idと一致するUserRoomのroom_idを取得
例:自分のidが1であった場合、user_id = 1と一致する room_id 1、4 を取得
user_room.png
user_rooms = UserRoom.find_by(user_id: @user.id, room_id: rooms)
上記のroomsと最初に取得してきた相手のuser_idを用いて合致するUserRoomが有るか探す
例:取得した相手のidが2であった場合、user_id = 2 かつroom_idが 1or4 であるものを探す。この場合は無し
user_room.png
上の図のように合致するものがない場合
・新しくRoomを作成
・save (saveしないとRoomのidが発行されない為)
・Roomのidを用いてUserRoomを新規作成となる

chat_room = user_rooms.room
roomの情報を取得する。
例:rails consoleで確認すると以下のように表示される

[2] pry(main)>chat_room = user_room.room
id: 1,created_at:以下略

@chats = chat_room.chats
先程取得したchat_roomと合致するchatテーブルの情報を持ってくる
例:上記のrails consoleの続き。chat_roomで取得したid、つまりroom_id:1のデータを取得

[3] pry(main)>chat_room.chats
    id: 6,
  user_id: 2,
  room_id: 1,
  message: "hello",
  created_at:以下略

    id: 7,
  user_id: 2,
  room_id: 1,
  message: "good",
  created_at:以下略 

createの作成

chats_controller.rb
  def create
    @chat = current_user.chats.new(chat_params)
    @chat.save
    redirect_to request.referer
  end

  private
  def chat_params
    params.require(:chat).permit(:message, :room_id)
  end

  def following_check
  #以下略

DMの投稿は常に自分であるためcurrent_user.chats.newになる
最初に設定したストロングパラメータの設定も忘れずに

4.viewの作成

show.html.rb
<div class="container">
  <div class ="row">
    <div class="col-8">
      <div class="d-flex justify-content-between">
        <h5 class="text-left">ユーザー名:  <%= @user.name %></h5>
        <h5 class="text-right">ユーザー名:  <%= current_user.name %></h5>
      </div>
      <% @chats.each do |chat| %>
        <% if chat.user_id == current_user.id %>
          <p class="text-right"><%= chat.message %></p>
        <% else %>
          <p class="text-left"><%= chat.message %></p>
        <% end %>
      <% end %>
    
    <%= form_with model: @chat do |f| %>
      <%= f.text_field :message %>
      <%= f.hidden_field :room_id %>
      <%= f.submit "送信" %>
    <% end %>
    </div>
  </div>
</div>

bootstrapの設定はお好みでどうぞ
完成形はこんな形になります
ちなみに送信ボタンは削除しても問題ありません。エンターで送信されます。
user_room.png

チャットを開始するボタンの設置

元々作成してあるフォロー・フォロワーボタンの部分に追加

_info.html.rb
<div class ="mb-4">
  <% unless current_user == user %>
    <% if current_user.following?(user) %>
      <%= link_to "フォロー外す",user_relationship_path(user.id), method: :delete, class: "btn btn-info" %>
    <% else %>
      <%= link_to "フォローする",user_relationships_path(user.id), method: :POST, class: "btn btn-success" %>
    <% end %> #ここから下
    <% if current_user != user && current_user.following?(user) && user.following?(current_user) %>
      <%= link_to 'chatを始める', chat_path(user.id) %>
    <% end %> #ここまで
  <% end %>
</div>

補足:非同期化の場合

イマイチ理解が足りてません。一応問題なく動いてはいますが、違う方法等有りましたらコメント頂けると幸いです。

まずshowページの一部部分テンプレート化。
この時chats/indexのdivに、id = "js_chat_list"を記述しています。

show.html.rb
<div class="container">
  <div class ="row">
    <div class="col-8">
      <div class="d-flex justify-content-between">
        <h5 class="text-left">ユーザー名:  <%= @user.name %></h5>
        <h5 class="text-right">ユーザー名:  <%= current_user.name %></h5>
      </div>
      <div id = "js_chat_list"> #ここから、idも記述
        <%= render 'chats/index', chats: @chats %>
      </div>
      <div>
        <%= render 'chats/form', chat: @chat %>
      </div> #ここまで
    </div>
  </div>
</div>

app/views/chats以下に_indexと_formのファイルを作成してshowの内容を移す

_index.html.rb
<% chats.each do |chat| %>
  <% if chat.user_id == current_user.id %>
    <p class="text-right"><%= chat.message %></p>
  <% else %>
    <p class="text-left"><%= chat.message %></p>
  <% end %>
<% end %>

_formにはlocal: false、text_fieldにclassを設定しています。

_form.html.rb
<%= form_with model: @chat, local: false do |f| %> #local:false追加
  <%= f.text_field :message, class:'message' %>  #class:'messege'追加
  <%= f.hidden_field :room_id %>
  <%= f.submit "送信" %>
<% end %>

chats_controllerではredirect_toを削除、こうすることでcreate.js.erbを探しに行ってくれます
またrender処理と同じように値を渡してあげなければならないため
chat_room = @chat.room ←roomのidを取得
@chats = chat_room.chats ←取得したroomのidを元にchatの内容を取得
を追加します

chats_controller.rb
  def create
    @chat = current_user.chats.new(chat_params)
    @chat.save
    # redirect_to request.referer ←削除
    chat_room = @chat.room  #追加
    @chats = chat_room.chats #追加
  end

最後にcreate.js.erbの書き方
記述場所はapp/views/chats/create.js.erbつまりchatsのviewファイル内になります
js_chat_listはshowのdivに記述した、id = "js_chat_list"のこと
また.messageは_formのext_fieldに設定したclass名、val('');は空欄にする処理

create.js.rb
$("#js_chat_list").html("<%= j(render 'chats/index', chats: @chats) %>");
$(".message").val('');

最後に

長々と書きましたがご覧いただいた方、ありがとうございました
意見、感想等ありましたらご自由にご記入ください

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?