{"record":{"id":"bfea7ecaf6df5640","repo":"instructure/canvas-lms","slug":"participant-has-met-per-participant-limit","errorCode":null,"errorMessage":"participant has met per-participant limit","messagePattern":"participant has met per-participant limit","errorType":"exception","errorClass":"CalendarEvent::ReservationError","httpStatus":null,"severity":"error","filePath":"app/models/calendar_event.rb","lineNumber":608,"sourceCode":"\n  def reserve_for(participant, user, options = {})\n    raise ReservationError, \"not an appointment\" unless context_type == \"AppointmentGroup\"\n    raise ReservationError, \"ineligible participant\" unless context.eligible_participant?(participant)\n\n    transaction do\n      lock! # in case two people two participants try to grab the same slot\n      participant.lock! # in case two people try to make a reservation for the same participant\n\n      if options[:cancel_existing]\n        context.reservations_for(participant).lock.each do |reservation|\n          raise ReservationError, \"cannot cancel past reservation\" if reservation.end_at < Time.now.utc\n\n          reservation.updating_user = user\n          reservation.destroy\n        end\n      end\n\n      raise ReservationError, \"participant has met per-participant limit\" if context.max_appointments_per_participant && context.reservations_for(participant).size >= context.max_appointments_per_participant\n      raise ReservationError, \"all slots filled\" if participants_per_appointment && child_events.size >= participants_per_appointment\n      raise ReservationError, \"participant has already reserved this appointment\" if child_events_for(participant).present?\n\n      event = child_events.build\n      event.updating_user = user\n      event.context = participant\n      event.workflow_state = :locked\n      event.comments = options[:comments]\n      event.save!\n      if active?\n        self.workflow_state = \"locked\"\n        save!\n      end\n      context.clear_cached_available_slots!\n      event\n    end\n  end\n","sourceCodeStart":590,"sourceCodeEnd":626,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/calendar_event.rb#L590-L626","documentation":"CalendarEvent.reserve_for raises ReservationError when the appointment group's max_appointments_per_participant is set and the participant already holds reservations equal to or exceeding that limit. It enforces the per-participant cap on an appointment group before creating a new reservation.","triggerScenarios":"Calling reserve_for when context.max_appointments_per_participant is non-nil and context.reservations_for(participant).size >= max_appointments_per_participant.","commonSituations":"Student booking more appointment-group slots than the course allows; integrations or scripts looping over multiple slots; users double-submitting the booking form.","solutions":["Cancel an existing reservation first (if it is in the future) so the count drops below the limit, then retry reserve_for.","Raise max_appointments_per_participant on the appointment group via its update API if the limit is too strict.","Check reservations_for(participant).size before calling and surface a friendly 'limit reached' UI message instead of attempting the booking."],"exampleFix":"// before\nslot.reserve_for(user, participant, cancel_existing: true)\n// after\nlimit = appointment_group.max_appointments_per_participant\nexisting = appointment_group.reservations_for(participant)\nif limit && existing.size >= limit\n  raise UserFacingError, 'You have reached the booking limit for this appointment group'\nend\nslot.reserve_for(user, participant)","handlingStrategy":"validation","validationCode":"limit = appointment_group.max_appointments_per_participant\nif limit && appointment_group.reservations_for(participant).size >= limit\n  raise UserFacingError, 'booking limit reached'\nend","typeGuard":null,"tryCatchPattern":"begin\n  slot.reserve_for(user, participant)\nrescue ReservationError => e\n  if e.message.include?('per-participant limit')\n    notify_limit_reached(participant)\n  else\n    raise\n  end\nend","preventionTips":["Check reservation count against max_appointments_per_participant before booking","Display remaining slots to the user in the scheduler UI","Avoid retry loops around reserve_for without canceling reservations"],"tags":["calendar","appointments","limit-exceeded","scheduler"],"backgroundTag":"limit-exceeded","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}