Skip to content

Fix notification read and dismiss under Mongoid 9 - #909

Merged
suttondemlong merged 2 commits into
masterfrom
fix/notifications-mongoid-9-regression
Sep 9, 2026
Merged

Fix notification read and dismiss under Mongoid 9#909
suttondemlong merged 2 commits into
masterfrom
fix/notifications-mongoid-9-regression

Conversation

@suttondemlong

@suttondemlong suttondemlong commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

Fixes #908.

Marking a notification as read and dismissing one both raise on every request on master today. This is a regression from the Mongoid 8 → 9 upgrade in #892, not something waiting on the rest of the upgrade work, so it is worth taking on its own rather than behind anything else.

Two commits: the fix, then the spec that guards it.

The regression

NotificationsController#notification_params returns ActionController::Parameters, and update and destroy feed it straight into Notification.where(...). Parameters has not been a Hash since Rails 5; Mongoid 8 accepted it, Mongoid 9 requires a real Hash and raises:

Mongoid::Errors::InvalidQuery: Expression must be a Hash:
  #<ActionController::Parameters {"notificateable_id"=>"...", "notificateable_type"=>"Post", ...}>

In production ExceptionLogger's rescue_from "Exception" turns that into a 422 quoting a Mongoid internal error, which is likely why it has read as noise rather than as two broken endpoints. index is unaffected — it builds its own criteria.

The fix is to_h, which is safe because the parameters have already been through permit.

I audited app/ and lib/ for the same shape and this is the only Mongoid case. The others are Active Record or Active Model, which still accept permitted Parameters, or plain hashes built by hand — ReactionsController#reaction_params is the latter.

Two N+1s in the same controller

Not scope creep: config/environments/test.rb sets Bullet.raise = true, so these made the endpoint untestable. Any spec touching index died on UnoptimizedQueryError before reaching an assertion. Fixing them is what allowed the regression spec to exist at all.

  • Notification's after_initialize :set_defaults recomputed post_id by reaching through notificateable on every instantiation — including records read back from the database that already had the value stored. One extra query per notification loaded, anywhere in the app, to recompute something it was already holding. Now guarded on post_id.blank?, which still backfills documents written before the field existed while costing nothing for the rest.
  • The index aggregation titles each group through notificateable, so the query now eager loads it. Mongoid 9 does support includes for this polymorphic belongs_to; I checked before relying on it rather than assuming.

Verification

  • 331 examples, 0 failures on this branch (324 on master, plus the 7 new ones), standardrb clean
  • Reverting just the fix commit and re-running the new spec reproduces the bug: 5 failures, Mongoid::Errors::InvalidQuery on update and destroy, Bullet::Notification::UnoptimizedQueryError on index
  • Branched from master, so it carries none of the in-flight upgrade work

Note on overlap

These same two commits also appear on the branch for #912, which is where they were found. They are cherry-picked here so this can merge first without waiting on that much larger review. Once this lands, #912 rebases and git drops the duplicates automatically.

Please rebase-and-merge or use a real merge commit — not squash, so the fix stays separable from its spec.

🤖 Generated with Claude Code

`NotificationsController#update` and `#destroy` raise on every request:

  Mongoid::Errors::InvalidQuery: Expression must be a Hash:
    #<ActionController::Parameters {"notificateable_id"=>...}>

`notification_params` returns ActionController::Parameters, which is not a Hash, and
Mongoid 9 requires a query expression to be one. Mongoid 8 accepted it. This is a
regression from the Mongoid 8 -> 9 upgrade in #892, already on master, so marking
notifications as read and dismissing them are both broken right now. Fixed by calling
`to_h`, which is safe because the parameters have been through `permit`.

I audited the rest of app/ and lib/ for the same shape. Every other case is either
ActiveRecord or ActiveModel, which still accept permitted Parameters, or a plain Hash
built by hand -- `ReactionsController#reaction_params` is the latter. This was the only
Mongoid one.

Two N+1s went with it, both of which made the index action untestable under
`Bullet.raise`:

- `after_initialize :set_defaults` recomputed `post_id` by reaching through
  `notificateable` on *every* instantiation, including records read back from the
  database that already had the value stored. Now guarded on `post_id.blank?`, which
  still backfills documents written before the field existed while costing nothing for
  the rest.
- The index aggregation titles each group through `notificateable`, so the query now
  eager loads it. Mongoid 9 does handle `includes` for this polymorphic belongs_to;
  I checked before relying on it.

469 examples, 0 failures.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
All three actions, including the grouping that collapses repeated notifications on one
subject into a single entry with a count. This is what surfaced both the Mongoid 9
regression and the N+1s fixed in the preceding commit -- update and destroy raised on
the first request a spec ever made to them.

469 examples, 0 failures. app/ and lib/ coverage 87.85% -> 88.94%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@suttondemlong
suttondemlong force-pushed the fix/notifications-mongoid-9-regression branch from 4093e1f to b86b093 Compare September 9, 2026 01:09
@suttondemlong
suttondemlong merged commit 29a9a0a into master Sep 9, 2026
12 checks passed
@suttondemlong
suttondemlong deleted the fix/notifications-mongoid-9-regression branch September 9, 2026 18:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Notification read and dismiss raise under Mongoid 9

3 participants