{"record":{"id":"3ad2cf7ad6219325","repo":"instructure/canvas-lms","slug":"ratings-expected-to-be-an-array","errorCode":null,"errorMessage":"ratings expected to be an array","messagePattern":"ratings expected to be an array","errorType":"validation","errorClass":null,"httpStatus":500,"severity":"error","filePath":"app/controllers/outcomes_academic_benchmark_import_api_controller.rb","lineNumber":111,"sourceCode":"    end\n  end\n\n  def parse_int(value)\n    Integer value\n  rescue\n    raise \"invalid value, must be integer: #{value}\"\n  end\n\n  def valid_options(hash)\n    options = {}\n    if hash.key? :calculation_method\n      options[:calculation_method] = parse_calculation_method hash[:calculation_method]\n      options[:calculation_int] = parse_calculation_int options[:calculation_method], hash[:calculation_int]\n    end\n    options[:mastery_points] = parse_int hash[:mastery_points] if hash.key? :mastery_points\n    options[:points_possible] = parse_int hash[:points_possible] if hash.key? :points_possible\n    unless hash[:ratings].nil?\n      raise \"ratings expected to be an array\" unless hash[:ratings].is_a?(Array)\n\n      options[:ratings] = hash[:ratings].map { |r| parse_rating(r) }\n    end\n    options\n  end\n\n  def parse_calculation_method(value)\n    unless LearningOutcome.valid_calculation_method?(value)\n      raise \"invalid calculation_method: #{value}\"\n    end\n\n    value\n  end\n\n  def parse_calculation_int(calc_method, value)\n    int = value.nil? ? nil : parse_int(value)\n    unless LearningOutcome.valid_calculation_int?(int, calc_method)\n      raise \"invalid calculation_int: #{value}\"","sourceCodeStart":93,"sourceCodeEnd":129,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/controllers/outcomes_academic_benchmark_import_api_controller.rb#L93-L129","documentation":"OutcomesAcademicBenchmarkImportApiController#valid_options checks that the optional ratings attribute is an Array before parsing each rating and raises otherwise. Ratings (mastery rubric levels) must arrive as an array of parameter objects.","triggerScenarios":"Posting outcome import data where :ratings is a hash, string, or single rating object instead of an array (e.g. ratings: {...} or ratings: 'none').","commonSituations":"Clients wrapping a single rating object without the array; JSON parsers turning a single-element collection into an object; template mix-ups sending a comma-separated string.","solutions":["Wrap rating objects in an array: ratings: [{description: ..., points: ...}, ...].","Send an empty array or omit the :ratings key if there are no ratings.","Ensure the JSON body is not being flattened before reaching the endpoint.","Validate Array.isArray/ratings.is_a?(Array) client-side."],"exampleFix":"// before\nratings: { description: \"Mastery\", points: 3 }\n// after\nratings: [{ description: \"Mastery\", points: 3 }]","handlingStrategy":"type-guard","validationCode":"raise ArgumentError, \"ratings must be an Array\" unless ratings.is_a?(Array)","typeGuard":"def ratings_array?(opts) = opts[:ratings].nil? || opts[:ratings].is_a?(Array)","tryCatchPattern":"begin\n  import_outcomes(options)\nrescue RuntimeError => e\n  raise InvalidRatingsPayload, e.message if e.message == 'ratings expected to be an array'\n  raise\nend","preventionTips":["Always serialize ratings as a JSON array.","Don't unwrap single-element rating collections.","Omit :ratings entirely when there are none.","Type-check the payload before posting."],"tags":["api","validation","ratings"],"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"}