paper_trail 4.0.0.beta2 → 4.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +9 -7
- data/CHANGELOG.md +38 -2
- data/README.md +30 -12
- data/Rakefile +1 -1
- data/gemfiles/3.0.gemfile +3 -1
- data/lib/generators/paper_trail/install_generator.rb +4 -1
- data/lib/paper_trail/cleaner.rb +1 -1
- data/lib/paper_trail/config.rb +6 -0
- data/lib/paper_trail/frameworks/sinatra.rb +1 -0
- data/lib/paper_trail/has_paper_trail.rb +80 -67
- data/lib/paper_trail/serializers/yaml.rb +2 -1
- data/lib/paper_trail/version_concern.rb +64 -19
- data/lib/paper_trail/version_number.rb +1 -1
- data/lib/paper_trail.rb +7 -3
- data/paper_trail.gemspec +2 -1
- data/spec/models/animal_spec.rb +19 -0
- data/spec/models/boolit_spec.rb +48 -0
- data/spec/models/json_version_spec.rb +80 -0
- data/spec/models/thing_spec.rb +11 -0
- data/spec/models/version_spec.rb +153 -78
- data/spec/models/widget_spec.rb +27 -6
- data/spec/modules/paper_trail_spec.rb +27 -0
- data/spec/requests/articles_spec.rb +0 -2
- data/test/dummy/app/models/animal.rb +2 -0
- data/test/dummy/app/models/boolit.rb +4 -0
- data/test/dummy/app/models/fruit.rb +5 -0
- data/test/dummy/app/models/song.rb +20 -0
- data/test/dummy/app/models/thing.rb +3 -0
- data/test/dummy/app/models/whatchamajigger.rb +4 -0
- data/test/dummy/app/models/widget.rb +1 -0
- data/test/dummy/app/versions/json_version.rb +3 -0
- data/test/dummy/config/initializers/paper_trail.rb +3 -0
- data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +44 -3
- data/test/dummy/db/schema.rb +13 -4
- data/test/functional/controller_test.rb +4 -2
- data/test/unit/model_test.rb +76 -12
- data/test/unit/serializer_test.rb +3 -3
- metadata +40 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c4a505b4f0b1cb4242e1a8eeb291fa5b95df54de
|
4
|
+
data.tar.gz: 26ef2a95bf0a15137d707b52b7a8f1aeff207b46
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 24bf40f194ddd24066e4ff0737eb35a603cb8b0e54b14060315a0d5ad57a98b53b8aaecbf7a07d69d65d3ab1d99ac6cead49b0396560a4d2f8545c3580d70041
|
7
|
+
data.tar.gz: f7efc50eab66db98cea15c876201ebe5773c7b5f64c0320f98743b607819a1f8084438096e9e3849b1b233d3b0c2e9f86114be07e8de72d09f18ed24c04175f4
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -6,9 +6,12 @@ rvm:
|
|
6
6
|
- jruby-19mode
|
7
7
|
- jruby-18mode
|
8
8
|
env:
|
9
|
-
|
10
|
-
|
11
|
-
|
9
|
+
global:
|
10
|
+
- TRAVIS=true
|
11
|
+
matrix:
|
12
|
+
- DB=mysql
|
13
|
+
- DB=postgres
|
14
|
+
- DB=sqlite
|
12
15
|
|
13
16
|
sudo: false
|
14
17
|
|
@@ -27,11 +30,10 @@ gemfile:
|
|
27
30
|
matrix:
|
28
31
|
fast_finish: true
|
29
32
|
allow_failures:
|
30
|
-
- rvm: jruby-19mode
|
31
|
-
env: DB=postgres
|
32
|
-
gemfile: Gemfile
|
33
33
|
- rvm: jruby-18mode
|
34
34
|
gemfile: Gemfile
|
35
35
|
- rvm: 1.8.7
|
36
36
|
gemfile: Gemfile
|
37
|
-
|
37
|
+
|
38
|
+
addons:
|
39
|
+
postgresql: "9.4"
|
data/CHANGELOG.md
CHANGED
@@ -9,13 +9,35 @@ PaperTrail::Rails::Engine.eager_load!
|
|
9
9
|
|
10
10
|
If you depend on the `RSpec` or `Cucumber` helpers, you will need to [manually load them into your test helper](https://github.com/airblade/paper_trail#testing).
|
11
11
|
|
12
|
+
- [#525](https://github.com/airblade/paper_trail/issues/525) / [#512](https://github.com/airblade/paper_trail/pull/512) -
|
13
|
+
Support for virtual accessors and redefined setter and getter methods.
|
14
|
+
- [#518](https://github.com/airblade/paper_trail/pull/518) - Support for querying against PostgreSQL's
|
15
|
+
[`JSON` and `JSONB` column types](http://www.postgresql.org/docs/9.4/static/datatype-json.html) via
|
16
|
+
`PaperTrail::VersionConcern#where_object` and `PaperTrail::VersionConcern#where_object_changes`
|
17
|
+
- [#507](https://github.com/airblade/paper_trail/pull/507) - Support for opting out of saving changesets on models by choice
|
18
|
+
when the `object_changes` column exists on the default `versions` table.
|
19
|
+
- [#500](https://github.com/airblade/paper_trail/pull/500) - Support for passing `on: []` as an argument, with only manual
|
20
|
+
versioning via calls to `touch_with_version`
|
21
|
+
- [#494](https://github.com/airblade/paper_trail/issues/494) - The install generator will warn the user if the migration they are
|
22
|
+
attempting to generate already exists.
|
23
|
+
- [#484](https://github.com/airblade/paper_trail/pull/484) - Support for
|
24
|
+
[PostgreSQL's `JSONB` Type](http://www.postgresql.org/docs/9.4/static/datatype-json.html) for storing `object`
|
25
|
+
and `object_changes`.
|
26
|
+
- [#479](https://github.com/airblade/paper_trail/issues/479) - Deprecated `originator` method in favor of `paper_trail_originator`
|
27
|
+
Deprecation warning informs users that the `originator` of the methods will be removed in version `4.0`
|
12
28
|
- [#458](https://github.com/airblade/paper_trail/pull/458) - For `create` events, metadata pointing at attributes should attempt
|
13
29
|
to grab the current value instead of looking at the value prior to the change (which would always be `nil`)
|
30
|
+
- [#451](https://github.com/airblade/paper_trail/issues/451) - Fix `reify` method in context of model where the base class
|
31
|
+
has a default scope, and the live instance is not scoped within that default scope
|
14
32
|
- [#440](https://github.com/airblade/paper_trail/pull/440) - `versions` association should clear/reload after a transaction rollback.
|
15
33
|
- [#439](https://github.com/airblade/paper_trail/pull/439) / [#12](https://github.com/airblade/paper_trail/issues/12) -
|
16
34
|
Support for versioning of associations (Has Many, Has One, HABTM, etc.)
|
17
|
-
- [#438](https://github.com/airblade/paper_trail/issues/438) - `
|
35
|
+
- [#438](https://github.com/airblade/paper_trail/issues/438) - `ModelKlass.paper_trail_enabled_for_model?` should return `false` if
|
18
36
|
`has_paper_trail` has not been declared on the class.
|
37
|
+
- [#404](https://github.com/airblade/paper_trail/issues/404) / [#428](https://github.com/airblade/paper_trail/issues/428) -
|
38
|
+
`model_instance.dup` does not need to be invoked when examining what the instance looked like before changes were persisted,
|
39
|
+
which avoids issues if a 3rd party has overriden the `dup` behavior. Also fixes errors occuring when a user attempts to
|
40
|
+
update the inheritance column on an STI model instance in `ActiveRecord` 4.1.x
|
19
41
|
- [#427](https://github.com/airblade/paper_trail/pull/427) - Fix `reify` method in context of model where a column has been removed.
|
20
42
|
- [#420](https://github.com/airblade/paper_trail/issues/420) - Add `VersionConcern#where_object_changes` instance method;
|
21
43
|
acts as a helper for querying against the `object_changes` column in versions table.
|
@@ -24,6 +46,8 @@ If you depend on the `RSpec` or `Cucumber` helpers, you will need to [manually l
|
|
24
46
|
deprecated in `ActiveRecord` version `4.2` and will be removed in version `5.0`
|
25
47
|
- [#414](https://github.com/airblade/paper_trail/issues/414) - Fix functionality `ignore` argument to `has_paper_trail`
|
26
48
|
in `ActiveRecord` 4.
|
49
|
+
- [#413](https://github.com/airblade/paper_trail/issues/413) - Utilize [RequestStore](https://github.com/steveklabnik/request_store)
|
50
|
+
to ensure that the `PaperTrail.whodunnit` is set in a thread safe manner within Rails & Sinatra.
|
27
51
|
- [#399](https://github.com/airblade/paper_trail/pull/399) - Add `:dup` argument for options hash to `reify` which forces a new model instance.
|
28
52
|
- [#394](https://github.com/airblade/paper_trail/pull/394) - Add RSpec matcher `have_a_version_with` for easier testing.
|
29
53
|
- [#391](https://github.com/airblade/paper_trail/issues/391) - `object_changes` value should dump to `YAML` as a normal `Hash`
|
@@ -38,6 +62,18 @@ If you depend on the `RSpec` or `Cucumber` helpers, you will need to [manually l
|
|
38
62
|
the gem is used with `Rails`.
|
39
63
|
- Methods handling serialized attributes should fallback to the currently set Serializer instead of always falling back
|
40
64
|
to `PaperTrail::Serializers::YAML`.
|
65
|
+
- Both `PaperTrail.config` and `PaperTrail.configure` are now identical, and will both return the `PaperTrail::Config`
|
66
|
+
instance and also yield it if a block is provided.
|
67
|
+
|
68
|
+
## 3.0.8
|
69
|
+
|
70
|
+
- [#525](https://github.com/airblade/paper_trail/issues/525) / [#512](https://github.com/airblade/paper_trail/pull/512) -
|
71
|
+
Support for virtual accessors and redefined setter and getter methods.
|
72
|
+
|
73
|
+
## 3.0.7
|
74
|
+
|
75
|
+
- [#404](https://github.com/airblade/paper_trail/issues/404) / [#428](https://github.com/airblade/paper_trail/issues/428) -
|
76
|
+
Fix errors occuring when a user attempts to update the inheritance column on an STI model instance in `ActiveRecord` 4.1.x
|
41
77
|
|
42
78
|
## 3.0.6
|
43
79
|
|
@@ -46,7 +82,7 @@ If you depend on the `RSpec` or `Cucumber` helpers, you will need to [manually l
|
|
46
82
|
|
47
83
|
## 3.0.5
|
48
84
|
|
49
|
-
- [#401](https://github.com/airblade/paper_trail/issues/401) / [#406](https://github.com/airblade/paper_trail/issues/406)
|
85
|
+
- [#401](https://github.com/airblade/paper_trail/issues/401) / [#406](https://github.com/airblade/paper_trail/issues/406) -
|
50
86
|
`PaperTrail::Version` class is not loaded via a `Rails::Engine`, even when the gem is used with in Rails. This feature has
|
51
87
|
will be re-introduced in version `4.0`.
|
52
88
|
- [#398](https://github.com/airblade/paper_trail/pull/398) - Only require the `RSpec` helper if `RSpec::Core` is required.
|
data/README.md
CHANGED
@@ -44,7 +44,7 @@ The Rails 2.3 code is on the [`rails2`](https://github.com/airblade/paper_trail/
|
|
44
44
|
|
45
45
|
1. Add PaperTrail to your `Gemfile`.
|
46
46
|
|
47
|
-
`gem 'paper_trail', '~>
|
47
|
+
`gem 'paper_trail', '~> 4.0.0.rc'`
|
48
48
|
|
49
49
|
2. Generate a migration which will add a `versions` table to your database.
|
50
50
|
|
@@ -66,7 +66,7 @@ your applications `ActiveRecord` connection in a manner similar to the way `Rail
|
|
66
66
|
|
67
67
|
1. Add PaperTrail to your `Gemfile`.
|
68
68
|
|
69
|
-
`gem 'paper_trail', '~>
|
69
|
+
`gem 'paper_trail', '~> 4.0.0.rc'`
|
70
70
|
|
71
71
|
2. Generate a migration to add a `versions` table to your database.
|
72
72
|
|
@@ -117,7 +117,7 @@ widget.version
|
|
117
117
|
widget.live?
|
118
118
|
|
119
119
|
# Returns who put the widget into its current state.
|
120
|
-
widget.
|
120
|
+
widget.paper_trail_originator
|
121
121
|
|
122
122
|
# Returns the widget (not a version) as it looked at the given timestamp.
|
123
123
|
widget.version_at(timestamp)
|
@@ -152,7 +152,7 @@ version.reify(options = {})
|
|
152
152
|
version.reify(dup: true)
|
153
153
|
|
154
154
|
# Returns who put the item into the state stored in this version.
|
155
|
-
version.
|
155
|
+
version.paper_trail_originator
|
156
156
|
|
157
157
|
# Returns who changed the item from the state it had in this version.
|
158
158
|
version.terminator
|
@@ -473,7 +473,7 @@ And you can perform `WHERE` queries for object versions based on attributes:
|
|
473
473
|
|
474
474
|
## Finding Out Who Was Responsible For A Change
|
475
475
|
|
476
|
-
If your `ApplicationController` has a `current_user` method, PaperTrail will store the value
|
476
|
+
If your `ApplicationController` has a `current_user` method, PaperTrail will attempt to store the value returned by `current_user.id` in the version's `whodunnit` column (if it returns). Note that this column is of type `String`, so you will have to convert it to an integer if it's an id and you want to look up the user later on:
|
477
477
|
|
478
478
|
```ruby
|
479
479
|
>> last_change = widget.versions.last
|
@@ -529,22 +529,22 @@ Sometimes you want to define who is responsible for a change in a small scope wi
|
|
529
529
|
|
530
530
|
A version's `whodunnit` records who changed the object causing the `version` to be stored. Because a version stores the object as it looked before the change (see the table above), `whodunnit` returns who stopped the object looking like this -- not who made it look like this. Hence `whodunnit` is aliased as `terminator`.
|
531
531
|
|
532
|
-
To find out who made a version's object look that way, use `version.
|
532
|
+
To find out who made a version's object look that way, use `version.paper_trail_originator`. And to find out who made a "live" object look like it does, call `paper_trail_originator` on the object.
|
533
533
|
|
534
534
|
```ruby
|
535
535
|
>> widget = Widget.find 153 # assume widget has 0 versions
|
536
536
|
>> PaperTrail.whodunnit = 'Alice'
|
537
537
|
>> widget.update_attributes :name => 'Yankee'
|
538
|
-
>> widget
|
538
|
+
>> widget..paper_trail_originator # 'Alice'
|
539
539
|
>> PaperTrail.whodunnit = 'Bob'
|
540
540
|
>> widget.update_attributes :name => 'Zulu'
|
541
|
-
>> widget.
|
541
|
+
>> widget.paper_trail_originator # 'Bob'
|
542
542
|
>> first_version, last_version = widget.versions.first, widget.versions.last
|
543
543
|
>> first_version.whodunnit # 'Alice'
|
544
|
-
>> first_version.
|
544
|
+
>> first_version.paper_trail_originator # nil
|
545
545
|
>> first_version.terminator # 'Alice'
|
546
546
|
>> last_version.whodunnit # 'Bob'
|
547
|
-
>> last_version.
|
547
|
+
>> last_version.paper_trail_originator # 'Alice'
|
548
548
|
>> last_version.terminator # 'Bob'
|
549
549
|
```
|
550
550
|
|
@@ -570,7 +570,7 @@ If you are using Postgres, you should also define the sequence that your custom
|
|
570
570
|
```ruby
|
571
571
|
class PostVersion < PaperTrail::Version
|
572
572
|
self.table_name = :post_versions
|
573
|
-
self.sequence_name = :
|
573
|
+
self.sequence_name = :post_versions_id_seq
|
574
574
|
end
|
575
575
|
```
|
576
576
|
|
@@ -655,7 +655,7 @@ If the parent and child are updated in one go, PaperTrail can use the aforementi
|
|
655
655
|
>> t.location.latitude # 12.345, instead of 54.321
|
656
656
|
```
|
657
657
|
|
658
|
-
By default, PaperTrail excludes an associated record from the reified parent model if the associated record exists in the live model but did not exist as at the time the version was created. This is usually what you want if you just want to look at the reified version. But if you want to persist it, it would be better to pass in option `:mark_for_destruction => true` so that the associated record is included and marked for destruction.
|
658
|
+
By default, PaperTrail excludes an associated record from the reified parent model if the associated record exists in the live model but did not exist as at the time the version was created. This is usually what you want if you just want to look at the reified version. But if you want to persist it, it would be better to pass in option `:mark_for_destruction => true` so that the associated record is included and marked for destruction. Note that `mark_for_destruction` only has [an effect on associations marked with `autosave: true`](http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html#method-i-mark_for_destruction).
|
659
659
|
|
660
660
|
```ruby
|
661
661
|
class Widget < ActiveRecord::Base
|
@@ -844,6 +844,7 @@ For diffing two ActiveRecord objects:
|
|
844
844
|
* [Jeremy Weiskotten's PaperTrail fork](http://github.com/jeremyw/paper_trail/blob/master/lib/paper_trail/has_paper_trail.rb#L151-156): uses ActiveSupport's diff to return an array of hashes of the changes.
|
845
845
|
* [activerecord-diff](http://github.com/tim/activerecord-diff): rather like ActiveRecord::Dirty but also allows you to specify which columns to compare.
|
846
846
|
|
847
|
+
If you wish to selectively record changes for some models but not others you can opt out of recording changes by passing `:save_changes => false` to your `has_paper_trail` method declaration.
|
847
848
|
|
848
849
|
## Turning PaperTrail Off/On
|
849
850
|
|
@@ -951,6 +952,23 @@ A valid serializer is a `module` (or `class`) that defines a `load` and `dump` m
|
|
951
952
|
* [PaperTrail::Serializers::YAML](https://github.com/airblade/paper_trail/blob/master/lib/paper_trail/serializers/yaml.rb) - Default
|
952
953
|
* [PaperTrail::Serializers::JSON](https://github.com/airblade/paper_trail/blob/master/lib/paper_trail/serializers/json.rb)
|
953
954
|
|
955
|
+
### PostgreSQL JSON column type support
|
956
|
+
|
957
|
+
If you use PostgreSQL, and would like to store your `object` (and/or `object_changes`) data in a column of
|
958
|
+
[type `JSON` or type `JSONB`](http://www.postgresql.org/docs/9.4/static/datatype-json.html),
|
959
|
+
specify `json` instead of `text` for these columns in your migration:
|
960
|
+
|
961
|
+
```ruby
|
962
|
+
create_table :versions do |t|
|
963
|
+
...
|
964
|
+
t.json :object # Full object changes
|
965
|
+
t.json :object_changes # Optional column-level changes
|
966
|
+
...
|
967
|
+
end
|
968
|
+
```
|
969
|
+
|
970
|
+
Note: You don't need to use a particular serializer for the PostgreSQL `JSON` column type.
|
971
|
+
|
954
972
|
## SerializedAttributes support
|
955
973
|
|
956
974
|
PaperTrail has a config option that can be used to enable/disable whether PaperTrail attempts to utilize
|
data/Rakefile
CHANGED
@@ -23,7 +23,7 @@ Rake::TestTask.new(:test) do |t|
|
|
23
23
|
end
|
24
24
|
|
25
25
|
require 'rspec/core/rake_task'
|
26
|
-
desc 'Run
|
26
|
+
desc 'Run tests on PaperTrail with RSpec'
|
27
27
|
RSpec::Core::RakeTask.new(:spec)
|
28
28
|
|
29
29
|
desc 'Default: run all available test suites'
|
data/gemfiles/3.0.gemfile
CHANGED
@@ -2,11 +2,12 @@ source 'https://rubygems.org'
|
|
2
2
|
|
3
3
|
gem 'activerecord', '~> 3.0'
|
4
4
|
gem 'i18n', '~> 0.6.11'
|
5
|
+
gem 'request_store', '~> 1.1.0'
|
5
6
|
|
6
7
|
group :development, :test do
|
7
8
|
gem 'rake', '~> 10.1.1'
|
8
9
|
gem 'shoulda', '~> 3.5'
|
9
|
-
gem 'ffaker', '
|
10
|
+
gem 'ffaker', '<= 1.31.0'
|
10
11
|
|
11
12
|
# Testing of Rails
|
12
13
|
gem 'railties', '~> 3.0'
|
@@ -46,5 +47,6 @@ group :development, :test do
|
|
46
47
|
gem 'activerecord-jdbcsqlite3-adapter', '~> 1.3'
|
47
48
|
gem 'activerecord-jdbcpostgresql-adapter', '~> 1.3'
|
48
49
|
gem 'activerecord-jdbcmysql-adapter', '~> 1.3'
|
50
|
+
gem 'activerecord-jdbc-adapter', '1.3.15'
|
49
51
|
end
|
50
52
|
end
|
@@ -30,8 +30,11 @@ module PaperTrail
|
|
30
30
|
def add_paper_trail_migration(template)
|
31
31
|
migration_dir = File.expand_path('db/migrate')
|
32
32
|
|
33
|
-
|
33
|
+
unless self.class.migration_exists?(migration_dir, template)
|
34
34
|
migration_template "#{template}.rb", "db/migrate/#{template}.rb"
|
35
|
+
else
|
36
|
+
warn("ALERT: Migration already exists named '#{template}'." +
|
37
|
+
" Please check your migrations directory before re-running")
|
35
38
|
end
|
36
39
|
end
|
37
40
|
end
|
data/lib/paper_trail/cleaner.rb
CHANGED
@@ -25,7 +25,7 @@ module PaperTrail
|
|
25
25
|
# Returns a hash of versions grouped by the `item_id` attribute formatted like this: {:item_id => PaperTrail::Version}.
|
26
26
|
# If `item_id` or `date` is set, versions will be narrowed to those pointing at items with those ids that were created on specified date.
|
27
27
|
def gather_versions(item_id = nil, date = :all)
|
28
|
-
raise "`date` argument must receive a Timestamp or `:all`" unless date == :all || date.respond_to?(:to_date)
|
28
|
+
raise ArgumentError.new("`date` argument must receive a Timestamp or `:all`") unless date == :all || date.respond_to?(:to_date)
|
29
29
|
versions = item_id ? PaperTrail::Version.where(:item_id => item_id) : PaperTrail::Version
|
30
30
|
versions = versions.between(date.to_date, date.to_date + 1.day) unless date == :all
|
31
31
|
versions = PaperTrail::Version.all if versions == PaperTrail::Version # if versions has not been converted to an ActiveRecord::Relation yet, do so now
|
data/lib/paper_trail/config.rb
CHANGED
@@ -5,6 +5,7 @@ module PaperTrail
|
|
5
5
|
include Singleton
|
6
6
|
attr_accessor :enabled, :timestamp_field, :serializer, :version_limit
|
7
7
|
attr_reader :serialized_attributes
|
8
|
+
attr_writer :track_associations
|
8
9
|
|
9
10
|
def initialize
|
10
11
|
@enabled = true # Indicates whether PaperTrail is on or off.
|
@@ -27,5 +28,10 @@ module PaperTrail
|
|
27
28
|
end
|
28
29
|
@serialized_attributes = value
|
29
30
|
end
|
31
|
+
|
32
|
+
def track_associations
|
33
|
+
@track_associations ||= PaperTrail::VersionAssociation.table_exists?
|
34
|
+
end
|
35
|
+
alias_method :track_associations?, :track_associations
|
30
36
|
end
|
31
37
|
end
|
@@ -5,6 +5,7 @@ module PaperTrail
|
|
5
5
|
|
6
6
|
# Register this module inside your Sinatra application to gain access to controller-level methods used by PaperTrail
|
7
7
|
def self.registered(app)
|
8
|
+
app.use RequestStore::Middleware
|
8
9
|
app.helpers self
|
9
10
|
app.before { set_paper_trail_whodunnit }
|
10
11
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'active_support/core_ext/object' # provides the `try` method
|
2
|
+
|
1
3
|
module PaperTrail
|
2
4
|
module Model
|
3
5
|
|
@@ -10,26 +12,28 @@ module PaperTrail
|
|
10
12
|
# the model is available in the `versions` association.
|
11
13
|
#
|
12
14
|
# Options:
|
13
|
-
# :on
|
14
|
-
#
|
15
|
-
# :class_name
|
16
|
-
# :ignore
|
17
|
-
#
|
18
|
-
#
|
19
|
-
# :if, :unless
|
20
|
-
# :only
|
21
|
-
#
|
22
|
-
#
|
23
|
-
# :skip
|
24
|
-
#
|
25
|
-
#
|
26
|
-
# :meta
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
# :versions
|
31
|
-
# :version
|
32
|
-
#
|
15
|
+
# :on the events to track (optional; defaults to all of them). Set to an array of
|
16
|
+
# `:create`, `:update`, `:destroy` as desired.
|
17
|
+
# :class_name the name of a custom Version class. This class should inherit from `PaperTrail::Version`.
|
18
|
+
# :ignore an array of attributes for which a new `Version` will not be created if only they change.
|
19
|
+
# it can also aceept a Hash as an argument where the key is the attribute to ignore (a `String` or `Symbol`),
|
20
|
+
# which will only be ignored if the value is a `Proc` which returns truthily.
|
21
|
+
# :if, :unless Procs that allow to specify conditions when to save versions for an object
|
22
|
+
# :only inverse of `ignore` - a new `Version` will be created only for these attributes if supplied
|
23
|
+
# it can also aceept a Hash as an argument where the key is the attribute to track (a `String` or `Symbol`),
|
24
|
+
# which will only be counted if the value is a `Proc` which returns truthily.
|
25
|
+
# :skip fields to ignore completely. As with `ignore`, updates to these fields will not create
|
26
|
+
# a new `Version`. In addition, these fields will not be included in the serialized versions
|
27
|
+
# of the object whenever a new `Version` is created.
|
28
|
+
# :meta a hash of extra data to store. You must add a column to the `versions` table for each key.
|
29
|
+
# Values are objects or procs (which are called with `self`, i.e. the model with the paper
|
30
|
+
# trail). See `PaperTrail::Controller.info_for_paper_trail` for how to store data from
|
31
|
+
# the controller.
|
32
|
+
# :versions the name to use for the versions association. Default is `:versions`.
|
33
|
+
# :version the name to use for the method which returns the version the instance was reified from.
|
34
|
+
# Default is `:version`.
|
35
|
+
# :save_changes whether or not to save changes to the object_changes column if it exists. Default is true
|
36
|
+
#
|
33
37
|
def has_paper_trail(options = {})
|
34
38
|
# Lazily include the instance methods so we don't clutter up
|
35
39
|
# any more ActiveRecord models than we have to.
|
@@ -53,6 +57,7 @@ module PaperTrail
|
|
53
57
|
end
|
54
58
|
|
55
59
|
paper_trail_options[:meta] ||= {}
|
60
|
+
paper_trail_options[:save_changes] = true if paper_trail_options[:save_changes].nil?
|
56
61
|
|
57
62
|
class_attribute :versions_association_name
|
58
63
|
self.versions_association_name = options[:versions] || :versions
|
@@ -70,14 +75,15 @@ module PaperTrail
|
|
70
75
|
:order => self.paper_trail_version_class.timestamp_sort_order
|
71
76
|
end
|
72
77
|
|
78
|
+
options[:on] ||= [:create, :update, :destroy]
|
73
79
|
options_on = Array(options[:on]) # so that a single symbol can be passed in without wrapping it in an `Array`
|
74
|
-
after_create :record_create, :if => :save_version? if options_on.
|
75
|
-
if options_on.
|
80
|
+
after_create :record_create, :if => :save_version? if options_on.include?(:create)
|
81
|
+
if options_on.include?(:update)
|
76
82
|
before_save :reset_timestamp_attrs_for_update_if_needed!, :on => :update
|
77
83
|
after_update :record_update, :if => :save_version?
|
78
84
|
after_update :clear_version_instance!
|
79
85
|
end
|
80
|
-
after_destroy :record_destroy, :if => :save_version? if options_on.
|
86
|
+
after_destroy :record_destroy, :if => :save_version? if options_on.include?(:destroy)
|
81
87
|
|
82
88
|
# Reset the transaction id when the transaction is closed
|
83
89
|
after_commit :reset_transaction_id
|
@@ -115,7 +121,7 @@ module PaperTrail
|
|
115
121
|
end
|
116
122
|
|
117
123
|
# Used for Version#object attribute
|
118
|
-
def serialize_attributes_for_paper_trail(attributes)
|
124
|
+
def serialize_attributes_for_paper_trail!(attributes)
|
119
125
|
# don't serialize before values before inserting into columns of type `JSON` on `PostgreSQL` databases
|
120
126
|
return attributes if self.paper_trail_version_class.object_col_is_json?
|
121
127
|
|
@@ -128,7 +134,7 @@ module PaperTrail
|
|
128
134
|
end
|
129
135
|
end
|
130
136
|
|
131
|
-
def unserialize_attributes_for_paper_trail(attributes)
|
137
|
+
def unserialize_attributes_for_paper_trail!(attributes)
|
132
138
|
# don't serialize before values before inserting into columns of type `JSON` on `PostgreSQL` databases
|
133
139
|
return attributes if self.paper_trail_version_class.object_col_is_json?
|
134
140
|
|
@@ -141,7 +147,7 @@ module PaperTrail
|
|
141
147
|
end
|
142
148
|
|
143
149
|
# Used for Version#object_changes attribute
|
144
|
-
def
|
150
|
+
def serialize_attribute_changes_for_paper_trail!(changes)
|
145
151
|
# don't serialize before values before inserting into columns of type `JSON` on `PostgreSQL` databases
|
146
152
|
return changes if self.paper_trail_version_class.object_changes_col_is_json?
|
147
153
|
|
@@ -156,7 +162,7 @@ module PaperTrail
|
|
156
162
|
end
|
157
163
|
end
|
158
164
|
|
159
|
-
def
|
165
|
+
def unserialize_attribute_changes_for_paper_trail!(changes)
|
160
166
|
# don't serialize before values before inserting into columns of type `JSON` on `PostgreSQL` databases
|
161
167
|
return changes if self.paper_trail_version_class.object_changes_col_is_json?
|
162
168
|
|
@@ -181,10 +187,15 @@ module PaperTrail
|
|
181
187
|
end
|
182
188
|
|
183
189
|
# Returns who put the object into its current state.
|
184
|
-
def
|
190
|
+
def paper_trail_originator
|
185
191
|
(source_version || send(self.class.versions_association_name).last).try(:whodunnit)
|
186
192
|
end
|
187
193
|
|
194
|
+
def originator
|
195
|
+
warn "DEPRECATED: use `paper_trail_originator` instead of `originator`. Support for `originator` will be removed in PaperTrail 4.0"
|
196
|
+
self.paper_trail_originator
|
197
|
+
end
|
198
|
+
|
188
199
|
# Invoked after rollbacks to ensure versions records are not created
|
189
200
|
# for changes that never actually took place
|
190
201
|
def clear_rolled_back_versions
|
@@ -268,7 +279,9 @@ module PaperTrail
|
|
268
279
|
current_time = current_time_from_proper_timezone
|
269
280
|
|
270
281
|
attributes.each { |column| write_attribute(column, current_time) }
|
271
|
-
|
282
|
+
# ensure a version is written even if the `:on` collection is empty
|
283
|
+
record_update(true) if paper_trail_options[:on] == []
|
284
|
+
save!(:validate => false)
|
272
285
|
end
|
273
286
|
|
274
287
|
private
|
@@ -280,13 +293,13 @@ module PaperTrail
|
|
280
293
|
def record_create
|
281
294
|
if paper_trail_switched_on?
|
282
295
|
data = {
|
283
|
-
:event
|
284
|
-
:whodunnit
|
296
|
+
:event => paper_trail_event || 'create',
|
297
|
+
:whodunnit => PaperTrail.whodunnit
|
285
298
|
}
|
286
299
|
if respond_to?(:created_at)
|
287
300
|
data[PaperTrail.timestamp_field] = created_at
|
288
301
|
end
|
289
|
-
if changed_notably?
|
302
|
+
if paper_trail_options[:save_changes] && changed_notably? && self.class.paper_trail_version_class.column_names.include?('object_changes')
|
290
303
|
data[:object_changes] = self.class.paper_trail_version_class.object_changes_col_is_json? ? changes_for_paper_trail :
|
291
304
|
PaperTrail.serializer.dump(changes_for_paper_trail)
|
292
305
|
end
|
@@ -299,18 +312,18 @@ module PaperTrail
|
|
299
312
|
end
|
300
313
|
end
|
301
314
|
|
302
|
-
def record_update
|
303
|
-
if paper_trail_switched_on? && changed_notably?
|
304
|
-
object_attrs = object_attrs_for_paper_trail(
|
315
|
+
def record_update(force = nil)
|
316
|
+
if paper_trail_switched_on? && (force || changed_notably?)
|
317
|
+
object_attrs = object_attrs_for_paper_trail(attributes_before_change)
|
305
318
|
data = {
|
306
|
-
:event
|
307
|
-
:object
|
308
|
-
:whodunnit
|
319
|
+
:event => paper_trail_event || 'update',
|
320
|
+
:object => self.class.paper_trail_version_class.object_col_is_json? ? object_attrs : PaperTrail.serializer.dump(object_attrs),
|
321
|
+
:whodunnit => PaperTrail.whodunnit
|
309
322
|
}
|
310
323
|
if respond_to?(:updated_at)
|
311
324
|
data[PaperTrail.timestamp_field] = updated_at
|
312
325
|
end
|
313
|
-
if self.class.paper_trail_version_class.column_names.include?('object_changes')
|
326
|
+
if paper_trail_options[:save_changes] && self.class.paper_trail_version_class.column_names.include?('object_changes')
|
314
327
|
data[:object_changes] = self.class.paper_trail_version_class.object_changes_col_is_json? ? changes_for_paper_trail :
|
315
328
|
PaperTrail.serializer.dump(changes_for_paper_trail)
|
316
329
|
end
|
@@ -326,7 +339,7 @@ module PaperTrail
|
|
326
339
|
def changes_for_paper_trail
|
327
340
|
_changes = changes.delete_if { |k,v| !notably_changed.include?(k) }
|
328
341
|
if PaperTrail.serialized_attributes?
|
329
|
-
self.class.
|
342
|
+
self.class.serialize_attribute_changes_for_paper_trail!(_changes)
|
330
343
|
end
|
331
344
|
_changes.to_hash
|
332
345
|
end
|
@@ -350,13 +363,13 @@ module PaperTrail
|
|
350
363
|
|
351
364
|
def record_destroy
|
352
365
|
if paper_trail_switched_on? and not new_record?
|
353
|
-
object_attrs = object_attrs_for_paper_trail(
|
366
|
+
object_attrs = object_attrs_for_paper_trail(attributes_before_change)
|
354
367
|
data = {
|
355
|
-
:item_id
|
356
|
-
:item_type
|
357
|
-
:event
|
358
|
-
:object
|
359
|
-
:whodunnit
|
368
|
+
:item_id => self.id,
|
369
|
+
:item_type => self.class.base_class.name,
|
370
|
+
:event => paper_trail_event || 'destroy',
|
371
|
+
:object => self.class.paper_trail_version_class.object_col_is_json? ? object_attrs : PaperTrail.serializer.dump(object_attrs),
|
372
|
+
:whodunnit => PaperTrail.whodunnit
|
360
373
|
}
|
361
374
|
if self.class.paper_trail_version_class.column_names.include?('transaction_id')
|
362
375
|
data[:transaction_id] = PaperTrail.transaction_id
|
@@ -371,15 +384,23 @@ module PaperTrail
|
|
371
384
|
|
372
385
|
# saves associations if the join table for `VersionAssociation` exists
|
373
386
|
def save_associations(version)
|
374
|
-
return unless PaperTrail
|
387
|
+
return unless PaperTrail.config.track_associations?
|
375
388
|
self.class.reflect_on_all_associations(:belongs_to).each do |assoc|
|
376
|
-
|
377
|
-
PaperTrail::VersionAssociation.create(
|
389
|
+
assoc_version_args = {
|
378
390
|
:version_id => version.id,
|
379
|
-
:foreign_key_name => assoc.foreign_key
|
380
|
-
|
381
|
-
|
391
|
+
:foreign_key_name => assoc.foreign_key
|
392
|
+
}
|
393
|
+
|
394
|
+
if assoc.options[:polymorphic]
|
395
|
+
associated_record = send(assoc.name) if send(assoc.foreign_type)
|
396
|
+
if associated_record && associated_record.class.paper_trail_enabled_for_model?
|
397
|
+
assoc_version_args.merge!(:foreign_key_id => associated_record.id)
|
398
|
+
end
|
399
|
+
elsif assoc.klass.paper_trail_enabled_for_model?
|
400
|
+
assoc_version_args.merge!(:foreign_key_id => send(assoc.foreign_key))
|
382
401
|
end
|
402
|
+
|
403
|
+
PaperTrail::VersionAssociation.create(assoc_version_args) if assoc_version_args.has_key?(:foreign_key_id)
|
383
404
|
end
|
384
405
|
end
|
385
406
|
|
@@ -403,7 +424,7 @@ module PaperTrail
|
|
403
424
|
if v.respond_to?(:call)
|
404
425
|
v.call(self)
|
405
426
|
elsif v.is_a?(Symbol) && respond_to?(v)
|
406
|
-
# if it is an attribute that is changing in an existing object,
|
427
|
+
# if it is an attribute that is changing in an existing object,
|
407
428
|
# be sure to grab the current version
|
408
429
|
if has_attribute?(v) && send("#{v}_changed?".to_sym) && data[:event] != 'create'
|
409
430
|
send("#{v}_was".to_sym)
|
@@ -418,17 +439,9 @@ module PaperTrail
|
|
418
439
|
data.merge(PaperTrail.controller_info || {})
|
419
440
|
end
|
420
441
|
|
421
|
-
def
|
422
|
-
|
423
|
-
|
424
|
-
all_timestamp_attributes.each do |column|
|
425
|
-
if self.class.column_names.include?(column.to_s) and not send("#{column}_was").nil?
|
426
|
-
previous[column] = send("#{column}_was")
|
427
|
-
end
|
428
|
-
end
|
429
|
-
enums = previous.respond_to?(:defined_enums) ? previous.defined_enums : {}
|
430
|
-
previous.tap do |prev|
|
431
|
-
prev.id = id # `dup` clears the `id` so we add that back
|
442
|
+
def attributes_before_change
|
443
|
+
attributes.tap do |prev|
|
444
|
+
enums = self.respond_to?(:defined_enums) ? self.defined_enums : {}
|
432
445
|
changed_attributes.select { |k,v| self.class.column_names.include?(k) }.each do |attr, before|
|
433
446
|
before = enums[attr][before] if enums[attr]
|
434
447
|
prev[attr] = before
|
@@ -438,10 +451,10 @@ module PaperTrail
|
|
438
451
|
|
439
452
|
# returns hash of attributes (with appropriate attributes serialized),
|
440
453
|
# ommitting attributes to be skipped
|
441
|
-
def object_attrs_for_paper_trail(
|
442
|
-
attrs =
|
454
|
+
def object_attrs_for_paper_trail(attributes_hash)
|
455
|
+
attrs = attributes_hash.except(*self.paper_trail_options[:skip])
|
443
456
|
if PaperTrail.serialized_attributes?
|
444
|
-
self.class.serialize_attributes_for_paper_trail(attrs)
|
457
|
+
self.class.serialize_attributes_for_paper_trail!(attrs)
|
445
458
|
end
|
446
459
|
attrs
|
447
460
|
end
|
@@ -23,7 +23,8 @@ module PaperTrail
|
|
23
23
|
# in the serialized object_changes
|
24
24
|
def where_object_changes_condition(arel_field, field, value)
|
25
25
|
# Need to check first (before) and secondary (after) fields
|
26
|
-
if defined?(::YAML::ENGINE) && ::YAML::ENGINE.yamler == 'psych'
|
26
|
+
if (defined?(::YAML::ENGINE) && ::YAML::ENGINE.yamler == 'psych') ||
|
27
|
+
(defined?(::Psych) && ::YAML == ::Psych)
|
27
28
|
arel_field.matches("%\n#{field}:\n- #{value}\n%").
|
28
29
|
or(arel_field.matches("%\n#{field}:\n-%\n- #{value}\n%"))
|
29
30
|
else # Syck adds extra spaces into array dumps
|