paper-trail-gem/paper_trail · warning

Passing versions association name as `has_paper_trail versio

Error message

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

What it means

has_paper_trail's `versions:` option used to accept a bare symbol/string naming the versions association (`versions: :audit_records`). ensure_versions_option_is_hash in lib/paper_trail/model_config.rb:195 now requires a Hash — the hash is forwarded directly to `has_many` (see the setup at model_config.rb:186-192, which splats `options[:versions].except(:name, :scope)`), so :name and :scope moved inside it. A non-nil non-Hash value triggers this deprecation via PaperTrail.deprecator and is then coerced to `{ name: options[:versions] }`, so current behavior is preserved but the bare form will stop working in a future release.

Source

Thrown at lib/paper_trail/model_config.rb:195

    def define_has_many_versions(options)
      options = ensure_versions_option_is_hash(options)
      check_version_class_name(options)
      check_versions_association_name(options)
      scope = get_versions_scope(options)
      @model_class.has_many(
        @model_class.versions_association_name,
        scope,
        class_name: @model_class.version_class_name,
        as: :item,
        **options[:versions].except(:name, :scope)
      )
    end

    def ensure_versions_option_is_hash(options)
      unless options[:versions].is_a?(Hash)
        if options[:versions]
          PaperTrail.deprecator.warn(
            format(
              DPR_PASSING_ASSOC_NAME_DIRECTLY_TO_VERSIONS_OPTION,
              versions_name: options[:versions].inspect
            ),
            caller(1)
          )
        end
        options[:versions] = {
          name: options[:versions]
        }
      end
      options
    end

    # Process an `ignore`, `skip`, or `only` option.
    def event_attribute_option(option_name)
      [@model_class.paper_trail_options[option_name]].
        flatten.

View on GitHub (pinned to 098058ae47)

Solutions

  1. Convert the bare value to a hash with the :name key: `has_paper_trail versions: { name: :audit_records }`.
  2. If you also customize the association scope or other has_many options, put them in the same hash (`versions: { name: :audit_records, scope: -> { order("created_at DESC") } }`) since the hash now goes directly to has_many.
  3. Sweep the codebase for other models using the shorthand and set `PaperTrail.deprecator.behavior = :raise` in tests so remaining call sites fail CI.

Example fix

# app/models/article.rb - before
has_paper_trail versions: :article_versions

# app/models/article.rb - after
has_paper_trail versions: { name: :article_versions }
Defensive patterns

Strategy: validation

Validate before calling

# CI guard: fail if any model passes a bare symbol/string to versions:
# grep -rn --include='*.rb' -E 'has_paper_trail[^\n]*versions:\s*:[a-zA-Z_]' app lib && exit 1 || exit 0

Prevention

When it happens

Trigger: Writing `has_paper_trail versions: :my_versions` or `versions: "my_versions"` (any truthy non-Hash) in a model on paper_trail 11+/12+. Fires at boot when ModelConfig#ensure_versions_option_is_hash runs; `versions: nil`, `versions: false`, or an omitted key do NOT warn (the `if options[:versions]` guard).

Common situations: Models upgraded from paper_trail < 11 that renamed the association via the old `versions: :symbol` shorthand; developers mixing the two syntaxes (`versions: { name: :x }` in one model, `versions: :x` in another); code generators or templates still emitting the pre-11 form.

Related errors


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