{"record":{"id":"e1846062f071cb43","repo":"paper-trail-gem/paper_trail","slug":"attribute-k-does-not-exist-on-version-item-ty","errorCode":null,"errorMessage":"Attribute #{k} does not exist on #{version.item_type} (Version id: #{version.id}).","messagePattern":"Attribute #(.+?) does not exist on #(.+?) \\(Version id: #(.+?)\\)\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"lib/paper_trail/reifier.rb","lineNumber":99,"sourceCode":"      def init_unversioned_attrs(attrs, model)\n        (model.attribute_names - attrs.keys).each { |k| attrs[k] = nil }\n      end\n\n      # Reify onto `model` an attribute named `k` with value `v` from `version`.\n      #\n      # `ObjectAttribute#deserialize` will return the mapped enum value and in\n      # Rails < 5, the []= uses the integer type caster from the column\n      # definition (in general) and thus will turn a (usually) string to 0\n      # instead of the correct value.\n      #\n      # @api private\n      def reify_attribute(k, v, model, version)\n        if model.has_attribute?(k)\n          model[k.to_sym] = v\n        elsif model.respond_to?(:\"#{k}=\")\n          model.send(:\"#{k}=\", v)\n        elsif version.logger\n          version.logger.warn(\n            \"Attribute #{k} does not exist on #{version.item_type} (Version id: #{version.id}).\"\n          )\n        end\n      end\n\n      # Reify onto `model` all the attributes of `version`.\n      # @api private\n      def reify_attributes(model, version, attrs)\n        AttributeSerializers::ObjectAttribute.new(model.class).deserialize(attrs)\n        attrs.each do |k, v|\n          reify_attribute(k, v, model, version)\n        end\n      end\n\n      # Given a `version`, return the class to reify. This method supports\n      # Single Table Inheritance (STI) with custom inheritance columns and\n      # custom inheritance column values.\n      #","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/paper-trail-gem/paper_trail/blob/098058ae472d13763fe66e6866a6a4dfc64a3eca/lib/paper_trail/reifier.rb#L81-L117","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"# Migration dropped :legacy_status after versions were recorded; reify logs:\n# \"Attribute legacy_status does not exist on Article (Version id: 42)\"\n\n# app/models/article.rb - after: virtual attribute absorbs historical values\nclass Article < ApplicationRecord\n  has_paper_trail\n  attr_accessor :legacy_status\nend","handlingStrategy":"validation","validationCode":"# Before reifying, verify the stored blob matches the current schema\nklass = version.item_type.constantize\nmodel = klass.new\nmissing = version.attributes[\"object\"].to_s.keys.map(&:to_s).reject do |k|\n  model.has_attribute?(k) || model.respond_to?(\"#{k}=\")\nend\nRails.logger.warn(\"reify will drop: #{missing.inspect} for #{klass}\") unless missing.empty?","typeGuard":"def reify_safe?(version)\n  model = version.item_type.constantize.new\n  version.attributes[\"object\"].to_s.keys.all? do |k|\n    model.has_attribute?(k.to_s) || model.respond_to?(\"#{k}=\")\n  end\nend","tryCatchPattern":null,"preventionTips":["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."],"tags":["ruby","paper-trail","activerecord","reify","schema-migration","audit-log"],"backgroundTag":"schema-column-mismatch","analyzedSha":"098058ae472d13763fe66e6866a6a4dfc64a3eca","analyzedAt":"2026-08-21T18:44:58.137Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}