Repository navigation
Allow post-close cancellations with frozen waitlist progression - #3003
Conversation
83bc64f to
0d1a071
Compare
| return unless next_spot | ||
|
|
||
| invitation = next_spot.invitation | ||
| next_spot.destroy |
There was a problem hiding this comment.
as we're using update! on line :37 I think it makes sense to also hard error if a record doesn't get destroyed by using destroy! here
There was a problem hiding this comment.
Changed to destroy! in f3f1988. It fixes more than style: promote_next already raises through update! when confirming the invitation fails, so a silent destroy failure would leave the entry on the waiting list while the invitation becomes attending. The entry would stay at the head of the list and a later promotion could confirm the same invitation again. destroy! raises and rolls the transaction back instead.
Thank you 🙏
| include InstanceMethods | ||
| end | ||
|
|
||
| module InstanceMethods |
There was a problem hiding this comment.
Minor: are we using the extra level of indirection here, for testing, for example, or is it just an extra level?
That is, it is possible to enter def directly in the included block.
There was a problem hiding this comment.
Flattened in d7f6ad4. The included do / include InstanceMethods shape exists for callbacks, and this concern defines none, so it is now a plain module with the methods declared directly and private at the top. (The same shape in the sibling concerns stays as-is: WorkshopInvitationConcerns needs the included block for its before_action.)
| # Releases the invitation's seat under a row lock so two concurrent | ||
| # cancellation requests cannot each believe they freed a seat. Returns true | ||
| # only when this request moved a seat-holder to not attending. | ||
| def release_seat(additional_attributes = {}) |
There was a problem hiding this comment.
Is it possible to use kwargs, ** here?
| = f.input :tutorial, collection: @tutorial_titles, include_blank: true | ||
| = f.input :note, required: false, input_html: { rows: 3, maxlength: 100 }, hint: 'Anything else we should know?', placeholder: 'e.g. I need help understanding selectors' | ||
| = f.button :button, 'Join the waiting list', class: 'btn btn-primary w-100 mb-0' | ||
| - if @workshop.waitlist_open? |
There was a problem hiding this comment.
Minor: this partial can take Strict Locals.
There was a problem hiding this comment.
Added -# locals: (invitation:) in d7f6ad4. Both render sites in show.html.haml already pass the local. Note the partial still reads @workshop, @invitation, and @tutorial_titles from ivars, so the guarantee covers the invitation local only.
Apply Olle's review comments on PR codebar#3003. The concern defines no callbacks, so it drops ActiveSupport::Concern and the InstanceMethods split for a plain private module. release_seat takes keyword arguments now; both call sites already passed kwargs. The waiting list partial declares strict locals; its only render sites already pass invitation. No behaviour change, suite unchanged at 1714 examples.
Adds the two time gates from issue codebar#2990. waitlist_closes_at is the earlier of the custom RSVP close time and the freeze, so new joins stop at the configured close. cancellations_open? tracks the freeze alone, so a member may cancel after the close and until the freeze. The freeze is the hard cutoff 3.5 hours before the start, requirement 4 of the issue, and rsvp_freezes_at owns that offset alone. The RSVP flows that apply these gates follow in the next commit.
A cancel frees a seat only when the invitation held one, so rejecting an invitation that never answered must not consume a waiting-list entry. release_seat claims the seat with a row-locked read-modify-write, so duplicate concurrent cancels free it once. promote_next pops the head in a transaction with FOR UPDATE SKIP LOCKED so concurrent promoters take different entries, and confirms with update! so a failed confirmation rolls the pop back instead of emailing a spot that was never granted. The FIFO order and the seat-freed rule are pinned by specs. The locking behaviour cannot be exercised by the single-threaded suite, so this message is where it is recorded. WaitingList.next_spot still serves the current RSVP flows until the next commit replaces it.
…eeze Once the custom RSVP close time passes, new joins to the main list and the waiting list stay blocked, while main-list members may cancel until the freeze. Each cancel that frees a seat promotes the first auto-RSVP waiting-list entry in created order. Admin removals use the same promotion path while the workshop is in the future. This commit replaces WaitingList.next_spot in the RSVP flows and removes it, now that promote_next covers it. The end-to-end pins cover the reject path, the admin removal path, and the waitlist join and leave gates.
promote_next already raises through update! when confirming the invitation fails, so a silent destroy failure left the entry on the waitlist while the invitation became attending, allowing a repeat promotion and duplicate attending email. destroy! rolls the whole transaction back instead.
Apply Olle's review comments on PR codebar#3003. The concern defines no callbacks, so it drops ActiveSupport::Concern and the InstanceMethods split for a plain private module. release_seat takes keyword arguments now; both call sites already passed kwargs. The waiting list partial declares strict locals; its only render sites already pass invitation. No behaviour change, suite unchanged at 1714 examples.
promote_next dropped the self-exclusion guard that PR codebar#2997 built into the reject flow with find_by(...)&.destroy before the next-spot lookup. With the cancelling member's own entry first (often the only one), the promotion confirmed their just-cancelled invitation and emailed them an attendance notice instead of reaching the real next member. promote_next now takes excluding:, and both callers pass the invitation being cancelled. Update the controller examples: the rejecter's entry stays on the list until they leave it, rather than being consumed by their own cancellation.
d7f6ad4 to
02afcec
Compare
Encodes the keyword-arguments-over-option-hashes preference from the PR codebar#3003 review for code written from now on. Issue codebar#3009.
Encodes the keyword-arguments-over-option-hashes preference from the PR codebar#3003 review for code written from now on. Issue codebar#3009.
Once a custom RSVP close time passes, members on the main list can still cancel, and each cancellation fills the freed seat from the waiting list until a hard freeze 3.5 hours before the workshop starts. New joins stay blocked from the close time onward. Closes #2990.
Review notes
release_seatclaims the seat under a row lock, andWaitingList.promote_nextpops the head in a transaction withFOR UPDATE SKIP LOCKED. The two must stay consistent: a repeated cancel must not free the same seat twice, and two promotions must not take the same entry. Test suites cannot prove that interleaving, so this part needs the closest read.Behaviour by action
The cross-role promotion spec calls
promote_next. This change replacesWaitingList.next_spotwithpromote_next, which confirms the invitation and removes the waiting-list entry in one step.