instructure/canvas-lms · error

ratings expected to be an array

Error message

ratings expected to be an array

What it means

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.

Solutions

  1. Wrap rating objects in an array: ratings: [{description: ..., points: ...}, ...].
  2. Send an empty array or omit the :ratings key if there are no ratings.
  3. Ensure the JSON body is not being flattened before reaching the endpoint.
  4. Validate Array.isArray/ratings.is_a?(Array) client-side.

Example fix

// before
ratings: { description: "Mastery", points: 3 }
// after
ratings: [{ description: "Mastery", points: 3 }]
Defensive patterns

Strategy: type-guard

Validate before calling

raise ArgumentError, "ratings must be an Array" unless ratings.is_a?(Array)

Type guard

def ratings_array?(opts) = opts[:ratings].nil? || opts[:ratings].is_a?(Array)

Try / catch

begin
  import_outcomes(options)
rescue RuntimeError => e
  raise InvalidRatingsPayload, e.message if e.message == 'ratings expected to be an array'
  raise
end

Prevention

When it happens

Trigger: Posting outcome import data where :ratings is a hash, string, or single rating object instead of an array (e.g. ratings: {...} or ratings: 'none').

Common situations: 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.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/3ad2cf7ad6219325. Report an issue: GitHub.

Appendix: source

Thrown at app/controllers/outcomes_academic_benchmark_import_api_controller.rb:111

    end
  end

  def parse_int(value)
    Integer value
  rescue
    raise "invalid value, must be integer: #{value}"
  end

  def valid_options(hash)
    options = {}
    if hash.key? :calculation_method
      options[:calculation_method] = parse_calculation_method hash[:calculation_method]
      options[:calculation_int] = parse_calculation_int options[:calculation_method], hash[:calculation_int]
    end
    options[:mastery_points] = parse_int hash[:mastery_points] if hash.key? :mastery_points
    options[:points_possible] = parse_int hash[:points_possible] if hash.key? :points_possible
    unless hash[:ratings].nil?
      raise "ratings expected to be an array" unless hash[:ratings].is_a?(Array)

      options[:ratings] = hash[:ratings].map { |r| parse_rating(r) }
    end
    options
  end

  def parse_calculation_method(value)
    unless LearningOutcome.valid_calculation_method?(value)
      raise "invalid calculation_method: #{value}"
    end

    value
  end

  def parse_calculation_int(calc_method, value)
    int = value.nil? ? nil : parse_int(value)
    unless LearningOutcome.valid_calculation_int?(int, calc_method)
      raise "invalid calculation_int: #{value}"

View on GitHub (pinned to 1c9f0bb801)