diff --git a/CHANGELOG.md b/CHANGELOG.md index b99d1537..c9346282 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,10 @@ recommendations of [keepachangelog.com](http://keepachangelog.com/). ### Fixed -- None +- [#1467](https://github.com/paper-trail-gem/paper_trail/issues/1467) - Rails 7.1 + enables ActiveRecord.raise_on_assign_to_attr_readonly so that writing to a + attr_readonly raises an exception. Fixes paper trail to allow setting the value + of an attr_readonly attribute to the value of the previous version when reifying. ## 16.0.0 (2024-11-08) diff --git a/lib/paper_trail/has_paper_trail.rb b/lib/paper_trail/has_paper_trail.rb index bbcf880e..84b8613a 100644 --- a/lib/paper_trail/has_paper_trail.rb +++ b/lib/paper_trail/has_paper_trail.rb @@ -13,6 +13,7 @@ module PaperTrail module Model def self.included(base) base.extend ClassMethods + base.thread_mattr_accessor :_paper_trail_reifying, instance_accessor: false end # :nodoc: @@ -78,6 +79,12 @@ def has_paper_trail(options = {}) def paper_trail ::PaperTrail::ModelConfig.new(self) end + + def readonly_attribute?(name) + return false if _paper_trail_reifying + + super + end end # Wrap the following methods in a module so we can include them only in the diff --git a/lib/paper_trail/reifier.rb b/lib/paper_trail/reifier.rb index d78f8720..53241c84 100644 --- a/lib/paper_trail/reifier.rb +++ b/lib/paper_trail/reifier.rb @@ -13,7 +13,9 @@ def reify(version, options) options = apply_defaults_to(options, version) attrs = version.object_deserialized model = init_model(attrs, options, version) + model.class._paper_trail_reifying = true reify_attributes(model, version, attrs) + model.class._paper_trail_reifying = false model.send "#{model.class.version_association_name}=", version model end diff --git a/spec/dummy_app/app/models/wotsit.rb b/spec/dummy_app/app/models/wotsit.rb index 37445535..f048ef00 100644 --- a/spec/dummy_app/app/models/wotsit.rb +++ b/spec/dummy_app/app/models/wotsit.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true class Wotsit < ApplicationRecord + attr_readonly :id has_paper_trail belongs_to :widget, optional: true diff --git a/spec/models/wotsit_spec.rb b/spec/models/wotsit_spec.rb index e72fbefa..5f752626 100644 --- a/spec/models/wotsit_spec.rb +++ b/spec/models/wotsit_spec.rb @@ -3,12 +3,40 @@ require "spec_helper" RSpec.describe Wotsit, versioning: true do - it "update! records timestamps" do - wotsit = described_class.create!(name: "wotsit") - wotsit.update!(name: "changed") - reified = wotsit.versions.last.reify - expect(reified.created_at).not_to(be_nil) - expect(reified.updated_at).not_to(be_nil) + context "when handling attr_readonly attributes" do + context "without raise_on_assign_to_attr_readonly" do + before do + # Rails 7.1 first introduces this setting, and framework_defaults 7.0 has it as false + if ActiveRecord.respond_to?(:raise_on_assign_to_attr_readonly) + ActiveRecord.raise_on_assign_to_attr_readonly = false + end + end + + it "update! records timestamps" do + wotsit = described_class.create!(name: "wotsit") + wotsit.update!(name: "changed") + reified = wotsit.versions.last.reify + expect(reified.created_at).not_to(be_nil) + expect(reified.updated_at).not_to(be_nil) + end + end + + if ActiveRecord.respond_to?(:raise_on_assign_to_attr_readonly) + context "with raise_on_assign_to_attr_readonly enabled" do + before do + ActiveRecord.raise_on_assign_to_attr_readonly = true + end + + it "update! records timestamps" do + wotsit = described_class.create!(name: "wotsit") + wotsit.update!(name: "changed") + reified = wotsit.versions.last.reify + expect(reified.created_at).not_to(be_nil) + expect(reified.updated_at).not_to(be_nil) + expect(reified.name).to eq("wotsit") + end + end + end end it "update! does not raise error" do