🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Ruby on Rails 5

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

Ruby

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

Q&A

解決済

2回答

2587閲覧

メッセージの未読・既読機能が実装出来ない。

punchan36

総合スコア105

Ruby on Rails 5

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

Ruby

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

0グッド

0クリップ

投稿2020/12/13 05:19

編集2020/12/18 09:02

前提・実現したいこと

受信したメッセージの横に、未開封であればマークを表示するといった処理をしたいです。
メッセージ機能及び通知機能は既に実装出来ています。

未読・既読機能としては、以下の流れを考えています。

  1. 通知テーブルにreadと言うboolean型のカラムを追加する。
  2. /rooms/:idにアクセスがあった場合にreadの中身をfalseからtrueに変える処理を施し、未読かどうかを判定する。
  3. trueの場合はマークを表示、/rooms/:idにアクセスがあった瞬間にマークを消す。

ただコードの記述の仕方が分からず足踏みしています。
「ログイン中のユーザーが抱えるmessageに関する通知(全件)を取得する」と言った通知機能は記述出来たのですが、「各room内のmessageに関する通知をそれぞれ取得する」記述の仕方が分かりません…。
ご助言を頂けますと有難いです。

schema.rb

Ruby

1 create_table "notifications", force: :cascade do |t| 2 t.integer "visitor_id", null: false 3 t.integer "visited_id", null: false 4 t.integer "post_id" 5 t.string "action", default: "", null: false 6 t.boolean "checked", default: false, null: false 7 t.datetime "created_at", null: false 8 t.datetime "updated_at", null: false 9 t.integer "comment_id" 10 t.integer "message_id" 11 t.integer "room_id" 12 t.boolean "read", default: false, null: false #未読・既読判定の為にカラム追加。 13 t.index ["comment_id"], name: "index_notifications_on_comment_id" 14 t.index ["message_id"], name: "index_notifications_on_message_id" 15 t.index ["post_id"], name: "index_notifications_on_post_id" 16 t.index ["room_id"], name: "index_notifications_on_room_id" 17 t.index ["visited_id"], name: "index_notifications_on_visited_id" 18 t.index ["visitor_id"], name: "index_notifications_on_visitor_id" 19 end

messages_controller.rb

Ruby

1 def create 2 @room = @message.room 3 if @message.save! 4 @roommembernotme = Entry.where(room_id: @room.id).where.not(user_id: @current_user.id) 5 @theid = @roommembernotme.find_by(room_id: @room.id) 6 notification = @current_user.active_notifications.new( 7 room_id: @room.id, 8 message_id: @message.id, 9 visited_id: @theid.user_id, 10 visitor_id: @current_user.id, 11 action: 'message' 12 ) 13 if notification.visitor_id == notification.visited_id 14 notification.checked = true 15 notification.read = true # 自分がメッセージを送った場合は既読にする。 16 end 17 notification.save! 18 end

rooms_controller.rb

Ruby

1 def show 2 # ログイン中のユーザーが抱える「message」に関する相手からの通知を取得。 3 @notifications = @current_user.passive_notifications.where(action: 'message').includes([:visitor], [:visited]).order(created_at: :desc).page(params[:page]).per(7) 4 # ログイン中のユーザーが抱える相手からの通知のうち、該当のroom_idを取得し、showを開いた時点でreadをtrueにする。 5 @current_user.passive_notifications.find_by(params[:id]).update(read: true) 6 # 該当のroom_id内にある未読メッセージの数を取得。 7 @unread = @notifications.where(room_id: params[:id], read: false).count 8 end 9 10 def index 11 @rooms = Room.page(params[:page]).per(3) 12 @user = @current_user 13 @currentEntries = @current_user.entries 14 myRoomIds = [] 15 @currentEntries.each do | entry | 16 myRoomIds << entry.room_id 17 end 18 @anotherEntries = Entry.includes(:user, :room).where(room_id: myRoomIds).where('user_id != ?', @user.id).order(created_at: :desc) 19 20 @notifications = @current_user.passive_notifications.where(action: 'message').includes([:visitor], [:visited]).order(created_at: :desc).page(params[:page]).per(7) 21 @notifications.where(checked: false).each do |notification| 22 notification.update_attributes(checked: true) 23 end 24 @unread = @notifications.where(room_id: params[:id], read: false).count 25 end

rooms_index.html.erb

Rails

1 <div class="message-items"> 2 <% @anotherEntries.each do |e| %> 3 <div class="message-item"> 4 <div class="message-left"> 5 <%= link_to room_path(e.room.id) do %> 6 <img src="<%= "/user_images/#{e.user.image_name}" %>"> 7 <% end %> 8 </div> 9 <div class="message-center"> 10 <div class="message-center-username"> 11 <%= link_to room_path(e.room.id) do % 12 <%= e.user.name %> 13 <%= @unread %> 14 <% end %> 15 </div> 16 <div class="message-center-message"> 17 <%= link_to room_path(e.room.id) do %> 18 <% dm = Message.find_by(id: e.room.message_ids.last).try(:content) %> 19 <%= truncate(dm, length: 9) %> 20 <% end %> 21 </div> 22 </div> 23 <div class="message-right"> 24 <div class="message-date" style="color: #C0C0C0;"><%= e.updated_at.strftime("%Y/%m/%d") %></div> 25 <div class="message-time" style="color: #C0C0C0;"><%= e.updated_at.strftime("%H:%M") %></div> 26 </div> 27 </div> 28 <% end %> 29 </div>

補足情報(FW/ツールのバージョンなど)

ruby 2.6.4p104
RubyGems 3.0.3
Rails 5.2.3

【追記】ER図

イメージ説明

【追記】解決したコード

rooms_controller.rb

Ruby

1 def index 2 #以下、メッセージ機能に関する記述。変更なし。 3 @rooms = Room.page(params[:page]).per(3) 4 @user = @current_user 5 @currentEntries = @current_user.entries 6 myRoomIds = [] 7 @currentEntries.each do | entry | 8 myRoomIds << entry.room_id 9 end 10 @anotherEntries = Entry.includes(:user, :room).where(room_id: myRoomIds).where('user_id != ?', @user.id).order(created_at: :desc) 11 12 # 既読機能についてはコントローラでは特に記述せず。ビューにて記述。 13 end 14 15 def show 16 #以下、メッセージ機能に関する記述。変更なし。 17 @rooms = Room.page(params[:page]).per(3) 18 @user = @current_user 19 @currentEntries = @current_user.entries 20 myRoomIds = [] 21 @currentEntries.each do | entry | 22 myRoomIds << entry.room_id 23 end 24 @anotherEntries = Entry.includes(:user, :room).where(room_id: myRoomIds).where('user_id != ?', @user.id).order(created_at: :desc) 25 26 # ここから既読機能。ログイン中のユーザーが抱える「message」に関する「相手からの通知」のうち、該当のroom_idを取得し、showを開いた時点でreadをtrueにする。 27 @current_user.passive_notifications.where(action: 'message', read: false, room_id: params[:id]).each do |notification| 28 notification.update_attributes(read: true) 29 end 30 end

rooms_index.html.erb と rooms_show.html.erb(既読機能に関する内容は同じ)

Rails

1 <!-- ログイン中のユーザーが保持している各Entryの中で、未読メッセージがある場合countを繰り返し表示。--> 2 <% message_count = Notification.where(action: "message", visited_id: @current_user.id, visitor_id: e.user.id, room_id: e.room.id, read: false).count %> 3 <% if message_count > 0 %> 4 <div class="message-count date-and-count"><%= message_count %></div> 5 <% end %>

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

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

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

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

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

guest

回答2

0

ベストアンサー

この質問は「追記・修正依頼」で解決しました。

他に書くことが思い当たらないため、rooms_controller.rbをリファクタリングしたものを載せておきます。
動かなかったらすみません。

ruby

1before_action :set_room, only: [:index, :show] 2before_action :set_another_entries, only: [:index, :show] 3 4def index 5end 6 7def show 8 @current_user 9 .passive_notifications 10 .where(action: 'message', read: false, room_id: params[:id]) 11 .update_all(read: true) 12end 13 14private 15 16def set_room 17 @rooms = Room.page(params[:page]).per(3) 18end 19 20def set_another_entries 21 my_room_ids = @current_user.entries.pluck(:room_id) 22 @another_entries = Entry.includes(:user, :room).where(room_id: my_room_ids).where('user_id != ?', @current_user.id).order(created_at: :desc) 23end 24

投稿2020/12/19 12:41

neko_daisuki

総合スコア2090

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

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

punchan36

2020/12/20 03:41

有難うございます!! こちらの記述でも、room作成・message送受信・通知機能と全て問題なく作動致しました(質問文には記載していなかった既存の変数はいくつか残す必要あり)。 配列のくだりはpluckと言うメソッドでこんなにも簡略化出来るのですね…まさに目から鱗が落ちる思いでした。大変勉強になりました。 親身にご助言も頂きまして有難うございました!
guest

0

visitor なのか visited なのかどちらかわかりませんが、それとroom で絞ってください
ああ、visitorの方ですね。そちらは絞れていますから
@notifications.find_by(room_id: params[:id]).update(read: true)
show すべき room が params[:id] で来ている前提です

投稿2020/12/13 06:53

winterboum

総合スコア23567

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

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

punchan36

2020/12/13 10:40

有難うございます! ご助言頂いた記述で、下記の2番は恐らく出来るようになったかと思います。 1. 各ルーム内における未読メッセージ(read: false)の数を各ルームにそれぞれ表示する。 2. 相手から届いたmessageに関する通知のうち、該当のroom_idを取得し、showを開いた時点でreadをtrueにする。 1番なのですが、rooms_controller.rb内に以下の記述をしました。 @unread = @notifications.find_by(room_id: params[:id]).where(read: false).count (該当のroom_id内にある未読メッセージの数を取得。) しかし以下のエラーが出てしまい前に進めません。 NoMethodError in RoomsController#index undefined method `where' for nil:NilClass コントローラの書き方など間違っておりますでしょうか…? (質問文は今現在のコードに書き直しました。)
winterboum

2020/12/13 11:53

やるなら @notifications.where(room_id: params[:id], read: false).count,read: false).count ですが、 @notifications.find_by(room_id: params[:id]) がnilってことは、2番もできていないですね。 RoomsController#index を呼んだ時のパラメータを確認してください。 room.idがどういう形で来ているのか、そもそも来ているのか
punchan36

2020/12/13 14:42

有難うございます。 @unread = @notifications.where(room_id: params[:id], read: false).count に記述し直してからログを追ってみました。 詳細は追記致しましたが、rooms_index.html.erbは表示されるもののrooms_show.html.erbを開くとエラーが出ます。 このような状態になってからの見極めがいつも苦手(難しい)なのですが、どのように見て行けば良いのでしょうか…。
winterboum

2020/12/13 21:59

@notifications.find_by(room_id: params[:id]).update これがそういうエラーを出しているとすると、そういうnotificationができていないということです。notificationを作る所を疑ってください。 なお、 エラーメッセージの載せ方がはんば。どの行か、もでてません?
neko_daisuki

2020/12/14 20:35

@current_user.passive_notifications.where(action: 'message').includes([:visitor], [:visited]).order(created_at: :desc).page(params[:page]).per(7) として絞り込んだものを .find_by(room_id: params[:id]) してるので見つからないんだと思います。 @current_user.passive_notifications の中には params[:id] のがあるはずなので これを find_by して update ですね。
winterboum

2020/12/14 23:13

です あ、その idが何のIDかを確認してくださいね
punchan36

2020/12/15 10:12

返答が遅くなり申し訳ありません。 また、ご助言頂き有難うございます。 先ほど質問文の内容を編集してしまったのですが、その後に自分のコードの間違いに気づきました。 romms_controller.rb内の記述を以下の様に変更したりと試行錯誤しております。 @notifications.find_by(room_id: params[:id]).update(read: true) ↓ @current_user.passive_notifications.find_by(params[:id]).update(read: true) まだ上手く機能はしておりませんが、エラーは出なくなりました。また行き詰りましたらコメント致します。
punchan36

2020/12/15 11:06

何度も申し訳ありません。 先ほど申しましたようにrooms_controller.rb内の記述を変更したところ、rooms_index.html.erb, rooms_show.html.erbのどちらを表示してもエラー文は出なくなりました。 しかしread: falseのメッセージがあるにも関わらず、countが0と表示されます。定義した「@unread」が上手く機能していないようです。 rooms_index.html.erbにあります「@unread」周りの記述も追記致しましたが、中身が上手く渡っていないのでしょうか…。
neko_daisuki

