collectiveidea/audited · error · StandardError

invalid action given #{action}

Error message

invalid action given #{action}

What it means

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.

Source

Thrown at lib/audited/audit.rb:109

      (audited_changes || {}).each_with_object({}.with_indifferent_access) do |(attr, values), attrs|
        attrs[attr] = (action == "update") ? values.first : values
      end
    end

    # Allows user to undo changes
    def undo
      case action
      when "create"
        # destroys a newly created record
        auditable.destroy!
      when "destroy"
        # creates a new record with the destroyed record attributes
        auditable_type.constantize.create!(audited_changes)
      when "update"
        # changes back attributes
        auditable.update!(audited_changes.transform_values(&:first))
      else
        raise StandardError, "invalid action given #{action}"
      end
    end

    # Allows user to be set to either a string or an ActiveRecord object
    # @private
    def user_as_string=(user)
      # reset both either way
      self.user_as_model = self.username = nil
      user.is_a?(::ActiveRecord::Base) ?
        self.user_as_model = user :
        self.username = user
    end
    alias_method :user_as_model=, :user=
    alias_method :user=, :user_as_string=

    # @private
    def user_as_string
      user_as_model || username

View on GitHub (pinned to dbf8432604)

Solutions

  1. 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.
  2. 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.
  3. 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.
  4. 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.

Example fix

# before
audit.undo # => raises StandardError: "invalid action given login"

# after
UNDOABLE_ACTIONS = %w[create update destroy].freeze

return unless UNDOABLE_ACTIONS.include?(audit.action)
audit.undo
Defensive patterns

Strategy: validation

Validate before calling

UNDOABLE_ACTIONS = %w[create update destroy].freeze

# call before audit.undo
return unless UNDOABLE_ACTIONS.include?(audit.action)
audit.undo

Type guard

# Returns true only for audits that Audited's #undo knows how to reverse.
def undoable?(audit)
  audit.is_a?(Audited::Audit) && %w[create update destroy].include?(audit.action)
end

# usage
audit.undo if undoable?(audit)

Try / catch

# This error is a plain StandardError (no dedicated class), so guard first
# and rescue narrowly by message prefix, re-raising anything unrelated:
begin
  audit.undo
rescue StandardError => e
  raise unless e.message.start_with?("invalid action given")
  Rails.logger.warn("audit ##{audit.id} action=#{audit.action.inspect} is not undoable; skipping")
end

Prevention

When it happens

Trigger: 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.

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

Understand the failure class

Background: Invalid enum value errors: "Unknown type", "Invalid scope", "must be one of" — when a string is not on the library's allowed list — this error's family across 23 libraries.


AI-assisted analysis of collectiveidea/audited@dbf8432604 (2026-08-23). Data as JSON: /api/errors/223e39bdf147a36e. Report an issue: GitHub.