{"record":{"id":"223e39bdf147a36e","repo":"collectiveidea/audited","slug":"invalid-action-given-action","errorCode":null,"errorMessage":"invalid action given #{action}","messagePattern":"invalid action given #(.+?)","errorType":"exception","errorClass":"StandardError","httpStatus":null,"severity":"error","filePath":"lib/audited/audit.rb","lineNumber":109,"sourceCode":"      (audited_changes || {}).each_with_object({}.with_indifferent_access) do |(attr, values), attrs|\n        attrs[attr] = (action == \"update\") ? values.first : values\n      end\n    end\n\n    # Allows user to undo changes\n    def undo\n      case action\n      when \"create\"\n        # destroys a newly created record\n        auditable.destroy!\n      when \"destroy\"\n        # creates a new record with the destroyed record attributes\n        auditable_type.constantize.create!(audited_changes)\n      when \"update\"\n        # changes back attributes\n        auditable.update!(audited_changes.transform_values(&:first))\n      else\n        raise StandardError, \"invalid action given #{action}\"\n      end\n    end\n\n    # Allows user to be set to either a string or an ActiveRecord object\n    # @private\n    def user_as_string=(user)\n      # reset both either way\n      self.user_as_model = self.username = nil\n      user.is_a?(::ActiveRecord::Base) ?\n        self.user_as_model = user :\n        self.username = user\n    end\n    alias_method :user_as_model=, :user=\n    alias_method :user=, :user_as_string=\n\n    # @private\n    def user_as_string\n      user_as_model || username","sourceCodeStart":91,"sourceCodeEnd":127,"githubUrl":"https://github.com/collectiveidea/audited/blob/dbf8432604ec97734bcfb3dc6df61f4faa0d1b84/lib/audited/audit.rb#L91-L127","documentation":"This is raised by Audited's Audit#undo (collectiveidea/audited, lib/audited/audit.rb). Audited writes one audit row per change with an action of 'create', 'update', or 'destroy', and #undo reverses exactly those three: a 'create' audit destroys the record, a 'destroy' audit recreates it from audited_changes, and an 'update' audit writes back the old values (audited_changes.transform_values(&:first)). Any other value in the audits.action column falls into the else branch and raises a plain StandardError with the message 'invalid action given <action>'. Because it is a bare StandardError rather than a dedicated error class, you cannot rescue it by class alone without catching everything else.","triggerScenarios":"Calling #undo on an audit whose action string is not exactly 'create', 'update', or 'destroy'. Concrete cases: (1) audits you wrote yourself with a custom action, e.g. user.audits.create(action: 'login', audited_changes: {...}) or Audited.audit_class.create!(auditable: user, action: 'promote'), then audit.undo; (2) case/format variants such as 'Update', 'update_attributes', ' archived ', or nil action from a manual SQL insert or rake task; (3) audit rows imported or migrated from another auditing setup or pre-4.x acts_as_audited data where action verbs differ; (4) an undo/rollback feature that iterates a model's audit history and blindly calls undo on every row.","commonSituations":"Teams that also store domain events (login, promote, export) in the audits table with custom action strings, then build an admin 'undo' button over audit history. Data migrations importing audits from other systems or old acts_as_audited versions whose action vocabulary differs. Hand-written SQL/rake backfills inserting audits without the canonical action values. Restored or hand-edited audit tables where action was normalized differently (casing, whitespace).","solutions":["Guard before calling: only invoke undo when audit.action is one of 'create', 'update', 'destroy' (see validationCode) and treat custom actions as non-reversible by undo.","If you write custom-action audits, reverse them yourself: dispatch on your own action names in application code instead of relying on Audit#undo, or subclass Audited::Audit and extend undo with a when clause for your actions.","Inspect the offending row and its writer: audit = Audited.audit_class.find(id); check audit.action for casing, whitespace, or nil, then fix the code path that inserted the bad value.","If rows carry near-canonical legacy values (e.g. 'Update', 'update_attributes'), run a data migration normalizing audits.action to the three canonical strings and add a CHECK constraint (CHECK action IN ('create','update','destroy') or an expanded whitelist) so bad values cannot re-enter."],"exampleFix":"# before\naudit.undo # => raises StandardError: \"invalid action given login\"\n\n# after\nUNDOABLE_ACTIONS = %w[create update destroy].freeze\n\nreturn unless UNDOABLE_ACTIONS.include?(audit.action)\naudit.undo","handlingStrategy":"validation","validationCode":"UNDOABLE_ACTIONS = %w[create update destroy].freeze\n\n# call before audit.undo\nreturn unless UNDOABLE_ACTIONS.include?(audit.action)\naudit.undo","typeGuard":"# Returns true only for audits that Audited's #undo knows how to reverse.\ndef undoable?(audit)\n  audit.is_a?(Audited::Audit) && %w[create update destroy].include?(audit.action)\nend\n\n# usage\naudit.undo if undoable?(audit)","tryCatchPattern":"# This error is a plain StandardError (no dedicated class), so guard first\n# and rescue narrowly by message prefix, re-raising anything unrelated:\nbegin\n  audit.undo\nrescue StandardError => e\n  raise unless e.message.start_with?(\"invalid action given\")\n  Rails.logger.warn(\"audit ##{audit.id} action=#{audit.action.inspect} is not undoable; skipping\")\nend","preventionTips":["Write only the canonical 'create'/'update'/'destroy' action strings into audits unless you intend to reverse them with your own code.","Keep a whitelist constant (e.g. UNDOABLE_ACTIONS) and gate every undo/rollback UI and background job on it.","Add a model validation or DB CHECK constraint on audits.action covering the canonical values plus any custom ones you deliberately use.","When importing or migrating audits from other systems, normalize the action column to the canonical strings before enabling undo features.","Never rescue bare StandardError around undo without re-raising unrelated failures — match the 'invalid action given' message prefix instead."],"tags":["ruby","rails","audited","activerecord","audit-trail","invalid-action"],"backgroundTag":"invalid-enum-value","analyzedSha":"dbf8432604ec97734bcfb3dc6df61f4faa0d1b84","analyzedAt":"2026-08-23T10:31:47.706Z","schemaVersion":2},"datasetVersion":"2026-08-23T13:39:53.451Z"}