diff --git a/CHANGELOG.md b/CHANGELOG.md index fab17070..7d81abb5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ PaperTrail::Rails::Engine.eager_load! ``` + - [#440](https://github.com/airblade/paper_trail/pull/440) - `versions` association should clear/reload after a transaction rollback. - [#438](https://github.com/airblade/paper_trail/issues/438) - `Model.paper_trail_enabled_for_model?` should return `false` if `has_paper_trail` has not been declared on the class. - [#427](https://github.com/airblade/paper_trail/pull/427) - Fix `reify` method in context of model where a column has been removed. diff --git a/lib/paper_trail/has_paper_trail.rb b/lib/paper_trail/has_paper_trail.rb index 793ed363..11ad7b04 100644 --- a/lib/paper_trail/has_paper_trail.rb +++ b/lib/paper_trail/has_paper_trail.rb @@ -78,6 +78,7 @@ module PaperTrail after_update :clear_version_instance! end after_destroy :record_destroy, :if => :save_version? if options_on.empty? || options_on.include?(:destroy) + after_rollback :clear_rolled_back_versions end # Switches PaperTrail off for this class. @@ -180,6 +181,12 @@ module PaperTrail (source_version || send(self.class.versions_association_name).last).try(:whodunnit) end + # Invoked after rollbacks to ensure versions records are not created + # for changes that never actually took place + def clear_rolled_back_versions + send(self.class.versions_association_name).reload + end + # Returns the object (not a Version) as it was at the given timestamp. def version_at(timestamp, reify_options={}) # Because a version stores how its object looked *before* the change, diff --git a/spec/models/widget_spec.rb b/spec/models/widget_spec.rb index bbd87d3a..a1ff9aab 100644 --- a/spec/models/widget_spec.rb +++ b/spec/models/widget_spec.rb @@ -85,6 +85,29 @@ describe Widget, :type => :model do expect(widget.version).to eq(widget.versions.last) end end + + describe :after_rollback do + let(:rolled_back_name) { 'Big Moo' } + + before do + begin + widget.transaction do + widget.update_attributes!(:name => rolled_back_name) + widget.update_attributes!(:name => Widget::EXCLUDED_NAME) + end + rescue ActiveRecord::RecordInvalid + widget.reload + widget.name = nil + widget.save + end + end + + it 'does not create an event for changes that did not happen' do + widget.versions.map(&:changeset).each do |changeset| + expect(changeset.fetch('name', [])).to_not include(rolled_back_name) + end + end + end end describe "Association", :versioning => true do diff --git a/test/dummy/app/models/widget.rb b/test/dummy/app/models/widget.rb index 68eb40eb..dd2e51aa 100644 --- a/test/dummy/app/models/widget.rb +++ b/test/dummy/app/models/widget.rb @@ -2,6 +2,10 @@ class Widget < ActiveRecord::Base has_paper_trail has_one :wotsit + EXCLUDED_NAME = 'Biglet' + + validates :name, :exclusion => { :in => [EXCLUDED_NAME] } + if ::ActiveRecord::VERSION::MAJOR >= 4 # `has_many` syntax for specifying order uses a lambda in Rails 4 has_many :fluxors, lambda { order(:name) } else