paper-trail-gem/paper_trail · warning

Attribute #{k} does not exist on #{version.item_type} (Versi

Error message

Attribute #{k} does not exist on #{version.item_type} (Version id: #{version.id}).

What it means

When reifying a version (e.g. `version.reify`), PaperTrail deserializes the `object` blob and applies each key to a fresh model instance. reify_attribute in lib/paper_trail/reifier.rb:99 first tries `model.has_attribute?(k)` (a real column), then a setter via `respond_to?(:"#{k}=")`; only when BOTH are missing does it log this message through `version.logger.warn` and silently skip the value. It is not an exception — the reified record is still returned — but it means historical data for that key is being dropped because the current schema no longer has the attribute.

Source

Thrown at lib/paper_trail/reifier.rb:99

      def init_unversioned_attrs(attrs, model)
        (model.attribute_names - attrs.keys).each { |k| attrs[k] = nil }
      end

      # Reify onto `model` an attribute named `k` with value `v` from `version`.
      #
      # `ObjectAttribute#deserialize` will return the mapped enum value and in
      # Rails < 5, the []= uses the integer type caster from the column
      # definition (in general) and thus will turn a (usually) string to 0
      # instead of the correct value.
      #
      # @api private
      def reify_attribute(k, v, model, version)
        if model.has_attribute?(k)
          model[k.to_sym] = v
        elsif model.respond_to?(:"#{k}=")
          model.send(:"#{k}=", v)
        elsif version.logger
          version.logger.warn(
            "Attribute #{k} does not exist on #{version.item_type} (Version id: #{version.id})."
          )
        end
      end

      # Reify onto `model` all the attributes of `version`.
      # @api private
      def reify_attributes(model, version, attrs)
        AttributeSerializers::ObjectAttribute.new(model.class).deserialize(attrs)
        attrs.each do |k, v|
          reify_attribute(k, v, model, version)
        end
      end

      # Given a `version`, return the class to reify. This method supports
      # Single Table Inheritance (STI) with custom inheritance columns and
      # custom inheritance column values.
      #

View on GitHub (pinned to 098058ae47)

Solutions

  1. If the attribute is still meaningful, add it back to the model (revert the drop, or re-add the column via a migration) so reify populates it again.
  2. If the column is gone for good, define a virtual setter on the model (`attr_accessor :old_column`) so historical values land somewhere instead of being skipped.
  3. If old data is disposable, migrate the version rows themselves: backfill/strip removed keys from the `object` YAML/JSON (or delete obsolete versions) so stored blobs match the current schema.
  4. Do nothing — the value is only logged and skipped, reify still returns the model with all remaining attributes.

Example fix

# Migration dropped :legacy_status after versions were recorded; reify logs:
# "Attribute legacy_status does not exist on Article (Version id: 42)"

# app/models/article.rb - after: virtual attribute absorbs historical values
class Article < ApplicationRecord
  has_paper_trail
  attr_accessor :legacy_status
end
Defensive patterns

Strategy: validation

Validate before calling

# Before reifying, verify the stored blob matches the current schema
klass = version.item_type.constantize
model = klass.new
missing = version.attributes["object"].to_s.keys.map(&:to_s).reject do |k|
  model.has_attribute?(k) || model.respond_to?("#{k}=")
end
Rails.logger.warn("reify will drop: #{missing.inspect} for #{klass}") unless missing.empty?

Type guard

def reify_safe?(version)
  model = version.item_type.constantize.new
  version.attributes["object"].to_s.keys.all? do |k|
    model.has_attribute?(k.to_s) || model.respond_to?("#{k}=")
  end
end

Prevention

When it happens

Trigger: Calling `PaperTrail::Version#reify` (directly or via `.previous`, `.versions.last.reify`, etc.) on a version whose serialized `object` contains a key that is neither a column of `version.item_type`'s class nor backed by a `#{k}=` setter. Classic cause: a column was dropped or renamed AFTER those versions were recorded, or the item_type's schema differs across environments.

Common situations: Running a `remove_column`/`rename_column` migration without pruning or rewriting old PaperTrail versions; restoring a deleted record (`versions.last.reify.save!`) months after a schema change and wondering why the old field is gone; multi-tenant or sharded setups where one environment's model lacks columns present when the version row was written.

Related errors


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