Summary
PR #2867 enforces one invitation per member per event/workshop in application code (InvitationManager). The database still encodes the old identity, so nothing prevents a second row — and any future code path (like the one fixed in the review commit for WorkshopsController#find_or_create_invitation) can reintroduce duplicates.
Current state in db/schema.rb:
invitations: unique index on (member_id, event_id, role) — role-inclusive, so it permits one row per role
workshop_invitations: unique index on (member_id, workshop_id, role) — same problem
Invitation and WorkshopInvitation model validations: uniqueness: { scope: [..., :role] }
Proposal
- Drop the role-inclusive unique indexes
- Add role-less unique indexes:
(member_id, event_id) on invitations, (member_id, workshop_id) on workshop_invitations
- Update the model uniqueness validations to match
These indexes also make create_or_find_by race-safe.
Prerequisites
- Historical duplicate rows must be deduped first (companion issue) or index creation fails
- The "which role survives" decision (companion issue) determines what the dedupe keeps
Related
Summary
PR #2867 enforces one invitation per member per event/workshop in application code (
InvitationManager). The database still encodes the old identity, so nothing prevents a second row — and any future code path (like the one fixed in the review commit forWorkshopsController#find_or_create_invitation) can reintroduce duplicates.Current state in
db/schema.rb:invitations: unique index on(member_id, event_id, role)— role-inclusive, so it permits one row per roleworkshop_invitations: unique index on(member_id, workshop_id, role)— same problemInvitationandWorkshopInvitationmodel validations:uniqueness: { scope: [..., :role] }Proposal
(member_id, event_id)oninvitations,(member_id, workshop_id)onworkshop_invitationsThese indexes also make
create_or_find_byrace-safe.Prerequisites
Related
invitationsandmeeting_invitations(different column, same tables) — coordinate migration ordering to avoid conflictingalgorithm: :concurrentlyruns