{"record":{"id":"023dad92518e7a38","repo":"instructure/canvas-lms","slug":"searching-for-announcements-in-sections-must-span-exactly","errorCode":null,"errorMessage":"Searching for announcements in sections must span exactly one course","messagePattern":"Searching for announcements in sections must span exactly one course","errorType":"exception","errorClass":"QueryError","httpStatus":null,"severity":"error","filePath":"app/models/discussion_topic.rb","lineNumber":906,"sourceCode":"  class QueryError < StandardError\n    attr_accessor :status_code\n\n    def initialize(message = nil, status_code = nil)\n      super(message)\n      self.status_code = status_code\n    end\n  end\n\n  # Retrieves all the *course* (as oppposed to group) discussion topics that apply\n  # to the given sections.  Group topics will not be returned.  TODO: figure out\n  # a good way to deal with group topics here.\n  #\n  # Takes in an array of section objects, and it is required that they all belong\n  # to the same course.  At least one section must be provided.\n  scope :in_sections, lambda { |course_sections|\n    course_ids = course_sections.pluck(:course_id).uniq\n    if course_ids.length != 1\n      raise QueryError, I18n.t(\"Searching for announcements in sections must span exactly one course\")\n    end\n\n    course_id = course_ids.first\n    joins(\"LEFT OUTER JOIN #{DiscussionTopicSectionVisibility.quoted_table_name}\n           AS discussion_section_visibilities ON discussion_topics.is_section_specific = true AND\n           discussion_section_visibilities.discussion_topic_id = discussion_topics.id\")\n      .where(\"discussion_topics.context_type = 'Course' AND\n             discussion_topics.context_id = :course_id\",\n             { course_id: })\n      .where(\"discussion_section_visibilities.id IS null OR\n             (discussion_section_visibilities.workflow_state = 'active' AND\n              discussion_section_visibilities.course_section_id IN (:course_sections))\",\n             { course_sections: course_sections.pluck(:id) }).distinct\n  }\n\n  scope :discussion_topic_section_visibility_scope, lambda { |student|\n    DiscussionTopicSectionVisibility\n      .select(1)","sourceCodeStart":888,"sourceCodeEnd":924,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/discussion_topic.rb#L888-L924","documentation":"DiscussionTopic.in_sections is a scope that builds an announcements query for a given array of CourseSection objects. It requires that all provided sections belong to exactly one course; otherwise the query would span multiple courses. When the unique course_id set has a size other than 1 (including zero sections), it raises QueryError.","triggerScenarios":"Calling DiscussionTopic.in_sections(sections) with sections from two or more different courses, or with an empty/unmatched relation that yields no course_ids.","commonSituations":"Account-level dashboards aggregating section-scoped announcements across multiple courses; passing a relation filtered by a deleted course (empty set).","solutions":["Group sections by course_id and call in_sections once per course, merging results.","Verify before calling: raise or short-circuit if sections.map(&:course_id).uniq.size != 1.","Handle empty input separately instead of passing it to in_sections."],"exampleFix":"// before\nAnnouncement.in_sections(sections)\n// after\nsections.group_by(&:course_id).each do |_, secs|\n  Announcement.in_sections(secs)\nend","handlingStrategy":"validation","validationCode":"course_ids = sections.map(&:course_id).uniq\nraise \"sections must span exactly one course\" unless course_ids.size == 1","typeGuard":null,"tryCatchPattern":"begin\n  Announcement.in_sections(sections)\nrescue QueryError => e\n  Rails.logger.warn(\"in_sections rejected: #{e.message}\")\n  sections.group_by(&:course_id).flat_map { |_, s| Announcement.in_sections(s) }\nend","preventionTips":["Always derive sections from a single course context","Check sections.map(&:course_id).uniq.size == 1 before calling","Handle empty section lists separately"],"tags":["activerecord","scope","guard-clause"],"backgroundTag":"invalid-argument-value","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"}