conditional tracking of only/ignore

This commit is contained in:
Vlad Bokov 2013-09-19 18:34:13 +07:00
parent 2c3f330521
commit 567f21b5a7
4 changed files with 79 additions and 12 deletions

View File

@ -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`.

View File

@ -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

View File

@ -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,

View File

@ -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