search
LoginSignup
8

More than 1 year has passed since last update.

posted at

updated at

[Rails] DM機能を解説する

ポートフォリオに実装した機能を解説していきます!
今回はDM(ダイレクトメッセージ)機能。

目標

ezgif.com-gif-maker (1).gif

機能としては、
ユーザー詳細ページからメッセージルームに飛んで個別にメッセージ、
またやりとりしたメッセージルームは一覧でマイページから取得できる。
ざっくりですがこんな感じ
では行こう!

前提

前提としてdeviseの導入を忘れずに!
deviseの導入はこちらの記事を参考にしてください。

設計

4つのモデルを使って実装します。
Userモデル => Userの情報(devise)
Roomモデル => 自分と相手の2人のユーザーが入ります
Entryモデル => どのUserがどのRoomに所属しているかを判断
Messageモデル => UserがどのRoomでどんなMessageを送ったか

スクリーンショット 2021-06-20 9.10.13.png

ER図はこんな感じ
User1人1人は他のUserに対して沢山のRoomを持っていることになるのでUserとRoomの関係性は多対多になり中間テーブルとしてEntryテーブルを用意。
またRoom内で2人のUserが沢山のMessageでやりとりするのでこちらも多対多で中間テーブルにMassegeを用意します

実装

では行こう!!

モデルの準備

ターミナル
$ rails g model room
$ rails g model entry user:references room:references
$ rails g model message user:references room:references body:text
ターミナル
$ rails db:migrate

リレーション

user.rb
 has_many :entries, dependent: :destroy
 has_many :messages, dependent: :destroy
room.rb
 has_many :entries, dependent: :destroy
 has_many :messages, dependent: :destroy
entry.rb
 belongs_to :user
 belongs_to :room
message.rb
 belongs_to :user
 belongs_to :room

routes

routes.rb
resources :messages, only: [:create]
resources :rooms, only: [:create, :index, :show]

Controller & View

users_controller.rb

users_controller.rb
class UsersController < ApplicationController
  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
            @is_room = true
            @room_id = current.room_id
          end
        end
      end
      unless @is_room
        @room = Room.new
        @entry = Entry.new
      end
    end
  end

解説

@current_entry = Entry.where(user_id: current_user.id)
@another_entry = Entry.where(user_id: @user.id)

ログインしてるユーザーとメッセージ相手のユーザー情報をEntryテーブルから検索して取得します。

unless @user.id == current_user.id
  @current_entry.each do |current|
    @another_entry.each do |another|
      if current.room_id == another.room_id
        @is_room = true
        @room_id = current.room_id
      end
     end
   end
   unless @is_room
     @room = Room.new
     @entry = Entry.new
    end
  end
end

unlessでログインしていないユーザーという条件をつけます。
そしてさっき取得した2つのユーザー情報をそれぞれeachで取り出してEntryテーブル内に同じroom_idが存在するかどうかを調べます。
同じroom_idが存在する場合は既にroomが存在しているということなのでroom_idの変数とroomが存在するかどうかの条件であるis_roomを渡します。
同じroom_idが存在しない場合は新しくインスタンスを作成します。

users/show.html.haml

続いてviewです。
viewはhamlで書いてます。

show.html.haml
- unless @user.id == current_user.id
  - if @is_room == true
    = link_to 'メッセージへ', room_path(@room_id)
  - else
    = form_for @room do |f|
      = fields_for @entry do |e|
        - e.hidden_field :user_id, value: @user.id 
      = f.button type: :submit do
        メッセージを送る  

ここではログインしているユーザーではないという条件をつけてis_roomの条件を使って既に部屋が存在しているかどうかで分岐させます。
既に部屋が存在していればその部屋に、していなければformでコントローラーにパラメーターを送っています。

rooms_controller.rb

rooms_controller.rb
class RoomsController < ApplicationController
  before_action :authenticate_user!
  def create
    room = Room.create
    current_entry = Entry.create(user_id: current_user.id, room_id: room.id)
    another_entry = Entry.create(user_id: params[:entry][:user_id], room_id: room.id)
    redirect_to room_path(room)
  end

  def index
    # ログインユーザー所属ルームID取得
    current_entries = current_user.entries
    my_room_id = []
    current_entries.each do |entry|
      my_room_id << entry.room.id
    end
    # 自分のroom_idでuser_idが自分じゃないのを取得
    @another_entries = Entry.where(room_id: my_room_id).where.not(user_id: current_user.id)
  end

  def show
    @room = Room.find(params[:id])
    @messages = @room.messages.all
    @message = Message.new
    @entries = @room.entries
    @another_entry = @entries.where.not(user_id: current_user.id).first
  end
