{"record":{"id":"48ed646d9b25cfd9","repo":"instructure/canvas-lms","slug":"content-is-not-a-submission","errorCode":null,"errorMessage":"content is not a Submission","messagePattern":"content is not a Submission","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/models/content_participation.rb","lineNumber":146,"sourceCode":"    content.content_participations.build(user:, workflow_state:, content_item:)\n  end\n  private_class_method :build_item\n\n  def self.same_workflow_state?(participant, workflow_state)\n    participant.present? && participant.workflow_state == workflow_state\n  end\n  private_class_method :same_workflow_state?\n\n  def self.submission_read?(content:, user:)\n    submission_read_state(content, user) != \"unread\"\n  end\n\n  def self.submission_item_read?(content:, user:, content_item:)\n    submission_read_state(content, user, content_item) != \"unread\"\n  end\n\n  def self.submission_read_state(content, user, content_item = nil)\n    raise \"content is not a Submission\" unless content.is_a?(Submission)\n    raise \"#{content_item} is invalid\" if content_item.present? && !CONTENT_ITEMS.include?(content_item)\n\n    states = if content_item.present?\n               ContentParticipation.where(content:, user:, content_item:).pluck(:workflow_state)\n             else\n               ContentParticipation.where(content:, user:).pluck(:workflow_state)\n             end\n\n    return nil if states.empty?\n    return \"unread\" if states.any?(\"unread\")\n\n    \"read\"\n  end\n\n  def self.items_by_submission(participations, workflow_state)\n    unread_items = {}\n\n    participations.each do |cp|","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/content_participation.rb#L128-L164","documentation":"ContentParticipation.submission_read_state requires its content argument to be a Submission. Raising \"content is not a Submission\" protects the participation-read-state lookup, which queries ContentParticipation scoped by content.","triggerScenarios":"Calling submission_read_state (or submission_item_read?) with a DiscussionEntry, Assignment, or other non-Submission object as content:; passing a nil content; refactor renamed parameters and callers pass the wrong record.","commonSituations":"Generic read-state code reused for multiple content types and passed the wrong model; seed/data-fix scripts passing an assignment instead of its submission; view/controller passing the wrong association (submission vs submission.history etc.).","solutions":["Pass the Submission record (e.g. assignment.submissions.find_by(user:)) instead of the assignment or discussion entry","Guard the call site so only Submission instances reach submission_read_state","If tracking participation for other content types, use the appropriate participation class for that type"],"exampleFix":"# before\nContentParticipation.submission_read_state(content: assignment, user: student)\n# after\nsubmission = assignment.submissions.find_by!(user: student)\nContentParticipation.submission_read_state(content: submission, user: student)","handlingStrategy":"type-guard","validationCode":"raise 'expected Submission' unless content.is_a?(Submission)","typeGuard":"def submission?(obj) = obj.is_a?(Submission)","tryCatchPattern":"begin\n  ContentParticipation.submission_read_state(content:, user:)\nrescue RuntimeError => e\n  raise e unless e.message == 'content is not a Submission'\n  Rails.logger.warn('submission_read_state called with non-Submission')\nend","preventionTips":["Resolve the Submission (assignment.submissions.find_by(user:)) rather than passing the assignment","Type-check parameters at call sites in generic read-state helpers","Keep content-participation calls out of polymorphic code paths that pass mixed models"],"tags":["ruby","content-participation","type-check"],"backgroundTag":"type-mismatch","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"}