paper-trail-gem/paper_trail · warning

Passing Version class name as `has_paper_trail class_name: %

Error message

Passing Version class name as `has_paper_trail class_name: %{class_name}` is deprecated. Use `has_paper_trail versions: {class_name: %{class_name}}` instead. The hash you pass to `versions:` is now passed directly to `has_many`.

What it means

has_paper_trail's old `class_name:` option (used to point a host model at a custom Version class) was replaced by the `versions:` hash. check_version_class_name in lib/paper_trail/model_config.rb:159 detects `options[:class_name]`, warns via PaperTrail.deprecator, then copies the value into `options[:versions][:class_name]` so behavior is unchanged for now. The `versions:` hash is now passed straight to `has_many`, so the old flat key will be removed in a future PT release — code relying on it will then silently fall back to PaperTrail::Version or raise.

Source

Thrown at lib/paper_trail/model_config.rb:159

    def assert_valid_recording_order_for_on_destroy(recording_order)
      unless %w[after before].include?(recording_order.to_s)
        raise ArgumentError, 'recording order can only be "after" or "before"'
      end

      if recording_order.to_s == "after" && cannot_record_after_destroy?
        raise Error, E_CANNOT_RECORD_AFTER_DESTROY
      end
    end

    def cannot_record_after_destroy?
      ::ActiveRecord::Base.belongs_to_required_by_default
    end

    def check_version_class_name(options)
      # @api private - `version_class_name`
      @model_class.class_attribute :version_class_name
      if options[:class_name]
        PaperTrail.deprecator.warn(
          format(
            DPR_CLASS_NAME_OPTION,
            class_name: options[:class_name].inspect
          ),
          caller(1)
        )
        options[:versions][:class_name] = options[:class_name]
      end
      @model_class.version_class_name = options[:versions][:class_name] || "PaperTrail::Version"
      assert_concrete_activerecord_class(@model_class.version_class_name)
    end

    def check_versions_association_name(options)
      # @api private - versions_association_name
      @model_class.class_attribute :versions_association_name
      @model_class.versions_association_name = options[:versions][:name] || :versions
    end

View on GitHub (pinned to 098058ae47)

Solutions

  1. Replace the flat option with the nested hash: `has_paper_trail versions: { class_name: "MyVersion" }`.
  2. If you were only passing class_name to get the default, drop the option entirely — PT defaults to PaperTrail::Version.
  3. Make deprecations loud in test/CI (`PaperTrail.deprecator.behavior = :raise`) and fix every call site before the next PT major, where the legacy key is removed.

Example fix

# app/models/article.rb - before
has_paper_trail class_name: "ArticleVersion"

# app/models/article.rb - after
has_paper_trail versions: { class_name: "ArticleVersion" }
Defensive patterns

Strategy: validation

Validate before calling

# CI guard: fail if any model still uses the deprecated flat option
# (run from repo root; adjust dirs to your layout)
# grep -rn --include='*.rb' -E 'has_paper_trail[^\n]*class_name:' app lib | grep -v 'versions:' && exit 1 || exit 0

Prevention

When it happens

Trigger: Declaring a host model with `has_paper_trail class_name: "MyVersion"` (or passing `class_name:` inside has_paper_trail options in any form) on paper_trail 11+/12+. Fires once per model class at boot time when ModelConfig#check_version_class_name runs, with caller(1) pointing at the offending model file.

Common situations: Apps upgrading from paper_trail < 11 (where `class_name:` was the documented syntax for Custom Version Classes) to PT 11/12+; old initializers or gems that configure `has_paper_trail class_name:` on behalf of models; copy-pasted blog examples predating the option reshuffle.

Related errors


AI-assisted analysis of paper-trail-gem/paper_trail@098058ae47 (2026-08-21). Data as JSON: /api/errors/ca733439045409ce. Report an issue: GitHub.