diff --git a/README.md b/README.md index 21bf5b20..c48c461e 100644 --- a/README.md +++ b/README.md @@ -268,7 +268,7 @@ You can ignore changes to certain attributes like this: ```ruby class Article < ActiveRecord::Base - has_paper_trail :ignore => [:title, :rating] + has_paper_trail :ignore => [:title, :rating => Proc.new { |obj| obj.raiting == 0 } ] end ``` @@ -277,10 +277,13 @@ This means that changes to just the `title` or `rating` will not store another v ```ruby >> a = Article.create >> a.versions.length # 1 ->> a.update_attributes :title => 'My Title', :rating => 3 +>> a.update_attributes :title => 'My Title', :rating => 0 >> a.versions.length # 1 ->> a.update_attributes :title => 'Greeting', :content => 'Hello' +>> a.update_attributes :title => 'My Title', :rating => 3 >> a.versions.length # 2 +>> a.previous_version.raiting # nil +>> a.update_attributes :title => 'Greeting', :content => 'Hello' +>> a.versions.length # 3 >> a.previous_version.title # 'My Title' ``` @@ -288,11 +291,11 @@ Or, you can specify a list of all attributes you care about: ```ruby class Article < ActiveRecord::Base - has_paper_trail :only => [:title] + has_paper_trail :only => [:title, :author => Proc.new { |obj| obj.author.present? }] end ``` -This means that only changes to the `title` will save a version of the article: +This means that only changes to the `title` and non-empty `author` will save a version of the article: ```ruby >> a = Article.create @@ -302,6 +305,11 @@ This means that only changes to the `title` will save a version of the article: >> a.update_attributes :content => 'Hello' >> a.versions.length # 2 >> a.previous_version.content # nil +>> a.update_attributes :author => 'Me' +>> a.versions.length # 3 +>> a.update_attributes :author => '' +>> a.versions.length # 3 +>> a.previous_version.author # 'Me' ``` Passing both `:ignore` and `:only` options will result in the article being saved if a changed attribute is included in `:only` but not in `:ignore`. diff --git a/lib/paper_trail/has_paper_trail.rb b/lib/paper_trail/has_paper_trail.rb index dfd82235..c942793c 100644 --- a/lib/paper_trail/has_paper_trail.rb +++ b/lib/paper_trail/has_paper_trail.rb @@ -15,8 +15,12 @@ module PaperTrail # `:create`, `:update`, `:destroy` as desired. # :class_name the name of a custom Version class. This class should inherit from Version. # :ignore an array of attributes for which a new `Version` will not be created if only they change. + # it can also has a Hash as an item: the key is the attribute to ignore, the value - a Proc + # given a current state of object before save, only if it results in true - attribute will be ignored # :if, :unless Procs that allow to specify conditions when to save versions for an object # :only inverse of `ignore` - a new `Version` will be created only for these attributes if supplied + # it can also has a Hash as an item: the key is the attribute to track, the value - a Proc + # given a current state of object before save, only if it results in true - attribute will create a version # :skip fields to ignore completely. As with `ignore`, updates to these fields will not create # a new `Version`. In addition, these fields will not be included in the serialized versions # of the object whenever a new `Version` is created. @@ -46,7 +50,15 @@ module PaperTrail [:ignore, :skip, :only].each do |k| paper_trail_options[k] = - ([paper_trail_options[k]].flatten.compact || []).map &:to_s + ([paper_trail_options[k]].flatten.compact || []).map do |attr| + if attr.is_a? Hash + Hash[attr.map do |attr_name, condition| + [attr_name.to_s, condition] + end] + else + attr.to_s + end + end end paper_trail_options[:meta] ||= {} @@ -284,12 +296,30 @@ module PaperTrail end def notably_changed - only = self.class.paper_trail_options[:only] + only = [] + self.class.paper_trail_options[:only].each do |attr| + if attr.is_a? Hash + attr.each do |attr_name, condition| + only << attr_name if condition.respond_to?(:call) && condition.call(self) + end + else + only << attr + end + end only.empty? ? changed_and_not_ignored : (changed_and_not_ignored & only) end def changed_and_not_ignored - ignore = self.class.paper_trail_options[:ignore] + ignore = [] + self.class.paper_trail_options[:ignore].each do |attr| + if attr.is_a? Hash + attr.each do |attr_name, condition| + ignore << attr_name if condition.respond_to?(:call) && condition.call(self) + end + else + ignore << attr + end + end skip = self.class.paper_trail_options[:skip] changed - ignore - skip end diff --git a/test/dummy/app/models/article.rb b/test/dummy/app/models/article.rb index f69b9283..038a975d 100644 --- a/test/dummy/app/models/article.rb +++ b/test/dummy/app/models/article.rb @@ -1,6 +1,6 @@ class Article < ActiveRecord::Base - has_paper_trail :ignore => :title, - :only => [:content], + has_paper_trail :ignore => [:title, { :abstract => Proc.new { |obj| ['ignore abstract', 'Other abstract'].include? obj.abstract } }], + :only => [:content, { :abstract => Proc.new { |obj| obj.abstract.present? } }], :skip => [:file_upload], :meta => { :answer => 42, diff --git a/test/unit/model_test.rb b/test/unit/model_test.rb index efcbd14a..5fddec45 100644 --- a/test/unit/model_test.rb +++ b/test/unit/model_test.rb @@ -11,8 +11,21 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase should 'not change the number of versions' do assert_equal(1, PaperTrail::Version.count) end end - context 'which updates an ignored column and a selected column' do - setup { @article.update_attributes :title => 'My first title', :content => 'Some text here.' } + context 'which updates an ignored column with truly Proc' do + setup { @article.update_attributes :abstract => 'ignore abstract' } + should 'not change the number of versions' do assert_equal(1, PaperTrail::Version.count) end + end + + context 'which updates an ignored column with falsy Proc' do + setup { @article.update_attributes :abstract => 'do not ignore abstract!' } + should 'change the number of versions' do assert_equal(2, PaperTrail::Version.count) end + end + + context 'which updates an ignored column, ignored with truly Proc and a selected column' do + setup { @article.update_attributes :title => 'My first title', + :content => 'Some text here.', + :abstract => 'ignore abstract' + } should 'change the number of versions' do assert_equal(2, PaperTrail::Version.count) end should "show the new version in the model's `versions` association" do @@ -24,6 +37,22 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase end end + context 'which updates an ignored column, ignored with falsy Proc and a selected column' do + setup { @article.update_attributes :title => 'My first title', + :content => 'Some text here.', + :abstract => 'do not ignore abstract' + } + should 'change the number of versions' do assert_equal(2, PaperTrail::Version.count) end + + should "show the new version in the model's `versions` association" do + assert_equal(2, @article.versions.size) + end + + should 'have stored only non-ignored attributes' do + assert_equal ({'content' => [nil, 'Some text here.'], 'abstract' => [nil, 'do not ignore abstract']}), @article.versions.last.changeset + end + end + context 'which updates a selected column' do setup { @article.update_attributes :content => 'Some text here.' } should 'change the number of versions' do assert_equal(2, PaperTrail::Version.count) end