instructure/canvas-lms · error

invalid association

Error message

invalid association

What it means

Course#clear_todo_list_cache_later validates that the association type it schedules a delayed job for is actually a course association whose klass is User. If the symbol doesn't name a known association (or names one not backed by User), it raises 'invalid association' before enqueueing the job.

Solutions

  1. Pass one of the existing User associations on Course (e.g. :admins, :teachers, :students, :tas).
  2. Define the missing has_many association on Course with class_name 'User' before calling.
  3. Fix the symbol typo at the call site; check with course.respond_to?(association_type) and Course.association(association_type).klass first.

Example fix

// before
course.clear_todo_list_cache_later(:instructor)

// after
course.clear_todo_list_cache_later(:teachers)
Defensive patterns

Strategy: validation

Validate before calling

ok = Course.new.association(sym).klass == User rescue false

Type guard

def user_association?(course, sym)
  course.association(sym.to_sym).klass == User
rescue ArgumentError
  false
end

Prevention

When it happens

Trigger: Calling course.clear_todo_list_cache_later(:some_symbol) where :some_symbol is not an association defined on Course, or is an association that isn't to User (e.g. an association to another model or nonexistent).

Common situations: Plugin/patch code adds a new admin-like association but passes the wrong symbol; renaming a Course association (e.g. :admins, :teachers, :students, :ta) without updating clear_todo_list_cache_later call sites; typo'd symbol in custom invalidation code.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


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

Appendix: source

Thrown at app/models/course.rb:4591

    # RUBY 2.7 this can go away (**{} will work at the caller)
    raise ArgumentError, "Only send one hash" if !changes.empty? && !kwargs.empty?

    changes = kwargs if changes.empty? && !kwargs.empty?

    if changes[:is_public] || changes[:is_public_to_auth_users]
      assignments.touch_all
      attachments.touch_all
      calendar_events.touch_all
      context_modules.touch_all
      discussion_topics.touch_all
      quizzes.touch_all
      wiki.touch
      wiki_pages.touch_all
    end
  end

  def clear_todo_list_cache_later(association_type)
    raise "invalid association" unless association(association_type).klass == User

    delay(run_at: 15.seconds.from_now, singleton: "course_clear_cache_#{global_id}_#{association_type}", on_conflict: :loose)
      .clear_todo_list_cache(association_type)
  end

  def clear_todo_list_cache(association_type)
    raise "invalid association" unless association(association_type).klass == User

    send(association_type).clear_cache_keys(:todo_list)
  end

  def touch_admins # TODO: remove after existing jobs run
    clear_todo_list_cache(:admins)
  end

  def clear_caches_if_necessary
    clear_cache_key(:account_associations) if saved_change_to_root_account_id? || saved_change_to_account_id?
  end

View on GitHub (pinned to 1c9f0bb801)