Skip to content

Scan /spec/notifications for N+1s#6994

Merged
compwron merged 5 commits into
rubyforgood:mainfrom
hexdevs:prosopite-notifications
Jun 12, 2026
Merged

Scan /spec/notifications for N+1s#6994
compwron merged 5 commits into
rubyforgood:mainfrom
hexdevs:prosopite-notifications

Conversation

@stefannibrasil

@stefannibrasil stefannibrasil commented Jun 9, 2026

Copy link
Copy Markdown
Contributor

What github issue is this PR for, if any?

Closes #6912

What changed, and why?

The error was raised because the query was being made every time it was called twice. By making the query only once, the error is fixed.

I also added more coverage to the notifier, memoized the reimbursement_amount to make sure it's calculated only once and moved some variables to be private methods to keep this class more object-oriented.

I also updated a request spec that uses this notifier to use instance_double instead of double.
instance_double raises an error if methods are not available in the specified class or arguments have changed, whether a double would still pass if the method is removed.

Prosopite was raising this error:

```
Prosopite::NPlusOneQueriesError:
    N+1 queries detected (1.2ms):
      SELECT "casa_orgs".* FROM "casa_orgs" WHERE "casa_orgs"."id" = $1 ORDER BY "casa_orgs"."id" ASC LIMIT $2
      SELECT "casa_orgs".* FROM "casa_orgs" WHERE "casa_orgs"."id" = $1 ORDER BY "casa_orgs"."id" ASC LIMIT $2
    Call stack:
      app/models/case_contact.rb:221:in 'CaseContact#reimbursement_amount'
      app/notifications/reimbursement_complete_notifier.rb:22:in 'ReimbursementCompleteNotifier#message'

    N+1 queries detected (0.6ms):
      SELECT "mileage_rates"."id", "mileage_rates"."amount", "mileage_rates"."effective_date", "mileage_rates"."is_active", "mileage_rates"."created_at", "mileage_rates"."updated_at", "mileage_rates"."casa_org_id" FROM "mileage_rates" WHERE "mileage_rates"."casa_org_id" = $1 AND "mileage_rates"."is_active" = $2 AND "mileage_rates"."effective_date" <= $3 ORDER BY "mileage_rates"."effective_date" DESC LIMIT $4
      SELECT "mileage_rates"."id", "mileage_rates"."amount", "mileage_rates"."effective_date", "mileage_rates"."is_active", "mileage_rates"."created_at", "mileage_rates"."updated_at", "mileage_rates"."casa_org_id" FROM "mileage_rates" WHERE "mileage_rates"."casa_org_id" = $1 AND "mileage_rates"."is_active" = $2 AND "mileage_rates"."effective_date" <= $3 ORDER BY "mileage_rates"."effective_date" DESC LIMIT $4
```

The error was raised because the query was being
made every time it was called twice. By making
the query only once, the error is fixed.

I also added more coverage to the notifier, memoized
the reimbursement_amount to make sure it's calculated
only once and moved some variables to be private
methods to keep this class more object-oriented.
instance_double raises an error if methods are
not available in the specified class or arguments
have changed, whether a double
would still pass if the method is removed.
@github-actions github-actions Bot added 🧪 Tests Tests ruby Touches Ruby code labels Jun 9, 2026
def reimbursement_amount
return @reimbursement_amount if defined?(@reimbursement_amount)

@reimbursement_amount = case_contact.reimbursement_amount

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How could case_contact have a method reimbursement_amount on it if we got it out of params? It is a string or json, not an object... so this would always come back as an error?

@stefannibrasil stefannibrasil Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! case_contact is an object that can be fetched with params[:case_contact] because of the noticed gem. We send an object in https://github.com/rubyforgood/casa/blob/main/app/controllers/reimbursements_controller.rb#L37 I just moved this around; before we were calling case_contact.reimbursement_amount as well. The only difference was that we were initializing case_contact = params[:case_contact] in the message method, and in here I moved it to memoized instance.

I also tested this locally and the notifications get sent:
Screenshot 2026-06-11 at 2 27 20 PM

This notification class also has coverage, which I expanded, so if the param was wrong, the tests would have caught them.

Comment thread spec/requests/reimbursements_spec.rb Outdated
let(:casa_org) { admin.casa_org }
let(:case_contact) { create(:case_contact) }
let(:notification_double) { double("ReimbursementCompleteNotifier") }
let(:notification_double) { instance_double("ReimbursementCompleteNotifier") }

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I appreciate the improved double but I also want to avoid doubles and mocks whereever possible

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the nudge. I didn't want to change this too much at first but I updated the test to verify that the notifications get created in 343686c

Instead of using doubles, let's check the notifications
get sent.
@compwron compwron merged commit 13ed9e0 into rubyforgood:main Jun 12, 2026
12 checks passed
@compwron

Copy link
Copy Markdown
Collaborator

:)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ruby Touches Ruby code 🧪 Tests Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Expand Prosopite N+1 enforcement: 12 newly-surfaced N+1s across 6 directories

2 participants