Merge branch 'master' into rails4
Conflicts: .travis.yml paper_trail.gemspec test/unit/model_test.rb test/unit/serializer_test.rb
This commit is contained in:
commit
bc97c6772c
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -1,3 +1,14 @@
|
|||
## 2.7.2
|
||||
|
||||
- [#228](https://github.com/airblade/paper_trail/issues/228) - Refactored default `user_for_paper_trail` method implementation
|
||||
so that `current_user` only gets invoked if it is defined.
|
||||
- [#219](https://github.com/airblade/paper_trail/pull/219) - Fixed issue where attributes stored with `nil` value might not get
|
||||
reified properly depending on the way the serializer worked.
|
||||
- [#213](https://github.com/airblade/paper_trail/issues/213) - Added a `version_limit` option to the `PaperTrail::Config` options
|
||||
that can be used to restrict the number of versions PaperTrail will store per object instance.
|
||||
- [#187](https://github.com/airblade/paper_trail/pull/187) - Confirmed JRuby support.
|
||||
- [#174](https://github.com/airblade/paper_trail/pull/174) - The `event` field on the versions table can now be customized.
|
||||
|
||||
## 2.7.1
|
||||
|
||||
- [#206](https://github.com/airblade/paper_trail/issues/206) - Fixed Ruby 1.8.7 compatibility for tracking `object_changes`.
|
||||
|
|
30
README.md
30
README.md
|
@ -193,6 +193,23 @@ class Article < ActiveRecord::Base
|
|||
end
|
||||
```
|
||||
|
||||
You may also have the `Version` model save a custom string in it's `event` field instead of the typical `create`, `update`, `destroy`.
|
||||
PaperTrail supplies a custom accessor method called `paper_trail_event`, which it will attempt to use to fill the `event` field before
|
||||
falling back on one of the default events.
|
||||
|
||||
```ruby
|
||||
>> a = Article.create
|
||||
>> a.versions.size # 1
|
||||
>> a.versions.last.event # 'create'
|
||||
>> a.paper_trail_event = 'update title'
|
||||
>> a.update_attributes :title => 'My Title'
|
||||
>> a.versions.size # 2
|
||||
>> a.versions.last.event # 'update title'
|
||||
>> a.paper_trail_event = nil
|
||||
>> a.update_attributes :title => "Alternate"
|
||||
>> a.versions.size # 3
|
||||
>> a.versions.last.event # 'update'
|
||||
```
|
||||
|
||||
## Choosing When To Save New Versions
|
||||
|
||||
|
@ -754,6 +771,19 @@ A valid serializer is a `module` (or `class`) that defines a `load` and `dump` m
|
|||
* [Yaml](https://github.com/airblade/paper_trail/blob/master/lib/paper_trail/serializers/yaml.rb) - Default
|
||||
* [Json](https://github.com/airblade/paper_trail/blob/master/lib/paper_trail/serializers/json.rb)
|
||||
|
||||
## Limiting the number of versions created per object instance
|
||||
|
||||
If you are weary of your `versions` table growing to an unwieldy size, or just don't care to track more than a certain number of versions per object,
|
||||
there is a configuration option that can be set to cap the number of versions saved per object. Note that this value must be numeric, and it only applies to
|
||||
versions other than `create` events (which will always be preserved if they are stored).
|
||||
|
||||
```ruby
|
||||
# will make it so that a maximum of 4 versions will be stored for each object (the 3 most recent ones plus a `create` event)
|
||||
>> PaperTrail.config.version_limit = 3
|
||||
# disables/removes the version limit
|
||||
>> PaperTrail.config.version_limit = nil
|
||||
```
|
||||
|
||||
## Deleting Old Versions
|
||||
|
||||
Over time your `versions` table will grow to an unwieldy size. Because each version is self-contained (see the Diffing section above for more) you can simply delete any records you don't want any more. For example:
|
||||
|
|
|
@ -3,7 +3,7 @@ require 'singleton'
|
|||
module PaperTrail
|
||||
class Config
|
||||
include Singleton
|
||||
attr_accessor :enabled, :timestamp_field, :serializer
|
||||
attr_accessor :enabled, :timestamp_field, :serializer, :version_limit
|
||||
|
||||
def initialize
|
||||
@enabled = true # Indicates whether PaperTrail is on or off.
|
||||
|
|
|
@ -14,7 +14,7 @@ module PaperTrail
|
|||
# Override this method in your controller to call a different
|
||||
# method, e.g. `current_person`, or anything you like.
|
||||
def user_for_paper_trail
|
||||
current_user rescue nil
|
||||
current_user if defined?(current_user)
|
||||
end
|
||||
|
||||
# Returns any information about the controller or request that you
|
||||
|
|
|
@ -57,6 +57,8 @@ module PaperTrail
|
|||
class_attribute :versions_association_name
|
||||
self.versions_association_name = options[:versions] || :versions
|
||||
|
||||
attr_accessor :paper_trail_event
|
||||
|
||||
has_many self.versions_association_name,
|
||||
lambda { |_model| order(PaperTrail.timestamp_field.to_sym => :asc, _model.class.version_key.to_sym => :asc) },
|
||||
:class_name => self.version_class_name, :as => :item
|
||||
|
@ -189,7 +191,7 @@ module PaperTrail
|
|||
def record_create
|
||||
if switched_on?
|
||||
data = {
|
||||
:event => 'create',
|
||||
:event => paper_trail_event || 'create',
|
||||
:whodunnit => PaperTrail.whodunnit
|
||||
}
|
||||
|
||||
|
@ -204,7 +206,7 @@ module PaperTrail
|
|||
def record_update
|
||||
if switched_on? && changed_notably?
|
||||
data = {
|
||||
:event => 'update',
|
||||
:event => paper_trail_event || 'update',
|
||||
:object => object_to_string(item_before_change),
|
||||
:whodunnit => PaperTrail.whodunnit
|
||||
}
|
||||
|
@ -227,7 +229,7 @@ module PaperTrail
|
|||
if switched_on? and not new_record?
|
||||
version_class.create merge_metadata(:item_id => self.id,
|
||||
:item_type => self.class.base_class.name,
|
||||
:event => 'destroy',
|
||||
:event => paper_trail_event || 'destroy',
|
||||
:object => object_to_string(item_before_change),
|
||||
:whodunnit => PaperTrail.whodunnit)
|
||||
end
|
||||
|
|
|
@ -2,6 +2,8 @@ class Version < ActiveRecord::Base
|
|||
belongs_to :item, :polymorphic => true
|
||||
validates_presence_of :event
|
||||
|
||||
after_create :enforce_version_limit!
|
||||
|
||||
def self.with_item_keys(item_type, item_id)
|
||||
where :item_type => item_type, :item_id => item_id
|
||||
end
|
||||
|
@ -18,6 +20,10 @@ class Version < ActiveRecord::Base
|
|||
where :event => 'destroy'
|
||||
end
|
||||
|
||||
def self.not_creates
|
||||
where 'event <> ?', 'create'
|
||||
end
|
||||
|
||||
scope :subsequent, lambda { |version|
|
||||
where("#{self.primary_key} > ?", version).order("#{self.primary_key} ASC")
|
||||
}
|
||||
|
@ -71,6 +77,8 @@ class Version < ActiveRecord::Base
|
|||
|
||||
if item
|
||||
model = item
|
||||
# Look for attributes that exist in the model and not in this version. These attributes should be set to nil.
|
||||
(model.attribute_names - attrs.keys).each { |k| attrs[k] = nil }
|
||||
else
|
||||
inheritance_column_name = item_type.constantize.inheritance_column
|
||||
class_name = attrs[inheritance_column_name].blank? ? item_type : attrs[inheritance_column_name]
|
||||
|
@ -79,6 +87,8 @@ class Version < ActiveRecord::Base
|
|||
end
|
||||
|
||||
model.class.unserialize_attributes_for_paper_trail attrs
|
||||
|
||||
# Set all the attributes in this version on the model
|
||||
attrs.each do |k, v|
|
||||
if model.respond_to?("#{k}=")
|
||||
model[k.to_sym] = v
|
||||
|
@ -175,4 +185,13 @@ class Version < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
# checks to see if a value has been set for the `version_limit` config option, and if so enforces it
|
||||
def enforce_version_limit!
|
||||
return unless PaperTrail.config.version_limit.is_a? Numeric
|
||||
previous_versions = sibling_versions.not_creates
|
||||
return unless previous_versions.size > PaperTrail.config.version_limit
|
||||
excess_previous_versions = previous_versions - previous_versions.last(PaperTrail.config.version_limit)
|
||||
excess_previous_versions.map(&:destroy)
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
module PaperTrail
|
||||
VERSION = '2.7.1'
|
||||
VERSION = '2.7.2'
|
||||
end
|
||||
|
|
|
@ -19,7 +19,14 @@ Gem::Specification.new do |s|
|
|||
s.add_dependency 'activerecord', '>= 4.0.0.beta', '< 5.0'
|
||||
|
||||
s.add_development_dependency 'rake'
|
||||
s.add_development_dependency 'sqlite3'
|
||||
s.add_development_dependency 'ffaker', '>= 1.15'
|
||||
s.add_development_dependency 'shoulda', '~> 3.3'
|
||||
s.add_development_dependency 'shoulda-matchers', '~> 1.5'
|
||||
s.add_development_dependency 'ffaker', '>= 1.15'
|
||||
s.add_development_dependency 'protected_attributes', '~> 1.0'
|
||||
# JRuby support for the test ENV
|
||||
unless defined?(JRUBY_VERSION)
|
||||
s.add_development_dependency 'sqlite3', '~> 1.2'
|
||||
else
|
||||
s.add_development_dependency 'activerecord-jdbcsqlite3-adapter', '~> 1.2.9'
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
# This custom serializer excludes nil values
|
||||
module CustomJsonSerializer
|
||||
extend PaperTrail::Serializers::Json
|
||||
|
||||
def self.load(string)
|
||||
parsed_value = super(string)
|
||||
parsed_value.is_a?(Hash) ? parsed_value.reject { |k,v| k.blank? || v.blank? } : parsed_value
|
||||
end
|
||||
|
||||
def self.dump(object)
|
||||
object.is_a?(Hash) ? super(object.reject { |k,v| v.nil? }) : super
|
||||
end
|
||||
end
|
|
@ -629,7 +629,7 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
|
|||
assert_equal 'Digit', @widget.version_at(1.day.from_now).name
|
||||
end
|
||||
|
||||
context 'passing in a string representation of a timestamp' do
|
||||
describe 'passing in a string representation of a timestamp' do
|
||||
should 'still return a widget when appropriate' do
|
||||
# need to add 1 second onto the timestamps before casting to a string, since casting a Time to a string drops the microseconds
|
||||
assert_equal 'Widget', @widget.version_at((@created + 1.second).to_s).name
|
||||
|
@ -891,7 +891,7 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
describe 'where the associated is created between model versions' do
|
||||
describe 'where the association is created between model versions' do
|
||||
setup do
|
||||
@wotsit = @widget.create_wotsit :name => 'wotsit_0'
|
||||
make_last_version_earlier @wotsit
|
||||
|
@ -1212,6 +1212,76 @@ class HasPaperTrailModelTest < ActiveSupport::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
describe 'custom events' do
|
||||
describe 'on create' do
|
||||
setup do
|
||||
Fluxor.reset_callbacks :create
|
||||
Fluxor.reset_callbacks :update
|
||||
Fluxor.reset_callbacks :destroy
|
||||
Fluxor.instance_eval <<-END
|
||||
has_paper_trail :on => [:create]
|
||||
END
|
||||
@fluxor = Fluxor.new.tap { |model| model.paper_trail_event = 'created' }
|
||||
@fluxor.update_attributes :name => 'blah'
|
||||
@fluxor.destroy
|
||||
end
|
||||
should 'only have a version for the created event' do
|
||||
assert_equal 1, @fluxor.versions.length
|
||||
assert_equal 'created', @fluxor.versions.last.event
|
||||
end
|
||||
end
|
||||
describe 'on update' do
|
||||
setup do
|
||||
Fluxor.reset_callbacks :create
|
||||
Fluxor.reset_callbacks :update
|
||||
Fluxor.reset_callbacks :destroy
|
||||
Fluxor.instance_eval <<-END
|
||||
has_paper_trail :on => [:update]
|
||||
END
|
||||
@fluxor = Fluxor.create.tap { |model| model.paper_trail_event = 'name_updated' }
|
||||
@fluxor.update_attributes :name => 'blah'
|
||||
@fluxor.destroy
|
||||
end
|
||||
should 'only have a version for the name_updated event' do
|
||||
assert_equal 1, @fluxor.versions.length
|
||||
assert_equal 'name_updated', @fluxor.versions.last.event
|
||||
end
|
||||
end
|
||||
describe 'on destroy' do
|
||||
setup do
|
||||
Fluxor.reset_callbacks :create
|
||||
Fluxor.reset_callbacks :update
|
||||
Fluxor.reset_callbacks :destroy
|
||||
Fluxor.instance_eval <<-END
|
||||
has_paper_trail :on => [:destroy]
|
||||
END
|
||||
@fluxor = Fluxor.create.tap { |model| model.paper_trail_event = 'destroyed' }
|
||||
@fluxor.update_attributes :name => 'blah'
|
||||
@fluxor.destroy
|
||||
end
|
||||
should 'only have a version for the destroy event' do
|
||||
assert_equal 1, @fluxor.versions.length
|
||||
assert_equal 'destroyed', @fluxor.versions.last.event
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '`PaperTrail::Config.version_limit` set' do
|
||||
setup do
|
||||
PaperTrail.config.version_limit = 2
|
||||
@widget = Widget.create! :name => 'Henry'
|
||||
6.times { @widget.update_attribute(:name, Faker::Lorem.word) }
|
||||
end
|
||||
|
||||
teardown { PaperTrail.config.version_limit = nil }
|
||||
|
||||
should "limit the number of versions to 3 (2 plus the created at event)" do
|
||||
assert_equal 'create', @widget.versions.first.event
|
||||
assert_equal 3, @widget.versions.size
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
# Updates `model`'s last version so it looks like the version was
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
require 'test_helper'
|
||||
require 'custom_json_serializer'
|
||||
|
||||
class SerializerTest < ActiveSupport::TestCase
|
||||
|
||||
|
@ -48,7 +49,7 @@ class SerializerTest < ActiveSupport::TestCase
|
|||
PaperTrail.config.serializer = PaperTrail::Serializers::Yaml
|
||||
end
|
||||
|
||||
should 'reify with custom serializer' do
|
||||
should 'reify with JSON serializer' do
|
||||
# Normal behaviour
|
||||
assert_equal 2, @fluxor.versions.length
|
||||
assert_nil @fluxor.versions[0].reify
|
||||
|
@ -71,4 +72,46 @@ class SerializerTest < ActiveSupport::TestCase
|
|||
end
|
||||
end
|
||||
|
||||
describe 'Custom Serializer' do
|
||||
setup do
|
||||
PaperTrail.configure do |config|
|
||||
config.serializer = CustomJsonSerializer
|
||||
end
|
||||
|
||||
Fluxor.instance_eval <<-END
|
||||
has_paper_trail
|
||||
END
|
||||
|
||||
@fluxor = Fluxor.create
|
||||
@original_fluxor_attributes = @fluxor.send(:item_before_change).attributes.reject { |k,v| v.nil? } # this is exactly what PaperTrail serializes
|
||||
@fluxor.update_attributes :name => 'Some more text.'
|
||||
end
|
||||
|
||||
teardown do
|
||||
PaperTrail.config.serializer = PaperTrail::Serializers::Yaml
|
||||
end
|
||||
|
||||
should 'reify with custom serializer' do
|
||||
# Normal behaviour
|
||||
assert_equal 2, @fluxor.versions.length
|
||||
assert_nil @fluxor.versions[0].reify
|
||||
assert_nil @fluxor.versions[1].reify.name
|
||||
|
||||
# Check values are stored as JSON.
|
||||
assert_equal @original_fluxor_attributes, ActiveSupport::JSON.decode(@fluxor.versions[1].object)
|
||||
# This test can't consistently pass in Ruby1.8 because hashes do no preserve order, which means the order of the
|
||||
# attributes in the JSON can't be ensured.
|
||||
if RUBY_VERSION.to_f >= 1.9
|
||||
assert_equal ActiveSupport::JSON.encode(@original_fluxor_attributes), @fluxor.versions[1].object
|
||||
end
|
||||
end
|
||||
|
||||
should 'store object_changes' do
|
||||
initial_changeset = {"id" => [nil, 1]}
|
||||
second_changeset = {"name"=>[nil, "Some more text."]}
|
||||
assert_equal initial_changeset, @fluxor.versions[0].changeset
|
||||
assert_equal second_changeset, @fluxor.versions[1].changeset
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -1,17 +1,5 @@
|
|||
require 'test_helper'
|
||||
|
||||
module CustomJsonSerializer
|
||||
extend PaperTrail::Serializers::Json
|
||||
|
||||
def self.load(string)
|
||||
parsed_value = super(string)
|
||||
parsed_value.is_a?(Hash) ? parsed_value.reject { |k,v| k.blank? || v.blank? } : parsed_value
|
||||
end
|
||||
|
||||
def self.dump(object)
|
||||
object.is_a?(Hash) ? super(object.reject { |k,v| v.nil? }) : super
|
||||
end
|
||||
end
|
||||
require 'custom_json_serializer'
|
||||
|
||||
class MixinJsonTest < ActiveSupport::TestCase
|
||||
|
||||
|
|
|
@ -40,4 +40,18 @@ class VersionTest < ActiveSupport::TestCase
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "Version.not_creates" do
|
||||
setup {
|
||||
@article.update_attributes(:name => 'Animal')
|
||||
@article.destroy
|
||||
assert Version.not_creates.present?
|
||||
}
|
||||
|
||||
should "return all items except create events" do
|
||||
Version.not_creates.each do |version|
|
||||
assert_not_equal "create", version.event
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue