Skip to content

Commit 4c0db86

Browse files
committed
Refactor notification handling in controllers and add NotificationReadable concern
1 parent b146f74 commit 4c0db86

File tree

6 files changed

+151
-42
lines changed

6 files changed

+151
-42
lines changed

app/controllers/better_together/conversations_controller.rb

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
module BetterTogether
44
# Handles managing conversations
55
class ConversationsController < ApplicationController # rubocop:todo Metrics/ClassLength
6+
include BetterTogether::NotificationReadable
7+
68
before_action :authenticate_user!
79
before_action :disallow_robots
810
before_action :set_conversations, only: %i[index new show]
@@ -103,11 +105,8 @@ def show # rubocop:todo Metrics/MethodLength, Metrics/AbcSize
103105
@message = @conversation.messages.build
104106

105107
if @messages.any?
106-
# Move this to separate action/bg process only activated when the messages are actually read.
107-
events = BetterTogether::NewMessageNotifier.where(record_id: @messages.pluck(:id)).select(:id)
108-
109-
notifications = helpers.current_person.notifications.unread.where(event_id: events.pluck(:id))
110-
notifications.update_all(read_at: Time.current)
108+
mark_notifications_read_for_event_records(BetterTogether::NewMessageNotifier, @messages.pluck(:id),
109+
recipient: helpers.current_person)
111110
end
112111

113112
respond_to do |format|

app/controllers/better_together/joatu/joatu_controller.rb

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ module BetterTogether
44
module Joatu
55
# Base controller for Joatu resources, adds notification mark-as-read helpers
66
class JoatuController < BetterTogether::FriendlyResourceController
7+
include BetterTogether::NotificationReadable
8+
79
# Normalize translated params so base keys are populated for current locale.
810
# This helps presence validations (esp. for ActionText) during create/update
911
# when forms submit locale-suffixed fields like `description_en`.
@@ -23,38 +25,6 @@ def resource_params # rubocop:todo Metrics/CyclomaticComplexity, Metrics/MethodL
2325

2426
rp
2527
end
26-
27-
protected
28-
29-
# Mark Noticed notifications as read for a specific record-based event
30-
def mark_notifications_read_for_record(record)
31-
return unless helpers.current_person && record.respond_to?(:id)
32-
33-
helpers.current_person.notifications.unread
34-
.includes(:event)
35-
.references(:event)
36-
.where(event: { record_id: record.id })
37-
.update_all(read_at: Time.current)
38-
end
39-
40-
# Mark Joatu match notifications as read when viewing an offer or request
41-
def mark_match_notifications_read_for(record) # rubocop:todo Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity
42-
return unless helpers.current_person && record.respond_to?(:id)
43-
44-
helpers.current_person.notifications.unread.includes(:event).find_each do |notification|
45-
event = notification.event
46-
next unless event.is_a?(BetterTogether::Joatu::MatchNotifier)
47-
48-
begin
49-
ids = []
50-
ids << event.offer&.id if event.respond_to?(:offer)
51-
ids << event.request&.id if event.respond_to?(:request)
52-
notification.update(read_at: Time.current) if ids.compact.include?(record.id)
53-
rescue StandardError
54-
next
55-
end
56-
end
57-
end
5828
end
5929
end
6030
end

app/controllers/better_together/notifications_controller.rb

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
module BetterTogether
44
# handles rendering and marking notifications as read
55
class NotificationsController < ApplicationController
6+
include BetterTogether::NotificationReadable
7+
68
before_action :authenticate_user!
79
before_action :disallow_robots
810

@@ -43,10 +45,7 @@ def mark_notification_as_read(id)
4345
end
4446

