{"record":{"id":"822cb372ff571446","repo":"instructure/canvas-lms","slug":"increment-completion-can-only-be-invoked-after-a-total-has","errorCode":null,"errorMessage":"`increment_completion!` can only be invoked after a total has been set with `calculate_completion!`","messagePattern":"`increment_completion!` can only be invoked after a total has been set with `calculate_completion!`","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"app/models/progress.rb","lineNumber":101,"sourceCode":"  end\n\n  def set_results(results)\n    self.results = results\n    save\n  end\n\n  def update_completion!(value)\n    update_attribute(:completion, value)\n  end\n\n  def calculate_completion!(current_value, total)\n    @total = total\n    @current_value = current_value\n    update_completion!(100.0 * @current_value / @total)\n  end\n\n  def increment_completion!(increment = 1)\n    raise \"`increment_completion!` can only be invoked after a total has been set with `calculate_completion!`\" if @total.nil?\n\n    @current_value += increment\n    new_value = 100.0 * @current_value / @total\n    # only update the db if we're at a different integral percentage point or it's been > 15s\n    if new_value.to_i != completion.to_i || (Time.now.utc - updated_at) > 15\n      update_completion!(new_value)\n    else\n      self.completion = new_value\n    end\n  end\n\n  def pending?\n    queued? || running? || waiting_for_external_tool?\n  end\n\n  # Tie this Progress model to a delayed job. Rather than `obj.delay.long_method`, use:\n  # `progress.process_job(obj, :long_method)`. This will transition from queued\n  # => running when the job starts, from running => completed when the job","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/models/progress.rb#L83-L119","documentation":"Progress#increment_completion! requires that Progress#calculate_completion! has already run to initialize @total (and @current_value). Calling increment_completion! before a total exists would divide by zero/nil, so the model raises this guard error. It protects the completion-percentage bookkeeping invariant.","triggerScenarios":"Calling progress.increment_completion!(n) on a Progress object/row where calculate_completion!(total, current_value) was never invoked (or was invoked on a different instance than the one reloaded from DB).","commonSituations":"Job or controller code increments progress on a freshly found Progress record without first calling calculate_completion!; long-running process caches a stale instance after reset; refactoring removed the initial calculate_completion! call.","solutions":["Call calculate_completion!(total, current_value) once at the start before any increment_completion! calls","Check that the same Progress instance is used for both calls (don't reload between them)","For persisted records, verify completion/total columns are set before incrementing (re-initialize if total is nil)","Default to calculate_completion! in the initialization path of the background job that tracks progress"],"exampleFix":"// before\nprogress.increment_completion!\n// after\nprogress.calculate_completion!(total_items, 0)\nprogress.increment_completion!\n","handlingStrategy":"validation","validationCode":"raise 'progress total not initialized' if progress.instance_variable_get(:@total).nil?\n# or for AR-backed records: initialize first\nprogress.calculate_completion!(total, 0) if progress.total.nil?","typeGuard":"can_increment = ->(p) { p.instance_variable_get(:@total).present? }","tryCatchPattern":"begin\n  progress.increment_completion!\nrescue RuntimeError => e\n  Rails.logger.warn(\"#{e.message}; initializing progress\")\n  progress.calculate_completion!(total, 0)\n  retry\nend","preventionTips":["Always call calculate_completion! immediately after creating/finding a Progress record","Keep a single Progress instance for the whole job lifecycle","Assert @total is set in tests that exercise increment_completion!","Centralize progress updates in one helper that owns initialization"],"tags":["progress","state-machine","ruby"],"backgroundTag":"invalid-state-transition","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"}