fix: deduplicate members in chapter_students and chapter_coaches#2551
Merged
mroderick merged 1 commit intocodebar:masterfrom Apr 9, 2026
Merged
Conversation
Members subscribed to multiple groups within a chapter were being processed multiple times when sending invitations with audience='everyone', causing 'Member has already been taken' validation errors. Root Cause: - Member.in_group() uses JOIN on subscriptions, returning duplicate rows when a member is subscribed to the same group type multiple times - 336 members in London chapter affected (subscribed to both Students and Coaches) - 35 members in Berlin chapter affected - 57 members in Brighton chapter affected Impact: - Workshop invitation batches failed mid-process (e.g., 517 of 7000+ invitations sent before failure) - InvitationLogEntry validates uniqueness of member_id per invitation, causing validation errors on second processing attempt - Admins had to manually retry batch sends multiple times Affected Chapters: - 46 of 54 chapters (85%) have members with duplicate subscriptions - ~1,200 members affected across all chapters - Top 5: London (336), West London (79), Brighton (57), South London (57), Barcelona (55), Edinburgh (37), Berlin (35) Timeline: - Original code (Jan 2017, commit 448acc7) included .uniq for deduplication - Refactoring (May 2018, commit d7a463d) extracted to scopes but accidentally removed the .uniq call Fix: Add .distinct to chapter_students and chapter_coaches methods, restoring the deduplication behavior that existed before the refactoring. Tests: - Add spec for unique members when subscribed to same group multiple times - Add spec for unique invitations per role when member is in both groups
f90828d to
75ecc09
Compare
olleolleolle
approved these changes
Apr 9, 2026
Collaborator
olleolleolle
left a comment
There was a problem hiding this comment.
Good to have that!
| @@ -0,0 +1,69 @@ | |||
| require 'rails_helper' | |||
|
|
|||
| RSpec.describe InvitationManager do | |||
Collaborator
There was a problem hiding this comment.
Ergonomics: I assume it'd be possible to have a describe "handling deduplication" block, wrapping these test cases, and placing them in a spec/models/invitation_manager_spec.rb, so that "flipping between test and implementation" works in an expected way.
olleolleolle
reviewed
Apr 9, 2026
| require 'rails_helper' | ||
|
|
||
| RSpec.describe InvitationManager do | ||
| subject(:manager) { InvitationManager.new } |
Collaborator
There was a problem hiding this comment.
Suggested change
| subject(:manager) { InvitationManager.new } | |
| subject(:manager) { described_class.new } |
Very minor in this test file, but in other cases, when it's repeated a lot in a test, it makes renaming classes more cumbersome.
Collaborator
Author
|
Thanks @olleolleolle! I've created #2554 to address the comments. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a bug where members subscribed to multiple groups within a chapter were processed multiple times when sending workshop invitations with
audience='everyone', causing batch failures and preventing remaining members from receiving invitations.Root Cause
The
Member.in_group()scope uses a SQL JOIN on thesubscriptionstable. When a member is subscribed to multiple groups of the same type (e.g., both "Students" and "Coaches" groups), the JOIN produces duplicate rows - one for each subscription.This caused members to appear multiple times in the iteration, triggering a validation error in
InvitationLogEntrywhich validates uniqueness ofmember_idscoped to(invitation_type, invitation_id).Timeline
448acc7c): Original code included.uniqfor deduplicationd7a463da): Refactoring extractedin_groupscope but accidentally removed.uniqImpact
Batch Job Behavior
When duplicate members were processed:
log_success()fails - InvitationLogEntry validation errorlog_failure()ALSO fails - same validation error (same member/invitation)Critical Consequences
All Invitation Batches from Production
Key Observations:
audience='everyone'batches on affected chapters failedaudience='students'batches succeeded (Derby hadno duplicate student subscriptions)audience='coaches'batches succeeded (fewer duplicate coach subscriptions)Email Impact Summary:
Note: Emails that succeeded in failed batches were NOT logged, making it impossible to determine exactly which members received invitations.
Affected Chapters
46 of 54 chapters (85%) have members with duplicate subscriptions:
~1,200 members affected across all chapters.
Solution
Add
.distincttochapter_studentsandchapter_coachesmethods to deduplicate members before processing:This restores the deduplication behavior that existed before the May 2018 refactoring.
Test Plan
Added comprehensive tests:
chapter_studentsreturns unique members when subscribed to same group multiple timeschapter_coachesreturns unique members when subscribed to same group multiple timesAll existing tests pass (30 examples, 0 failures).
Verification
Run locally:
bundle exec rspec spec/models/invitation_manager_spec.rb spec/models/invitation_manager_deduplication_spec.rbTo verify in production after deploy: