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:
Ben Atkins 2013-05-29 17:38:44 -04:00
commit bc97c6772c
13 changed files with 221 additions and 24 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,3 @@
module PaperTrail
VERSION = '2.7.1'
VERSION = '2.7.2'
end

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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