{"record":{"id":"80e2fd9407bd77c0","repo":"instructure/canvas-lms","slug":"peer-review-sub-assignment-exists","errorCode":null,"errorMessage":"Peer review sub assignment exists","messagePattern":"Peer review sub assignment exists","errorType":"validation","errorClass":"PeerReview::SubAssignmentExistsError","httpStatus":null,"severity":"error","filePath":"app/services/peer_review/validations.rb","lineNumber":61,"sourceCode":"  def validate_assignment_submission_types(assignment)\n    if assignment.external_tool?\n      raise PeerReview::InvalidAssignmentSubmissionTypesError, I18n.t(\"Peer reviews cannot be used with External Tool assignments\")\n    end\n\n    if assignment.submission_types == \"discussion_topic\"\n      raise PeerReview::InvalidAssignmentSubmissionTypesError, I18n.t(\"Peer reviews cannot be used with Discussion Topic assignments\")\n    end\n  end\n\n  def validate_peer_review_sub_assignment_exists(assignment)\n    if assignment.peer_review_sub_assignment.blank?\n      raise PeerReview::SubAssignmentNotExistError, I18n.t(\"Peer review sub assignment does not exist\")\n    end\n  end\n\n  def validate_peer_review_sub_assignment_not_exist(assignment)\n    if assignment.peer_review_sub_assignment.present?\n      raise PeerReview::SubAssignmentExistsError, I18n.t(\"Peer review sub assignment exists\")\n    end\n  end\n\n  # Validates that peer review dates follow the restriction below\n  # peer review unlock_at < peer review due_at <= peer review lock_at\n  def validate_peer_review_dates(peer_review_dates)\n    parsed_dates = {}\n\n    %w[due_at unlock_at lock_at].each do |date_field|\n      date_value = peer_review_dates.fetch(date_field.to_sym, nil)\n      next unless date_value.present?\n\n      # Accept both Time objects and ISO8601 strings for API compatibility\n      if date_value.is_a?(String)\n        unless Api::ISO8601_REGEX.match?(date_value)\n          raise PeerReview::InvalidDatesError, I18n.t(\"Invalid datetime format for %{attribute}\", attribute: date_field)\n        end\n","sourceCodeStart":43,"sourceCodeEnd":79,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/services/peer_review/validations.rb#L43-L79","documentation":"PeerReview::Validations#validate_peer_review_sub_assignment_not_exist raises PeerReview::SubAssignmentExistsError when `assignment.peer_review_sub_assignment` is present but the operation (creation flow) requires that no sub assignment exists yet. It enforces the one-sub-assignment-per-assignment invariant.","triggerScenarios":"Calling the sub assignment creation service twice for the same parent assignment; a retry after a partially successful creation; syncing jobs creating sub assignments concurrently.","commonSituations":"Duplicate API calls from the frontend (double-click submit); background job retries without idempotency guards; course copy importing a sub assignment that already exists.","solutions":["Make creation idempotent: check `assignment.peer_review_sub_assignment.present?` and return the existing sub assignment instead of creating a new one.","Use the update service rather than the create service when a sub assignment already exists.","Serialize/dedupe background jobs per assignment ID to avoid concurrent creation."],"exampleFix":"// before\nPeerReview::SubAssignmentCreator.new(assignment: assignment).call # runs even if one exists\n// after\nunless assignment.peer_review_sub_assignment.present?\n  PeerReview::SubAssignmentCreator.new(assignment: assignment).call\nend","handlingStrategy":"validation","validationCode":"return assignment.peer_review_sub_assignment if assignment.peer_review_sub_assignment.present?","typeGuard":"def sub_assignment_exists?(assignment) = assignment.peer_review_sub_assignment.present?","tryCatchPattern":"begin\n  creator.call\nrescue PeerReview::SubAssignmentExistsError\n  assignment.reload.peer_review_sub_assignment # already created; treat as success\nend","preventionTips":["Make creation idempotent (find-or-create)","Deduplicate/lock background jobs per assignment ID","Debounce double-submits in the frontend"],"tags":["peer-review","rails","duplicate","validation"],"backgroundTag":"file-already-exists","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"}