LoginSignup
0
0

More than 1 year has passed since last update.

Rail6を使った世界一わかりやすいDM機能の実装

Last updated at Posted at 2023-05-05

はじめに

今回紹介するのはRails6を使ったDM機能の実装方法です。

最終的にはこのような画面になります。
chat_image.jng.jpg

開発環境

  • AWS Cloud9
  • Ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [x86_64-linux]
  • Rails 6.1.7.3

使用中のGem

  • gem "devise"
  • gem "jquery-rails"

前提環境

  • Deviseのインストールが完了していて、ユーザーの新規登録やログインができる状態。
  • フォローやフォロワー機能がある状態(なくてもいいです)

実装

モデルの作成

rails g model Chat user_id:integer room_id:integer message:text
rails g model Room
rails g model UserRoom user_id:integer room_id:integer

RoomモデルはDMをする為の部屋番号を管理し、ChatモデルはChatの内容を管理。
UserRoomモデルはUserとRoomを結ぶ中間テーブルになっています。

では、いつも通り、migrateしましょう。

rails db:migrate

アソシエーションの設定

続いてアソシエーションの設定を行います。
まずはUserから

models/user.rb
has_many :user_rooms
has_many :chats
has_many :rooms, through: user_rooms

続いてRoom

models/room.rb
class Room < ApplicationRecord
  has_many :user_rooms
  has_many :chats
end

続いてUserRoom

models/userroom.rb
class UserRoom < ApplicationRecord
  belongs_to :user
  belongs_to :room
end

最後はChat

models/chat.rb
class Chat < ApplicationRecord
  balongs_to :user
  belongs_to :room

  validates :message, length: { in: 1..140 }

送信するメッセージは1~140字以内でないといけない、というバリデーションをつけてみました。アプリの企画によって変更してください。

ユーザーは多くの部屋に参加することができ、ルームは多くのユーザーを持つことができるため、多対多になります。ユーザーとチャットの関係性はユーザーは多くのチャットを投稿することができますが、一つのチャットにかかわるユーザーは一人なので多対一になります。

ルーティングの設定

config/routes.rb
resources :chats, only:[:show, :create]

コントローラの作成

rails g controller chats

それではコントローラに記述していきましょう

