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
- 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.
- 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.
- 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.
- 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
- Pair every remove_column/rename_column with a decision about old PaperTrail rows: backfill the `object` blob, or purge versions that reference the dropped key.
- Keep a virtual setter (`attr_accessor`) for recently removed columns for one release cycle so historical reify calls stay lossless.
- Monitor logs for 'does not exist on' during restores/audits — it is the earliest signal that schema drift has outrun your version history.
- In a regression test, reify the oldest version of each core model and assert no warning is emitted; this catches schema-drift before users do.
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
- PaperTrail %s is not compatible with ActiveRecord %s. We all
- Passing Version class name as `has_paper_trail class_name: %
- Passing versions association name as `has_paper_trail versio
AI-assisted analysis of paper-trail-gem/paper_trail@098058ae47 (2026-08-21).
Data as JSON: /api/errors/e1846062f071cb43.
Report an issue: GitHub.