2020/12/15 11:58

find_by の使い方が間違ってます。 #show の @unread はper(7)となってるので最大で7にしかなりません。 (どうして 0 になるのかは分かりません) #index の @unread は @notifications が見つかりません。 @unread = @notifications.where(room_id: params[:id], read: false) は複数あるはずなのですか? だとすると、update(read: true) したい notification ってどう決まるのでしょうか。 なんか混乱してきました。
punchan36

2020/12/15 12:46

引き続き有難うございます! 申し訳ありません。 #index 内にも #show 同様 @notifications は記述しておりましたが、質問文に書き忘れていました(追記致しました)。@notificationsの記述があった上で、countが0になりました。 また、そうですね。@notifications の定義には per(7) の記述も入ってましたので、@read の定義は少し長くなりますが以下の様に書き換えようと思います。 @unread = @notifications.where(room_id: params[:id], read: false).count ↓ @unread = @current_user.passive_notifications.where(action: 'message', room_id: params[:id], read: false).includes([:visitor], [:visited]).count 更に@unreadの定義の意味ですが、各room_id内にある未読メッセージの数をそれぞれ取得したいです。ですので、room_idは各ルームにつきもちろん一つですが、未読メッセージの数は2つ以上ある可能性もありますのでwhereを使い記述致しました。
neko_daisuki

2020/12/15 21:56

#index @unread = @notifications.where(room_id: params[:id], read: false).count としてますが、#index には params[:id] がないのでは。 #show 未読メッセージの数が2つ以上ある可能性があるのなら、 2つあったとして、どちらを既読にするのでしょうか。 どちらも既読にするのなら @unread は 0 になりますよね。
punchan36

2020/12/16 01:56

有難うございます。 なるほどです…。言っていただいて初めて気が付きました。 #index には params[:d] が存在しない為、各ルームの未読メッセージの count も 0 になる。 #show では、rooms/id を開いた時点で params[:d] 内にある全ての未読メッセージを read:trueにしたいです。 しかし rooms_show.html.erb も rooms_index.html.erb と同じように記述してしまっているため、各ルームの部分に「今開いている rooms/id で受け取った未読メッセージの count」が表示されてしまっています(今開いている rooms/id の count が 0 になる → 他のルームの count 表示 も 0 になる)。 私がこれまで実装した物の中では一番複雑かもしれず、これ以上どうすれば良いのか見当がつかないのですが、根本的にコードを見直さないと実装は難しそうでしょうか…。
neko_daisuki

2020/12/16 09:58

#index と #show でどの通知を表示してどんな時に何を既読にするのか仕様がよく分かりません。 仕様がはっきりすればそんなに難しいコードは必要ないと思いますよ。
punchan36

2020/12/16 12:01

その辺りを上手く説明出来ておらず、申し訳ありませんでした。 仕様と致しましてはLINEやFaceBookのMessengerと同様です。 #index: ログイン中のユーザーが抱えている各 room を画面左側に一覧で表示する(ユーザー名、ユーザーのプロフィール画像、最新メッセージの冒頭数文字、最新メッセージの送信時間など)。 ここまでは実装出来ており、ここから更に、それぞれの room に未読メッセージが何件あるか、各ユーザー名の横にその数字を表示したい(Aさん=2, Bさん=3, Cさん=0など)。 #show: indexで表示していた画面左側部分(room一覧)は引き続き表示したままにし、クリックされたユーザーとのメッセージのやり取りの詳細を画面右側に表示する。 ここまでは実装出来ており、ここから更に、rooms/id がクリックされた瞬間に、 該当の rooms/id にあった未読メッセージを「全て」 read:true にしたい。 またその処理により、画面左側に表示していた、該当 room における未読メッセージの数も0(表示なし)にしたい。 他の room に未読メッセージがある場合は、該当 rooms/id をクリックしない限り、もちろんその count が表示され続けるようにしたい(#index の説明の最後に書いた例で、仮に今回Aさんとの room をクリックした場合、表示される count は「Aさん=0, Bさん=3, Cさん=0」とAさんの count のみ0に変更したい)。 このような仕様となっております。 中々解決にならず申し訳ありませんが、出来ましたら引き続きご助言頂けますと有難いです。
neko_daisuki

2020/12/16 23:51

index.html.erb の #show へのリンクを <%= link_to room_path(e.room.id, visitor_id: e.user.id) do%> として、#show で params[:visitor_id] を受け取れば良いのではないでしょうか。 #show では current_user と params[:id](roomのid) と params[:visitor_id] で絞り込んだ notification を read: true にする。 #index はモデルの関連付けがどうなっているのか分からないので なんとも言えませんが、できないことはなさそうです。
punchan36

2020/12/17 10:32

有難うございます。 まだ上手く機能しませんが、根本的に色々と見落としている気がしてきました。 #show @unread = @notifications.where(room_id: params[:id], read: false).count こちらの定義も、今表示されている params[:id] の情報を取得している訳ですので、そこでの未読メッセージの count が仮に3であれば、他のルームの未読メッセージ数も全て3と表示されてしまいます。 ですので、rooms_show を表示していなくても、各ルームの未読メッセージの数を取得+表示出来るような変数を定義出来れば良いのかなと思いました。 つまりは、#index で count を正しく表示出来れば、#show でも表示出来るのかなと思います。 ただ #index を表示している段階では room_id: params[:id] を取ってくる事も出来ません。各ルームの情報は以下の変数を繰り返し処理して表示させている訳ですが、ここに他テーブルの情報である Notification の count の情報を組み込む方法も複雑でよく分かりません…。 #index @anotherEntries = Entry.includes(:user, :room).where(room_id: myRoomIds).where('user_id != ?', @user.id).order(created_at: :desc) 最初に質問させて頂いた時から内容が少し変わってきておりますが、ER図等も含め、別で質問を設けた方が望ましいでしょうか…。 貴重なお時間を割かせてしまい申し訳ありません。
neko_daisuki

2020/12/17 11:36 編集

僕は構いませんが、winterboumさんに通知でご迷惑をおかけしていないかは気になります。 Entryというのは user と room の中間テーブルでしょうか? @anotherEntries は自分が参加してるroomで、 かつ自分以外が参加しているroomを取得しているんですよね。 N+1 とかは無視して取り合えずこんな感じでダメなんでしょうか @anotherEntries.each do |entry| entry.user.name # 未読のカウント Notification.where(actoin: "message", visited: current_user, visitor: entry.user, room: entry.room, read: false).count end または、rails c で実行してみて欲しいのですが、 Notification.where(visited_id: 1, read: false).group(:visitor_id).count とすると、{ (visitor_id => (count), (visitor_id => (count), ...} というのが得られます。 これを @unread として、ビューで @unread[e.user.id] としても未読を表示できるんじゃないかと思います。
punchan36

2020/12/18 09:03

有難うございます!無事理想通り動くようになりました。 最終的に動くようになったコードは追記致しました。 ただ一点だけ理解に至らない点がありまして、 rooms/controller.rbの#showにあります以下の行ですが、room_id: params[:id]は1つのみを取得したいのに、where(複数を取得するメソッド)できちんと動く事が少し不思議でした。 @current_user.passive_notifications.where(action: 'message', read: false, room_id:params[:id]).each do |notification| notification.update_attributes(read: true) end また「Entry」や「@anotherEntries」の定義の意味は仰る通りでございます。全体のER図も載せておきました。 解決致しましたので質問を閉じようと思うのですが、こちらのご回答をそのままベストアンサーにしても宜しいのでしょうか…?
neko_daisuki

2020/12/18 10:45

おめでとうございます。 ループの中でユーザーの数だけSQLが実行されてしまう点はご留意ください。 条件に当てはまるレコードが本当にひとつなら下のでも良いです。 @current_user.passive_notifications.find_by(action: 'message', read: false, room_id: params[:id]).update_attributes(read: true) ベストアンサーはお任せします。
winterboum

2020/12/18 10:54

nekoさん回答まとめてくださいよ。そちらにベストを
punchan36

2020/12/19 10:46

なるほどです。お二方とも有難うございました! winterboumさま、親身にご助言頂いたにも関わらずなかなか解決まで辿り着けず申し訳ありませんでした。 neko_daisukiさまからご回答があるようであれば、そちらをベストアンサーに選ばさせて頂ければと思います。
neko_daisuki

2020/12/19 12:37

了解しました。 お二方、お気遣いいただきありがとうございます。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.36%

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

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

質問する

関連した質問