chats/controller.rb
class ChatsController < ApplicationController

 def show
   @user = User.find(params[:id:) #チャットする相手
   rooms = current_user_rooms.pluck(:room_id)
   user_rooms = UserRoom.find_by(user_id: @user_id, room_id; rooms) #チャットする相手とのルームがあるかの確認

   unless user_rooms.nil? #ユーザールームがある場合
     @room = user_rooms.room #変数@roomにユーザー(自分と相手)と紐づいているroomを代入
   else #ユーザールームが無かった場合
     @room = Room.new #新しくRoomを作成
     @room.save #そして保存
     UserRoom.create(user_id: current_user.id, room_id: @room.id) #自分の中間テーブルを作る
     UserRoom.create(user_id @user.id, room_id: @room.id) #相手の中間テーブルを作る
   end
   @chats = @room.chats #チャット一覧
   @chat = Chat.new(room_id: @room_id)#チャット投稿
 end

 def create
   @chat = current_user.chats.new(chat_params)
   @room = @chat.room
   @chats = @room.chats
   render :validater unless @chat.save
 end

private

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

ここでもし、相互フォローしてないとDMできないようにする為の記述を追加します。また、これを追加しないとURLに打ち込むと別のDMに行けてしまうのでその対策をします。

chats/controller.rb
class ChatsController < ApplicationController
  before_action :reject_non_related, only: [:show] #追加

 def show
   @user = User.find(params[:id:) #チャットする相手
   rooms = current_user_rooms.pluck(:room_id)
   user_rooms = UserRoom.find_by(user_id: @user_id, room_id; roomd) #チャットする相手とのルームがあるかの確認

   unless user_rooms.nil? #ユーザールームがある場合
     @room = user_rooms.room #変数@roomにユーザー(自分と相手)と紐づいているroomを代入
   else #ユーザールームが無かった場合
     @room = Room.new #新しくRoomを作成
     @room.save #そして保存
     UserRoom.create(user_id: current_user.id, room_id: @room.id) #自分の中間テーブルを作る
     UserRoom.create(user_id @user.id, room_id: @room.id) #相手の中間テーブルを作る
   end
   @chats = @room.chats #チャット一覧
   @chat = Chat.new(room_id: @room_id)#チャット投稿
 end

 def create
   @chat = current_user.chats.new(chat_params)
   @room = @chat.room
   @chats = @room.chats
   render :validater unless @chat.save
 end

private

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

 def reject_non_related #追加
   user = User.find(params[:id])
   unless current_user.following?(user) && user.following?(current_user)
     redirect_to user_path
  end
   end
end

レイアウトも整えてみよう

stylesheets/application.scss
.chat-container {
  display: flex;
  flex-direction: column;
  height: 100%;
  max-width: 800px;
  margin: 0 auto;
}

.chat-topbar {
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #1E90FF;
  color: #fff;
  height: 50px;
  padding: 0 20px;
}

.chat-messages {
  flex: 1;
  overflow-y: scroll;
  padding: 20px;
}

.message {
  display: flex;
  flex-direction: column;
  margin-bottom: 10px;
}

.message-sender {
  margin-bottom: 5px;
  font-size: 0.8em;
  color: #666;
}

.message-body {
  padding: 10px;
  border-radius: 10px;
  max-width: 70%;
}

.self .message-body {
  align-self: flex-end;
  background-color: #1E90FF;
  color: #fff;
}

.other .message-body {
  align-self: flex-start;
  background-color: #f5f5f5;
  color: #333;
}

.chat-form {
  background-color: #f5f5f5;
  padding: 20px;
}

.chat-form form {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
}

.chat-form input[type="text"] {
  flex: 1;
  margin-right: 10px;
  padding: 10px;
  font-size: 1em;
  border: none;
  border-radius: 5px;
  background-color: #fff;
}

.chat-form input[type="submit"] {
  flex: 0 0 auto;
  padding: 10px;
  font-size: 1em;
  border: none;
  border-radius: 5px;
  background-color: #29b6f6;
  color: #fff;
  cursor: pointer;
}

続いてビューの作成

非同期通信にしたいので部分テンプレートを作ります。

app/viewa/chat
に新しく[_message.js.erb]というファイルを作成してください

app/views/chats/_message.js.erb
<div class="chat-messages">
  <% chats.each do |chat| %>
    <% if chat.user_id == current_user.id %>
      <div class="message self">
        <div class="message-body">
          <%= chat.message %>
        </div>
      </div>
    <% else %>
      <div class="message other">
        <div class="message-sender"><%= chat.user.name %></div>
        <div class="message-body">
          <%= chat.message %>
        </div>
      </div>
    <% end %>
  <% end %>
</div>

さらに、app/viewa/chat
に新しく[_message.html.erb]を作成。

app/views/chats/_message.html.erb
<div class="chat-messages">
  <% chats.each do |chat| %>
    <% if chat.user_id == current_user.id %>
      <div class="message self">
        <div class="message-body">
          <%= chat.message %>
        </div>
      </div>
    <% else %>
      <div class="message other">
        <div class="message-sender"><%= chat.user.username %></div>
        <div class="message-body">
          <%= chat.message %>
        </div>
      </div>
    <% end %>
  <% end %>
</div>

続いて
app/viewa/chat
に新しく[show.html.erb]を作成。

app/views/chats/show.html.erb
<div class="chat-container">
  <div class="chat-topbar">
    <h2><%= @user.username %> さんとのチャット</h2>
  </div>
  <div class="message">
    <%= render "chats/messages", chats: @chats %>
  </div>

  <div class="chat-form">
    <%= form_with model: @chat, data: {remote: true} do |f| %>

      <%= f.text_field :message, placeholder: "メッセージを入力してください", autocomplete: "off" %>
      <%= f.hidden_field :room_id, value: @room.id %>
      <%= f.submit "送信" %>
    <% end %>
  </div>
</div>

続いて
app/viewa/chat
に新しく[create.js.erb]を作成。

app/views/chats/create.js.erb
$('.message').html("<%= j(render 'chats/messages', chats: @chats) %>");
$('input[type=text]').val("")

続いて
app/viewa/chat
に新しく[validator.js.erb]を作成。

app/views/chats/validator.js.erb
$('.errors').html("<%= j(render 'layouts/errors', obj: @chat) %>");

続いて
app/javasprict/chats
に新しく[validator.js]を作成。

app/javasprict/chats/validator.js.erb
$(document).ready(function() {
  $('.errors').html("<%= j(render 'layouts/errors', obj: @chat) %>");
});

最後に
app/viewa/chat
に新しく[_info.html.erb]を作成

app/views/chats/_info.html.erb
<% if current_user != @user && current_user.following?(@user) && @user.following?(current_user) %>
  <%= link_to 'チャットを始める', chat_path(@user.id), class: "ml-3" %>
<% end %>

長くなりましたが以上で実装の完了です!
お疲れ様でした。

もしよければコメント、いいねお待ちしてます!

0
0
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
0