end 

解説

def create
  room = Room.create
  current_entry = Entry.create(user_id: current_user.id, room_id: room.id)
  another_entry = Entry.create(user_id: params[:entry][:user_id], room_id: room.id)
  redirect_to room_path(room)
end

ここでusers/show.html.hamlで部屋が存在しなかった場合にformで送られてきたパラメーターきます。
やっていることとしては現在ログインしているユーザーとメッセージ相手のユーザーそれぞれの情報をroom_idで紐付けてEntryテーブルにcreateしています。

def index
  current_entries = current_user.entries
  my_room_id = []
  current_entries.each do |entry|
    my_room_id << entry.room.id
  end
  @another_entries = Entry.where(room_id: my_room_id).where.not(user_id: current_user.id)
end

続いてindex
indexでは現在やりとりしているroom一覧を取得します。

current_entries = current_user.entries
my_room_id = []
current_entries.each do |entry|
  my_room_id << entry.room.id
end

まずここでログインユーザーがやりとりしているroomのIDをすべて取得しそれを配列化してmy_room_idとします。

@another_entries = Entry.where(room_id: my_room_id).where.not(user_id: current_user.id)

そしてEntryテーブルからmy_room_idでuser_idが自分のIDじゃないレコードを取り出します。
こうすることで現在自分が参加中のroomを相手の情報で取得しviewで表示することができます。

rooms/index.html.haml

index.html.haml
- @another_entries.each do |entry|
  = link_to room_path(entry.room) do
    .card
      .card-body
         %div
           .d-flex
             .chat-avatar
                %object
                  = link_to user_path(entry.user) do
                    = image_tag entry.user.avatar_image
              .chat-user-name
                = entry.user.display_name
           chat-text
             = Message.find_by(id: entry.room.message_ids.last)&.body
 = Message.find_by(id: entry.room.message_ids.last)&.body

ここではroomの最後のメッセージを表示しています。

def show
    @room = Room.find(params[:id])
    @messages = @room.messages.all
    @message = Message.new
    @entries = @room.entries
    @another_entry = @entries.where.not(user_id: current_user.id).first
    end
  end

ここでは@roomで1つのroomを表示
@messagesで過去のやりとりを全て表示し@messageで新しいメッセージを作るためのインスタンスを作成。
@@entries@another_entryでview側に相手の名前を表示させています。

rooms/show.html.haml

show.html.haml
.title
  %h2 
    = ("#{@another_entry.user.email}さんとのメッセージ")

- @messages.each do |m|
  - if m.user_id == current_user.id
    .mycomment
      %p 
        = m.body
- else 
  .fukidasi
    .faceicon 
      = image_tag m.user.email
    .chatting
      .says
        %p 
          = m.body

.chat-form-box
  = form_for @message do |f|
    .chat-form-group
      = f.text_field :body
      = f.hidden_field :room_id, :value => @room.id
      = f.submit "送信する"

@anoter_entryで相手の名前やメールアドレスで表示させることができます。

@messagesをそれぞれIDで分岐させることでLineっぽく自分のメッセージは右、相手のメッセージは左みたいなスタイルを作ることができます。

form部分では@messageの他にこのmessageがどのroomに所属しているかを判断するためにhidden_fieldにroomの情報を持たせてます。

そしてこのformで送られたパラメーターがmessages_controllerに渡ります。

messages_controller.rb

messages_controller.rb
class MessagesController < ApplicationController
  before_action :authenticate_user!

  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, :body)
    end
end 

最後にmessage部分。
rooms/show.html.hamlから送られたパラメータをcreateします。
やっていることは、自分のmessageかどうかで分岐させてtrueならroom_idとbodyをパラメーターで許可してcreateしています。
この辺はいつものcreate actionと同じですね。

完成

中間テーブルが2つあって複雑ですが1つ1つ取り出してみるとやっていることは初心者の僕でも理解できるような内容でした。

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
What you can do with signing up
8