fix: pass serialized data in notification.deleted event to avoid Deserialisation (#13061)

https://one.newrelic.com/alerts/issue?account=3437125&duration=259200000&state=d088e9b7-d0ce-3fcf-fda5-145df8b9cb2a


## Description
Pass serialized data instead of ActiveRecord object in
dispatch_destroy_event to prevent ActiveJob::DeserializationError when
the notification is already deleted.

This error occurs frequently because RemoveDuplicateNotificationJob
deletes notifications, and by the time the async EventDispatcherJob
runs, the record no longer exists.

## Type of change

- [ ] Bug fix (non-breaking change which fixes an issue)

## How Has This Been Tested?



## Checklist:

- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Avoids ActiveJob deserialization failures by sending serialized data
for notification deletion and updating the listener accordingly.
> 
> - `Notification#dispatch_destroy_event` now dispatches
`NOTIFICATION_DELETED` with serialized `notification_data` (`id`,
`user_id`, `account_id`) instead of the AR object
> - `ActionCableListener#notification_deleted` reads
`notification_data`, finds `User`/`Account`, computes
`unread_count`/`count` via `NotificationFinder`, and broadcasts using
the user’s pubsub token
> - Specs updated to pass `notification_data` and assert payload
(including `unread_count`/`count`)
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
e2ffbe765b148fdfd2cd2e031c657c36e423c1f5. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com>
This commit is contained in:
Tanmay Deep Sharma
2026-01-12 13:15:40 +05:30
committed by GitHub
parent a2e348df06
commit d526cf283d
3 changed files with 31 additions and 6 deletions

View File

@@ -14,11 +14,19 @@ class ActionCableListener < BaseListener
end end
def notification_deleted(event) def notification_deleted(event)
return if event.data[:notification].user.blank? notification_data = event.data[:notification_data]
notification, account, unread_count, count = extract_notification_and_account(event) user = User.find_by(id: notification_data[:user_id])
tokens = [event.data[:notification].user.pubsub_token] account = Account.find_by(id: notification_data[:account_id])
broadcast(account, tokens, NOTIFICATION_DELETED, { notification: { id: notification.id }, unread_count: unread_count, count: count }) return if user.blank? || account.blank?
notification_finder = NotificationFinder.new(user, account)
tokens = [user.pubsub_token]
broadcast(account, tokens, NOTIFICATION_DELETED, {
notification: { id: notification_data[:id] },
unread_count: notification_finder.unread_count,
count: notification_finder.count
})
end end
def account_cache_invalidated(event) def account_cache_invalidated(event)

View File

@@ -180,7 +180,17 @@ class Notification < ApplicationRecord
end end
def dispatch_destroy_event def dispatch_destroy_event
Rails.configuration.dispatcher.dispatch(NOTIFICATION_DELETED, Time.zone.now, notification: self) # Pass serialized data instead of ActiveRecord object to avoid DeserializationError
# when the async EventDispatcherJob runs after the notification has been deleted
Rails.configuration.dispatcher.dispatch(
NOTIFICATION_DELETED,
Time.zone.now,
notification_data: {
id: id,
user_id: user_id,
account_id: account_id
}
)
end end
def set_last_activity_at def set_last_activity_at

View File

@@ -132,7 +132,14 @@ describe ActionCableListener do
describe '#notification_deleted' do describe '#notification_deleted' do
let(:event_name) { :'notification.deleted' } let(:event_name) { :'notification.deleted' }
let!(:notification) { create(:notification, account: account, user: agent) } let!(:notification) { create(:notification, account: account, user: agent) }
let!(:event) { Events::Base.new(event_name, Time.zone.now, notification: notification) } let(:notification_data) do
{
id: notification.id,
user_id: agent.id,
account_id: account.id
}
end
let!(:event) { Events::Base.new(event_name, Time.zone.now, notification_data: notification_data) }
it 'sends message to account admins, inbox agents' do it 'sends message to account admins, inbox agents' do
expect(ActionCableBroadcastJob).to receive(:perform_later).with( expect(ActionCableBroadcastJob).to receive(:perform_later).with(