4547
def mark_record_notification_as_read(id)
46-
@notifications = helpers.current_person.notifications.unread.includes(
47-
:event
48-
).references(:event).where(event: { record_id: id })
49-
@notifications.update_all(read_at: Time.current)
48+
mark_notifications_read_for_record(Struct.new(id: id), recipient: helpers.current_person)
5049
end
5150
end
5251
end
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
# frozen_string_literal: true
2+
3+
module BetterTogether
4+
# Controller concern to mark Noticed notifications as read for a record
5+
module NotificationReadable
6+
extend ActiveSupport::Concern
7+
8+
# Marks notifications (for the current person) as read for events bound to a record
9+
# via Noticed::Event#record_id (generic helper used across features).
10+
def mark_notifications_read_for_record(record, recipient: helpers.current_person) # rubocop:todo Metrics/AbcSize
11+
return unless recipient && record.respond_to?(:id)
12+
13+
nn = Noticed::Notification.arel_table
14+
ne = Noticed::Event.arel_table
15+
16+
join = nn.join(ne).on(ne[:id].eq(nn[:event_id])).join_sources
17+
18+
relation = Noticed::Notification
19+
.where(recipient:)
20+
.where(nn[:read_at].eq(nil))
21+
.joins(join)
22+
.where(ne[:record_id].eq(record.id))
23+
24+
relation.update_all(read_at: Time.current)
25+
end
26+
27+
# Marks notifications as read for a set of records associated to a given Noticed event class
28+
# using the event's record_id field.
29+
def mark_notifications_read_for_event_records(event_class, record_ids, recipient: helpers.current_person) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
30+
return unless recipient && record_ids.present?
31+
32+
nn = Noticed::Notification.arel_table
33+
ne = Noticed::Event.arel_table
34+
35+
join = nn.join(ne).on(ne[:id].eq(nn[:event_id])).join_sources
36+
37+
relation = Noticed::Notification
38+
.where(recipient:)
39+
.where(nn[:read_at].eq(nil))
40+
.joins(join)
41+
.where(ne[:type].eq(event_class.to_s))
42+
.where(ne[:record_id].in(Array(record_ids)))
43+
44+
relation.update_all(read_at: Time.current)
45+
end
46+
47+
# Marks Joatu match notifications as read for an Offer or Request record by matching
48+
# the record's GlobalID against the Noticed::Event params (supports both direct string
49+
# serialization and ActiveJob-style nested _aj_globalid key).
50+
def mark_match_notifications_read_for(record, recipient: helpers.current_person) # rubocop:todo Metrics/AbcSize, Metrics/MethodLength
51+
return unless recipient && record.respond_to?(:to_global_id)
52+
53+
gid = record.to_global_id.to_s
54+
return if gid.blank?
55+
56+
nn = Noticed::Notification.arel_table
57+
ne = Noticed::Event.arel_table
58+
59+
join = nn.join(ne).on(ne[:id].eq(nn[:event_id])).join_sources
60+
61+
relation = Noticed::Notification
62+
.where(recipient:)
63+
.where(nn[:read_at].eq(nil))
64+
.joins(join)
65+
.where(ne[:type].eq('BetterTogether::Joatu::MatchNotifier'))
66+
67+
# JSONB params filter (offer/request match on direct string or AJ global id)
68+
json_filter_sql = <<~SQL.squish
69+
(noticed_events.params ->> 'offer' = :gid OR
70+
noticed_events.params -> 'offer' ->> '_aj_globalid' = :gid OR
71+
noticed_events.params ->> 'request' = :gid OR
72+
noticed_events.params -> 'request' ->> '_aj_globalid' = :gid)
73+
SQL
74+
relation = relation.where(
75+
ActiveRecord::Base.send(
76+
:sanitize_sql_array, [json_filter_sql, { gid: }]
77+
)
78+
)
79+
80+
relation.update_all(read_at: Time.current)
81+
end
82+
end
83+
end
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# frozen_string_literal: true
2+
3+
require 'rails_helper'
4+
5+
RSpec.describe BetterTogether::NotificationReadable do # rubocop:todo RSpec/SpecFilePathFormat
6+
let(:recipient) { create(:better_together_person) }
7+
8+
let(:concern_host) do
9+
Class.new do
10+
include BetterTogether::NotificationReadable
11+
end.new
12+
end
13+
14+
describe '#mark_match_notifications_read_for' do
15+
# rubocop:todo RSpec/MultipleExpectations
16+
it 'marks unread match notifications for the given record as read' do # rubocop:todo RSpec/ExampleLength, RSpec/MultipleExpectations
17+
# rubocop:enable RSpec/MultipleExpectations
18+
offer = create(:better_together_joatu_offer)
19+
request = create(:better_together_joatu_request)
20+
21+
BetterTogether::Joatu::MatchNotifier.with(offer:, request:).deliver(recipient)
22+
23+
unread_before = recipient.notifications.unread.joins(:event)
24+
.where(noticed_events: { type: 'BetterTogether::Joatu::MatchNotifier' }).count
25+
expect(unread_before).to be >= 1
26+
27+
concern_host.mark_match_notifications_read_for(offer, recipient:)
28+
29+
unread_after = recipient.notifications.unread.joins(:event)
30+
.where(noticed_events: { type: 'BetterTogether::Joatu::MatchNotifier' }).count
31+
expect(unread_after).to eq(0)
32+
end
33+
end
34+
35+
describe '#mark_notifications_read_for_record' do
36+
# rubocop:todo RSpec/MultipleExpectations
37+
it 'marks unread notifications tied to the event record as read' do # rubocop:todo RSpec/ExampleLength, RSpec/MultipleExpectations
38+
# rubocop:enable RSpec/MultipleExpectations
39+
conversation = create(:better_together_conversation)
40+
create(:better_together_user, person: recipient)
41+
# Ensure recipient is a participant in the conversation to be notified
42+
conversation.participants << recipient unless conversation.participants.include?(recipient)
43+
44+
message = conversation.messages.create!(sender: recipient, content: 'Hi there')
45+
BetterTogether::NewMessageNotifier.with(record: message, conversation_id: conversation.id).deliver(recipient)
46+
47+
unread_before = recipient.notifications.unread.joins(:event)
48+
.where(noticed_events: { type: 'BetterTogether::NewMessageNotifier' }).count
49+
expect(unread_before).to be >= 1
50+
51+
concern_host.mark_notifications_read_for_record(message, recipient:)
52+
53+
unread_after = recipient.notifications.unread.joins(:event)
54+
.where(noticed_events: { type: 'BetterTogether::NewMessageNotifier' }).count
55+
expect(unread_after).to eq(0)
56+
end
57+
end
58+
end

spec/factories/better_together/platforms.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
id { SecureRandom.uuid }
1010
name { Faker::Company.name }
1111
description { Faker::Lorem.paragraph }
12-
identifier { name.parameterize }
12+
identifier { Faker::Internet.unique.username(specifier: 10..20) }
1313
url { Faker::Internet.url }
1414
host { false }
1515
time_zone { Faker::Address.time_zone }

0 commit comments

Comments
 (0)