質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Ruby on Rails 5

Ruby on Rails 5は、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

Q&A

0回答

162閲覧

Message機能でユーザー間の最後のメッセージを効率よく取得する

mashoo

総合スコア8

Ruby on Rails 5

Ruby on Rails 5は、オープンソースのWebアプリケーションフレームワークです。「同じことを繰り返さない」というRailsの基本理念のもと、他のフレームワークより少ないコードで簡単に開発できるよう設計されています。

Ruby

Rubyはプログラミング言語のひとつで、オープンソース、オブジェクト指向のプログラミング開発に対応しています。

0グッド

0クリップ

投稿2020/03/15 06:48

ユーザー同士がチャットできる機能を実装しています。
メッセージの送信は実装出来ています。
ユーザー同士のメッセージのやりとりの中での、最後のメッセージを効率よく取得する方法を探しています。
LINEやTwitterのチャット一覧をイメージしています。
最後のメッセージと、相手の名前、画像が表示される画面です。

前提条件

ruby

1# routes.rb 2resources :users, only: %i[index show] do 3 resources :messages, only: %i[index create] do 4 collection do 5 get :dashbord 6 end 7 end 8end

ruby

1# user.rb 2class User < ApplicationRecord 3 has_many :from_messages, class_name: 'Message', 4 foreign_key: 'from_id', dependent: :destroy 5 has_many :to_messages, class_name: 'Message', 6 foreign_key: 'to_id', dependent: :destroy 7 8 def send_message(other_user, room_id, content) 9 from_messages.create!(to_id: other_user.id, room_id: room_id, content: content) 10 end 11end

ruby

1# message.rb 2class Message < ApplicationRecord 3 belongs_to :from, class_name: 'User' 4 belongs_to :to, class_name: 'User' 5 6 def self.recent_in_room(room_id) 7 where(room_id: room_id).last(500) 8 end 9end

ruby

1class MessagesController < ApplicationController 2 def index 3 @room_id = message_room_id(current_user.id, params[:user_id]) 4 @messages = Message.recent_in_room(@room_id) 5 end 6 7 def create 8 @message = current_user.from_messages.build(message_params) 9 end 10 11 private 12 def message_room_id(first_user_id, second_user_id) 13 first_id = first_user_id.to_i 14 second_id = second_user_id.to_i 15 if first_id < second_id 16 "#{first_user_id}-#{second_user_id}" 17 else 18 "#{second_user_id}-#{first_user_id}" 19 end 20 end 21end

Messageのカラムは,to_id(送り先のユーザーid), from_id(送信したユーザーid), content(文章), room_id(ユーザーidをハイフンで結合したもの)になっています。
room_idは二人のユーザー間で同じになるように、Messageモデルのmessage_room_idで実装しています。
例えばユーザー1がユーザー2にメッセージを送信した時、そのメッセージのroom_idは1-2となります。
ユーザー2がユーザー1にメッセージを送信した時も、room_idは1-2となります。
このように、room_idを二つのユーザー間で同じにすることで、MessageControllerのindexでメッセージのやりとりを取得できます。

思考錯誤した部分 

LINEやTwitterのチャット一覧画面は、どのような動きをしているか考えました。

  • 自分が送信したか、相手が送信したかに限らず、やりとりの最後のメッセージが表示される
  • やりとりしている相手の名前、画像が表示される
  • 上記二つが複数表示される

自分が関わったメッセージを取得するには、room_idを使うことにしました。

ruby

1class MessagesController < ApplicationController 2 def dashbord 3 @my_rooms=Message.where('room_id like ? or room_id like ?', "#{current_user.id}-%", "%-#{current_user.id}") 4 end 5end

ruby

1# dashbordの結果 2=> #<ActiveRecord::Relation [#<Message id: 29, content: "こんにちは", from_id: 1, to_id: 2, room_id: "1-2", created_at: "2020-03-14 06:03:13", updated_at: "2020-03-14 06:03:13">, #<Message id: 30, content: "いい天気ですね", from_id: 2, to_id: 1, room_id: "1-2", created_at: "2020-03-14 06:03:43", updated_at: "2020-03-14 06:03:43">, #<Message id: 38, content: "こんにちは", from_id: 2, to_id: 2, room_id: "2-2", created_at: "2020-03-14 07:45:08", updated_at: "2020-03-14 07:45:08">]>

こんな感じで自分が関わったメッセージを取得できます。
ですが、ここで一つ問題があります。
ユーザー間でのやり取りが複数あった場合、メッセージも複数取得することになります。
自分が実装したいのはroom_idごとに最後のメッセージを取得する機能です。
そうだ!重複を削除すればいいんだ!と考え以下のように絞り込んでみました。

ruby

1@my_rooms=Message.where('room_id like ? or room_id like ?', "#{current_user.id}-%", "%-#{current_user.id}").order(id: DESC).group(:room_id).select(:room_id)

ですが、重複を削除した時点で、絞り込んだMessageのidがnilになってしまい、Messageを特定できずにいます。
このまま重複を削除した状態で、room_idのみを使い再度Messageを探すこともできます。

ruby

1@my_rooms.each do |room| 2 message=Message.where(room_id: room.room_id).last 3 puts message.content 4end

こんな感じでメッセージを取得することもできるのですが、whereが二回も使われてしまうのは、あまりよくないかなと考えています。
メッセージの相手が増えていった場合、読み込みに時間がかかるのは避けたいです。
また最大の問題は、userを取得する時に、

ruby

1@my_rooms.each do |room| 2 message=Message.includes(:to, :from).where(room_id: room.room_id).last 3 puts message.content 4 puts message.to.name 5end

のように書かなくてはいけないことです。
ループの中で、includesしているので、 結局ループの回数分userが読み込まれることになります。

知りたいこと

Messageを絞り込む部分で、もっとスマートに書く方法を探しています。
全く同じではないが、ほとんど似たようなSQLを発行しないようなコードを目指しています。
また、そもそもモデルの構成、root等の前提がよろしくない可能性もあるなと考えています。

そこまで複雑な機能ではないので、作り直しも考えています。
仮にRoomモデルなんかがあったら

ruby

1message=room.messages.last 2message.content

のように手軽に取得できるのに... と思っています。

他に必要なコード、ご質問等ありましたらコメントおねがします。
よろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだ回答がついていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問