paper_trail 3.0.6 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +5 -0
- data/.rspec +1 -2
- data/.travis.yml +14 -5
- data/CHANGELOG.md +215 -8
- data/CONTRIBUTING.md +84 -0
- data/README.md +922 -502
- data/Rakefile +2 -2
- data/doc/bug_report_template.rb +65 -0
- data/gemfiles/ar3.gemfile +61 -0
- data/lib/generators/paper_trail/install_generator.rb +22 -3
- data/lib/generators/paper_trail/templates/add_object_changes_to_versions.rb +6 -1
- data/lib/generators/paper_trail/templates/add_transaction_id_column_to_versions.rb +11 -0
- data/lib/generators/paper_trail/templates/create_version_associations.rb +17 -0
- data/lib/generators/paper_trail/templates/create_versions.rb +22 -1
- data/lib/paper_trail.rb +52 -22
- data/lib/paper_trail/attributes_serialization.rb +89 -0
- data/lib/paper_trail/cleaner.rb +32 -15
- data/lib/paper_trail/config.rb +35 -2
- data/lib/paper_trail/frameworks/active_record.rb +4 -5
- data/lib/paper_trail/frameworks/active_record/models/paper_trail/version_association.rb +7 -0
- data/lib/paper_trail/frameworks/rails.rb +1 -0
- data/lib/paper_trail/frameworks/rails/controller.rb +27 -11
- data/lib/paper_trail/frameworks/rspec.rb +5 -0
- data/lib/paper_trail/frameworks/sinatra.rb +3 -1
- data/lib/paper_trail/has_paper_trail.rb +304 -148
- data/lib/paper_trail/record_history.rb +59 -0
- data/lib/paper_trail/reifier.rb +270 -0
- data/lib/paper_trail/serializers/json.rb +13 -2
- data/lib/paper_trail/serializers/yaml.rb +16 -2
- data/lib/paper_trail/version_association_concern.rb +15 -0
- data/lib/paper_trail/version_concern.rb +160 -122
- data/lib/paper_trail/version_number.rb +3 -3
- data/paper_trail.gemspec +22 -9
- data/spec/generators/install_generator_spec.rb +4 -4
- data/spec/models/animal_spec.rb +36 -0
- data/spec/models/boolit_spec.rb +48 -0
- data/spec/models/callback_modifier_spec.rb +96 -0
- data/spec/models/fluxor_spec.rb +19 -0
- data/spec/models/gadget_spec.rb +14 -12
- data/spec/models/joined_version_spec.rb +9 -9
- data/spec/models/json_version_spec.rb +103 -0
- data/spec/models/kitchen/banana_spec.rb +14 -0
- data/spec/models/not_on_update_spec.rb +19 -0
- data/spec/models/post_with_status_spec.rb +3 -3
- data/spec/models/skipper_spec.rb +46 -0
- data/spec/models/thing_spec.rb +11 -0
- data/spec/models/version_spec.rb +195 -44
- data/spec/models/widget_spec.rb +136 -76
- data/spec/modules/paper_trail_spec.rb +27 -0
- data/spec/modules/version_concern_spec.rb +8 -8
- data/spec/modules/version_number_spec.rb +16 -16
- data/spec/paper_trail/config_spec.rb +52 -0
- data/spec/paper_trail_spec.rb +17 -17
- data/spec/rails_helper.rb +34 -0
- data/spec/requests/articles_spec.rb +10 -14
- data/spec/spec_helper.rb +81 -34
- data/spec/support/alt_db_init.rb +1 -1
- data/test/dummy/app/controllers/application_controller.rb +1 -1
- data/test/dummy/app/controllers/articles_controller.rb +4 -1
- data/test/dummy/app/models/animal.rb +2 -0
- data/test/dummy/app/models/book.rb +4 -0
- data/test/dummy/app/models/boolit.rb +4 -0
- data/test/dummy/app/models/callback_modifier.rb +45 -0
- data/test/dummy/app/models/chapter.rb +9 -0
- data/test/dummy/app/models/citation.rb +5 -0
- data/test/dummy/app/models/customer.rb +4 -0
- data/test/dummy/app/models/editor.rb +4 -0
- data/test/dummy/app/models/editorship.rb +5 -0
- data/test/dummy/app/models/fruit.rb +5 -0
- data/test/dummy/app/models/kitchen/banana.rb +5 -0
- data/test/dummy/app/models/line_item.rb +4 -0
- data/test/dummy/app/models/not_on_update.rb +4 -0
- data/test/dummy/app/models/order.rb +5 -0
- data/test/dummy/app/models/paragraph.rb +5 -0
- data/test/dummy/app/models/person.rb +13 -3
- data/test/dummy/app/models/post.rb +0 -1
- data/test/dummy/app/models/quotation.rb +5 -0
- data/test/dummy/app/models/section.rb +6 -0
- data/test/dummy/app/models/skipper.rb +6 -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 +5 -0
- data/test/dummy/app/versions/json_version.rb +3 -0
- data/test/dummy/app/versions/kitchen/banana_version.rb +5 -0
- data/test/dummy/config/application.rb +6 -0
- data/test/dummy/config/database.postgres.yml +1 -1
- data/test/dummy/config/environments/test.rb +5 -1
- data/test/dummy/config/initializers/paper_trail.rb +6 -1
- data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +143 -3
- data/test/dummy/db/schema.rb +169 -25
- data/test/functional/controller_test.rb +4 -2
- data/test/functional/modular_sinatra_test.rb +1 -1
- data/test/functional/sinatra_test.rb +1 -1
- data/test/paper_trail_test.rb +7 -0
- data/test/test_helper.rb +38 -2
- data/test/time_travel_helper.rb +15 -0
- data/test/unit/associations_test.rb +726 -0
- data/test/unit/inheritance_column_test.rb +6 -6
- data/test/unit/model_test.rb +109 -125
- data/test/unit/protected_attrs_test.rb +4 -3
- data/test/unit/serializer_test.rb +6 -6
- data/test/unit/serializers/json_test.rb +17 -4
- data/test/unit/serializers/yaml_test.rb +5 -1
- data/test/unit/version_test.rb +87 -69
- metadata +172 -75
- data/gemfiles/3.0.gemfile +0 -42
- data/test/dummy/public/404.html +0 -26
- data/test/dummy/public/422.html +0 -26
- data/test/dummy/public/500.html +0 -26
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/public/javascripts/application.js +0 -2
- data/test/dummy/public/javascripts/controls.js +0 -965
- data/test/dummy/public/javascripts/dragdrop.js +0 -974
- data/test/dummy/public/javascripts/effects.js +0 -1123
- data/test/dummy/public/javascripts/prototype.js +0 -6001
- data/test/dummy/public/javascripts/rails.js +0 -175
- data/test/dummy/public/stylesheets/.gitkeep +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e7937d44654d4f77422ffe61d4bc2d81b7ed4026
|
|
4
|
+
data.tar.gz: 9ec1f8bc013795818775e58e1cd9c33e5dd36499
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e546ad5e910cc2a08aeaaf0dca6754e05f3a89fbfb022b49a078025e09a623e359a552c2e52f3126c84662fad2cd804c34db709689211852ff43e32ff40db3a1
|
|
7
|
+
data.tar.gz: 0ef05118041333567e076bc8407330917cc36ec5d23aec22fd6dced9367b10a466c4284eccddbb3f4d0675c4154165f24097eafe4acf0135e133c426a2b71d4b
|
data/.gitignore
CHANGED
data/.rspec
CHANGED
data/.travis.yml
CHANGED
|
@@ -6,21 +6,28 @@ 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
|
|
15
|
+
|
|
16
|
+
sudo: false
|
|
12
17
|
|
|
13
18
|
before_script:
|
|
19
|
+
- mysql --version
|
|
14
20
|
- sh -c "if [ \"$DB\" = 'mysql' ]; then mysql -e 'create database paper_trail_test;'; fi"
|
|
15
21
|
- sh -c "if [ \"$DB\" = 'mysql' ]; then mysql -e 'create database paper_trail_bar; '; fi"
|
|
16
22
|
- sh -c "if [ \"$DB\" = 'mysql' ]; then mysql -e 'create database paper_trail_foo; '; fi"
|
|
23
|
+
- psql --version
|
|
17
24
|
- sh -c "if [ \"$DB\" = 'postgres' ]; then psql -c 'create database paper_trail_test;' -U postgres; fi"
|
|
18
25
|
- sh -c "if [ \"$DB\" = 'postgres' ]; then psql -c 'create database paper_trail_bar;' -U postgres; fi"
|
|
19
26
|
- sh -c "if [ \"$DB\" = 'postgres' ]; then psql -c 'create database paper_trail_foo;' -U postgres; fi"
|
|
20
27
|
|
|
21
28
|
gemfile:
|
|
22
29
|
- Gemfile
|
|
23
|
-
- gemfiles/
|
|
30
|
+
- gemfiles/ar3.gemfile
|
|
24
31
|
|
|
25
32
|
matrix:
|
|
26
33
|
fast_finish: true
|
|
@@ -29,4 +36,6 @@ matrix:
|
|
|
29
36
|
gemfile: Gemfile
|
|
30
37
|
- rvm: 1.8.7
|
|
31
38
|
gemfile: Gemfile
|
|
32
|
-
|
|
39
|
+
|
|
40
|
+
addons:
|
|
41
|
+
postgresql: "9.4"
|
data/CHANGELOG.md
CHANGED
|
@@ -1,13 +1,220 @@
|
|
|
1
|
+
## 4.2.0 (2016-05-31)
|
|
2
|
+
|
|
3
|
+
### Breaking Changes
|
|
4
|
+
|
|
5
|
+
- None
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- [#808](https://github.com/airblade/paper_trail/pull/808) -
|
|
10
|
+
Warn when destroy callback is set to :after with ActiveRecord 5
|
|
11
|
+
option `belongs_to_required_by_default` set to `true`.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- None
|
|
16
|
+
|
|
17
|
+
## 4.1.0 (2016-01-30)
|
|
18
|
+
|
|
19
|
+
### Breaking Changes
|
|
20
|
+
|
|
21
|
+
- None
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- A way to control the order of AR callbacks.
|
|
26
|
+
[#614](https://github.com/airblade/paper_trail/pull/614)
|
|
27
|
+
- Added `unversioned_attributes` option to `reify`.
|
|
28
|
+
[#579](https://github.com/airblade/paper_trail/pull/579)
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- None
|
|
33
|
+
|
|
34
|
+
## 4.0.2
|
|
35
|
+
|
|
36
|
+
### Breaking Changes
|
|
37
|
+
|
|
38
|
+
- None
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
- None
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- [#696](https://github.com/airblade/paper_trail/issues/696) /
|
|
47
|
+
[#697](https://github.com/airblade/paper_trail/pull/697)
|
|
48
|
+
Bind JSON query parameters in `where_object` and `where_object_changes`.
|
|
49
|
+
|
|
50
|
+
## 4.0.1
|
|
51
|
+
|
|
52
|
+
### Breaking Changes
|
|
53
|
+
|
|
54
|
+
- None
|
|
55
|
+
|
|
56
|
+
### Added
|
|
57
|
+
|
|
58
|
+
- None
|
|
59
|
+
|
|
60
|
+
### Fixed
|
|
61
|
+
|
|
62
|
+
- [#636](https://github.com/airblade/paper_trail/issues/636) -
|
|
63
|
+
Should compile assets without a db connection
|
|
64
|
+
- [#589](https://github.com/airblade/paper_trail/pull/589) /
|
|
65
|
+
[#588](https://github.com/airblade/paper_trail/issues/588) -
|
|
66
|
+
Fixes timestamp for "create" versions
|
|
67
|
+
|
|
68
|
+
## 4.0.0
|
|
69
|
+
|
|
70
|
+
This major release adds JSON column support in PostgreSQL, limited support for
|
|
71
|
+
versioning associations, various new configuration options, and a year's worth
|
|
72
|
+
of bug fixes. Thanks to everyone who helped test the two betas and two release
|
|
73
|
+
candidates.
|
|
74
|
+
|
|
75
|
+
### Breaking Changes
|
|
76
|
+
|
|
77
|
+
- Using a Rails initializer to reopen PaperTrail::Version or otherwise extend
|
|
78
|
+
PaperTrail is no longer recommended. An alternative is described in the
|
|
79
|
+
readme. See https://github.com/airblade/paper_trail/pull/557 and
|
|
80
|
+
https://github.com/airblade/paper_trail/pull/492.
|
|
81
|
+
- If you depend on the `RSpec` or `Cucumber` helpers, you must
|
|
82
|
+
[require them in your test helper](https://github.com/airblade/paper_trail#testing).
|
|
83
|
+
- [#566](https://github.com/airblade/paper_trail/pull/566) - Removed deprecated
|
|
84
|
+
methods `paper_trail_on` and `paper_trail_off`. Use `paper_trail_on!` and
|
|
85
|
+
`paper_trail_off!` instead.
|
|
86
|
+
- [#458](https://github.com/airblade/paper_trail/pull/458) - Version metadata
|
|
87
|
+
(the `:meta` option) from AR attributes for `create` events will now save the
|
|
88
|
+
current value instead of `nil`.
|
|
89
|
+
- [#391](https://github.com/airblade/paper_trail/issues/391) - `object_changes`
|
|
90
|
+
value should dump to `YAML` as a normal `Hash` instead of an
|
|
91
|
+
`ActiveSupport::HashWithIndifferentAccess`.
|
|
92
|
+
- [#375](https://github.com/airblade/paper_trail/pull/375) /
|
|
93
|
+
[#374](https://github.com/airblade/paper_trail/issues/374) /
|
|
94
|
+
[#354](https://github.com/airblade/paper_trail/issues/354) /
|
|
95
|
+
[#131](https://github.com/airblade/paper_trail/issues/131) -
|
|
96
|
+
Versions are now saved with an `after_` callback, instead of a `before_`
|
|
97
|
+
callback. This ensures that the timestamp field for a version matches the
|
|
98
|
+
corresponding timestamp in the model.
|
|
99
|
+
- `3da1f104` - `PaperTrail.config` and `PaperTrail.configure` are now
|
|
100
|
+
identical: both return the `PaperTrail::Config` instance and also
|
|
101
|
+
yield it if a block is provided.
|
|
102
|
+
|
|
103
|
+
### Added
|
|
104
|
+
|
|
105
|
+
- [#525](https://github.com/airblade/paper_trail/issues/525) /
|
|
106
|
+
[#512](https://github.com/airblade/paper_trail/pull/512) -
|
|
107
|
+
Support for virtual accessors and redefined setter and getter methods.
|
|
108
|
+
- [#518](https://github.com/airblade/paper_trail/pull/518) - Support for
|
|
109
|
+
querying against PostgreSQL's
|
|
110
|
+
[`JSON` and `JSONB` column types](http://www.postgresql.org/docs/9.4/static/datatype-json.html)
|
|
111
|
+
via `PaperTrail::VersionConcern#where_object` and
|
|
112
|
+
`PaperTrail::VersionConcern#where_object_changes`
|
|
113
|
+
- [#507](https://github.com/airblade/paper_trail/pull/507) -
|
|
114
|
+
New option: `:save_changes` controls whether or not to save changes to the
|
|
115
|
+
`object_changes` column (if it exists).
|
|
116
|
+
- [#500](https://github.com/airblade/paper_trail/pull/500) - Support for
|
|
117
|
+
passing an empty array to the `on` option (`on: []`) to disable all
|
|
118
|
+
automatic versioning.
|
|
119
|
+
- [#494](https://github.com/airblade/paper_trail/issues/494) - The install
|
|
120
|
+
generator will warn the user if the migration they are attempting to
|
|
121
|
+
generate already exists.
|
|
122
|
+
- [#484](https://github.com/airblade/paper_trail/pull/484) - Support for
|
|
123
|
+
[PostgreSQL's `JSONB` Type](http://www.postgresql.org/docs/9.4/static/datatype-json.html)
|
|
124
|
+
for storing `object` and `object_changes`.
|
|
125
|
+
- [#439](https://github.com/airblade/paper_trail/pull/439) /
|
|
126
|
+
[#12](https://github.com/airblade/paper_trail/issues/12) -
|
|
127
|
+
Support for versioning associations (has many, has one, etc.) one level deep.
|
|
128
|
+
- [#420](https://github.com/airblade/paper_trail/issues/420) - Add
|
|
129
|
+
`VersionConcern#where_object_changes` instance method; acts as a helper for
|
|
130
|
+
querying against the `object_changes` column in versions table.
|
|
131
|
+
- [#416](https://github.com/airblade/paper_trail/issues/416) - Added a
|
|
132
|
+
`config` option for enabling/disabling utilization of
|
|
133
|
+
`serialized_attributes` for `ActiveRecord`, necessary because
|
|
134
|
+
`serialized_attributes` has been deprecated in `ActiveRecord` version `4.2`
|
|
135
|
+
and will be removed in version `5.0`
|
|
136
|
+
- [#399](https://github.com/airblade/paper_trail/pull/399) - Add `:dup`
|
|
137
|
+
argument for options hash to `reify` which forces a new model instance.
|
|
138
|
+
- [#394](https://github.com/airblade/paper_trail/pull/394) - Add RSpec matcher
|
|
139
|
+
`have_a_version_with` for easier testing.
|
|
140
|
+
- [#347](https://github.com/airblade/paper_trail/pull/347) - Autoload
|
|
141
|
+
`ActiveRecord` models in via a `Rails::Engine` when the gem is used with
|
|
142
|
+
`Rails`.
|
|
143
|
+
|
|
144
|
+
### Fixed
|
|
145
|
+
|
|
146
|
+
- [#563](https://github.com/airblade/paper_trail/pull/563) - Fixed a bug in
|
|
147
|
+
`touch_with_version` so that it will still create a version even when the
|
|
148
|
+
`on` option is, e.g. `[:create]`.
|
|
149
|
+
- [#541](https://github.com/airblade/paper_trail/pull/541) -
|
|
150
|
+
`PaperTrail.config.enabled` should be Thread Safe
|
|
151
|
+
- [#451](https://github.com/airblade/paper_trail/issues/451) - Fix `reify`
|
|
152
|
+
method in context of model where the base class has a default scope, and the
|
|
153
|
+
live instance is not scoped within that default scope.
|
|
154
|
+
- [#440](https://github.com/airblade/paper_trail/pull/440) - `versions`
|
|
155
|
+
association should clear/reload after a transaction rollback.
|
|
156
|
+
- [#438](https://github.com/airblade/paper_trail/issues/438) -
|
|
157
|
+
`ModelKlass.paper_trail_enabled_for_model?` should return `false` if
|
|
158
|
+
`has_paper_trail` has not been declared on the class.
|
|
159
|
+
- [#404](https://github.com/airblade/paper_trail/issues/404) /
|
|
160
|
+
[#428](https://github.com/airblade/paper_trail/issues/428) -
|
|
161
|
+
`model_instance.dup` does not need to be invoked when examining what the
|
|
162
|
+
instance looked like before changes were persisted, which avoids issues if a
|
|
163
|
+
3rd party has overriden the `dup` behavior. Also fixes errors occuring when
|
|
164
|
+
a user attempts to update the inheritance column on an STI model instance in
|
|
165
|
+
`ActiveRecord` 4.1.x
|
|
166
|
+
- [#427](https://github.com/airblade/paper_trail/pull/427) - Fix `reify`
|
|
167
|
+
method in context of model where a column has been removed.
|
|
168
|
+
- [#414](https://github.com/airblade/paper_trail/issues/414) - Fix
|
|
169
|
+
functionality `ignore` argument to `has_paper_trail` in `ActiveRecord` 4.
|
|
170
|
+
- [#413](https://github.com/airblade/paper_trail/issues/413) - Utilize
|
|
171
|
+
[RequestStore](https://github.com/steveklabnik/request_store) to ensure that
|
|
172
|
+
the `PaperTrail.whodunnit` is set in a thread safe manner within Rails and
|
|
173
|
+
Sinatra.
|
|
174
|
+
- [#381](https://github.com/airblade/paper_trail/issues/381) - Fix `irb`
|
|
175
|
+
warning: `can't alias context from irb_context`. `Rspec` and `Cucumber`
|
|
176
|
+
helpers should not be loaded by default, regardless of whether those
|
|
177
|
+
libraries are loaded.
|
|
178
|
+
- [#248](https://github.com/airblade/paper_trail/issues/248) - In MySQL, to
|
|
179
|
+
prevent truncation, generated migrations now use `longtext` instead of `text`.
|
|
180
|
+
- Methods handling serialized attributes should fallback to the currently set
|
|
181
|
+
Serializer instead of always falling back to `PaperTrail::Serializers::YAML`.
|
|
182
|
+
|
|
183
|
+
### Deprecated
|
|
184
|
+
|
|
185
|
+
- [#479](https://github.com/airblade/paper_trail/issues/479) - Deprecated
|
|
186
|
+
`originator` method, use `paper_trail_originator`.
|
|
187
|
+
|
|
188
|
+
## 3.0.9
|
|
189
|
+
|
|
190
|
+
- [#479](https://github.com/airblade/paper_trail/issues/479) - Deprecated
|
|
191
|
+
`originator` method in favor of `paper_trail_originator` Deprecation warning
|
|
192
|
+
informs users that the `originator` of the methods will be removed in
|
|
193
|
+
version `4.0`. (Backported from v4)
|
|
194
|
+
- Updated deprecation warnings for `Model.paper_trail_on` and
|
|
195
|
+
`Model.paper_trail_off` to have display correct version number the methods
|
|
196
|
+
will be removed (`4.0`)
|
|
197
|
+
|
|
198
|
+
## 3.0.8
|
|
199
|
+
|
|
200
|
+
- [#525](https://github.com/airblade/paper_trail/issues/525) / [#512](https://github.com/airblade/paper_trail/pull/512) -
|
|
201
|
+
Support for virtual accessors and redefined setter and getter methods.
|
|
202
|
+
|
|
203
|
+
## 3.0.7
|
|
204
|
+
|
|
205
|
+
- [#404](https://github.com/airblade/paper_trail/issues/404) / [#428](https://github.com/airblade/paper_trail/issues/428) -
|
|
206
|
+
Fix errors occuring when a user attempts to update the inheritance column on an STI model instance in `ActiveRecord` 4.1.x
|
|
207
|
+
|
|
1
208
|
## 3.0.6
|
|
2
209
|
|
|
3
|
-
- [#414](https://github.com/airblade/paper_trail/issues/414) -
|
|
4
|
-
|
|
210
|
+
- [#414](https://github.com/airblade/paper_trail/issues/414) - Backport fix for `ignore` argument to `has_paper_trail` in
|
|
211
|
+
`ActiveRecord` 4.
|
|
5
212
|
|
|
6
213
|
## 3.0.5
|
|
7
214
|
|
|
8
|
-
- [#401](https://github.com/airblade/paper_trail/issues/401) / [#406](https://github.com/airblade/paper_trail/issues/406)
|
|
215
|
+
- [#401](https://github.com/airblade/paper_trail/issues/401) / [#406](https://github.com/airblade/paper_trail/issues/406) -
|
|
9
216
|
`PaperTrail::Version` class is not loaded via a `Rails::Engine`, even when the gem is used with in Rails. This feature has
|
|
10
|
-
will be re-introduced in version `
|
|
217
|
+
will be re-introduced in version `4.0`.
|
|
11
218
|
- [#398](https://github.com/airblade/paper_trail/pull/398) - Only require the `RSpec` helper if `RSpec::Core` is required.
|
|
12
219
|
|
|
13
220
|
## 3.0.3
|
|
@@ -52,7 +259,7 @@ in the `PaperTrail::Version` class through a `Rails::Engine` when the gem is use
|
|
|
52
259
|
with Rails `4.1.0.rc1`.
|
|
53
260
|
- [#334](https://github.com/airblade/paper_trail/pull/334) - Add small-scope `whodunnit` method to `PaperTrail::Model::InstanceMethods`.
|
|
54
261
|
- [#329](https://github.com/airblade/paper_trail/issues/329) - Add `touch_with_version` method to `PaperTrail::Model::InstanceMethods`,
|
|
55
|
-
to allow for generating a version `touch`ing a model.
|
|
262
|
+
to allow for generating a version while `touch`ing a model.
|
|
56
263
|
- [#328](https://github.com/airblade/paper_trail/pull/328) / [#326](https://github.com/airblade/paper_trail/issues/326) /
|
|
57
264
|
[#307](https://github.com/airblade/paper_trail/issues/307) - `Model.paper_trail_enabled_for_model?` and
|
|
58
265
|
`model_instance.without_versioning` is now thread-safe.
|
|
@@ -63,8 +270,8 @@ in the `PaperTrail::Version` class through a `Rails::Engine` when the gem is use
|
|
|
63
270
|
- [#312](https://github.com/airblade/paper_trail/issues/312) - Fix RSpec `with_versioning` class level helper method.
|
|
64
271
|
- `model_instance.without_versioning` now yields the `model_instance`, enabling syntax like this:
|
|
65
272
|
`model_instance.without_versioning { |obj| obj.update_attributes(:name => 'value') }`.
|
|
66
|
-
- Deprecated `Model.paper_trail_on` and `Model.paper_trail_off` in favor of bang versions of the methods.
|
|
67
|
-
informs users that the non-bang versions of the methods will be removed in version `
|
|
273
|
+
- Deprecated `Model.paper_trail_on` and `Model.paper_trail_off` in favor of bang versions of the methods.
|
|
274
|
+
Deprecation warning informs users that the non-bang versions of the methods will be removed in version `4.0`
|
|
68
275
|
|
|
69
276
|
## 3.0.0
|
|
70
277
|
|
|
@@ -91,7 +298,7 @@ in the `PaperTrail::Version` class through a `Rails::Engine` when the gem is use
|
|
|
91
298
|
- [#216](https://github.com/airblade/paper_trail/pull/216) - Added helper & extension for [RSpec](https://github.com/rspec/rspec),
|
|
92
299
|
and helper for [Cucumber](http://cukes.info).
|
|
93
300
|
- [#212](https://github.com/airblade/paper_trail/pull/212) - Added `PaperTrail::Cleaner` module, useful for discarding draft versions.
|
|
94
|
-
- [#207](https://github.com/airblade/paper_trail/issues/207) - Versions for `'create'` events are now created with `create!` instead of
|
|
301
|
+
- [#207](https://github.com/airblade/paper_trail/issues/207) - Versions for `'create'` events are now created with `create!` instead of
|
|
95
302
|
`create` so that an exception gets raised if it is appropriate to do so.
|
|
96
303
|
- [#199](https://github.com/airblade/paper_trail/pull/199) - Rails 4 compatibility.
|
|
97
304
|
- [#165](https://github.com/airblade/paper_trail/pull/165) - Namespaced the `Version` class under the `PaperTrail` module.
|
data/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Thanks for your interest in PaperTrail!
|
|
4
|
+
|
|
5
|
+
Ask usage questions on Stack Overflow:
|
|
6
|
+
http://stackoverflow.com/tags/papertrail
|
|
7
|
+
|
|
8
|
+
**Please do not use github issues to ask usage questions.**
|
|
9
|
+
|
|
10
|
+
On github, we appreciate bug reports, feature
|
|
11
|
+
suggestions, and especially pull requests.
|
|
12
|
+
|
|
13
|
+
Thanks, and happy (paper) trails :)
|
|
14
|
+
|
|
15
|
+
## Reporting Bugs
|
|
16
|
+
|
|
17
|
+
Please use our [bug report template][1].
|
|
18
|
+
|
|
19
|
+
## Development
|
|
20
|
+
|
|
21
|
+
Testing is a little awkward because the test suite:
|
|
22
|
+
|
|
23
|
+
1. contains a rails app with three databases (test, foo, and bar)
|
|
24
|
+
1. supports three different RDBMS': sqlite, mysql, and postgres
|
|
25
|
+
|
|
26
|
+
Run tests with sqlite:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
# Create the appropriate database config. file
|
|
30
|
+
rm test/dummy/config/database.yml
|
|
31
|
+
DB=sqlite bundle exec rake prepare
|
|
32
|
+
|
|
33
|
+
# If this is the first test run ever, create databases
|
|
34
|
+
cd test/dummy
|
|
35
|
+
RAILS_ENV=test bundle exec rake db:setup
|
|
36
|
+
RAILS_ENV=foo bundle exec rake db:setup
|
|
37
|
+
RAILS_ENV=bar bundle exec rake db:setup
|
|
38
|
+
cd ../..
|
|
39
|
+
|
|
40
|
+
# Run tests
|
|
41
|
+
DB=sqlite bundle exec rake
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Run tests with mysql:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
# Create the appropriate database config. file
|
|
48
|
+
rm test/dummy/config/database.yml
|
|
49
|
+
DB=mysql bundle exec rake prepare
|
|
50
|
+
|
|
51
|
+
# If this is the first test run ever, create databases
|
|
52
|
+
cd test/dummy
|
|
53
|
+
RAILS_ENV=test bundle exec rake db:setup
|
|
54
|
+
RAILS_ENV=foo bundle exec rake db:setup
|
|
55
|
+
RAILS_ENV=bar bundle exec rake db:setup
|
|
56
|
+
cd ../..
|
|
57
|
+
|
|
58
|
+
# Run tests
|
|
59
|
+
DB=mysql bundle exec rake
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Run tests with postgres:
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
# Create the appropriate database config. file
|
|
66
|
+
rm test/dummy/config/database.yml
|
|
67
|
+
DB=postgres bundle exec rake prepare
|
|
68
|
+
|
|
69
|
+
# If this is the first test run ever, create databases.
|
|
70
|
+
# Unlike mysql, use create/migrate instead of setup.
|
|
71
|
+
cd test/dummy
|
|
72
|
+
RAILS_ENV=test bundle exec rake db:create
|
|
73
|
+
RAILS_ENV=test bundle exec rake db:migrate
|
|
74
|
+
RAILS_ENV=foo bundle exec rake db:create
|
|
75
|
+
RAILS_ENV=foo bundle exec rake db:migrate
|
|
76
|
+
RAILS_ENV=bar bundle exec rake db:create
|
|
77
|
+
RAILS_ENV=bar bundle exec rake db:migrate
|
|
78
|
+
cd ../..
|
|
79
|
+
|
|
80
|
+
# Run tests
|
|
81
|
+
DB=postgres bundle exec rake
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
[1]: https://github.com/airblade/paper_trail/blob/master/doc/bug_report_template.rb
|
data/README.md
CHANGED
|
@@ -1,99 +1,136 @@
|
|
|
1
|
-
# PaperTrail
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
# PaperTrail
|
|
2
|
+
|
|
3
|
+
[![Build Status][4]][5] [![Dependency Status][6]][7]
|
|
4
|
+
|
|
5
|
+
Track changes to your models, for auditing or versioning. See how a model looked
|
|
6
|
+
at any stage in its lifecycle, revert it to any version, or restore it after it
|
|
7
|
+
has been destroyed.
|
|
8
|
+
|
|
9
|
+
## Documentation
|
|
10
|
+
|
|
11
|
+
| Version | Documentation |
|
|
12
|
+
| -------------- | ------------- |
|
|
13
|
+
| 4 | https://github.com/airblade/paper_trail/blob/4-stable/README.md |
|
|
14
|
+
| 3 | https://github.com/airblade/paper_trail/blob/3.0-stable/README.md |
|
|
15
|
+
| 2 | https://github.com/airblade/paper_trail/blob/2.7-stable/README.md |
|
|
16
|
+
| 1 | https://github.com/airblade/paper_trail/blob/rails2/README.md |
|
|
17
|
+
|
|
18
|
+
## Table of Contents
|
|
19
|
+
|
|
20
|
+
- [Compatibility](#compatibility)
|
|
21
|
+
- [Installation](#installation)
|
|
22
|
+
- [Basic Usage](#basic-usage)
|
|
23
|
+
- [API Summary](#api-summary)
|
|
24
|
+
- Limiting What is Versioned, and When
|
|
25
|
+
- [Choosing Lifecycle Events To Monitor](#choosing-lifecycle-events-to-monitor)
|
|
26
|
+
- [Choosing When To Save New Versions](#choosing-when-to-save-new-versions)
|
|
27
|
+
- [Choosing Attributes To Monitor](#choosing-attributes-to-monitor)
|
|
28
|
+
- [Turning PaperTrail Off/On](#turning-papertrail-offon)
|
|
29
|
+
- [Limiting the Number of Versions Created](#limiting-the-number-of-versions-created)
|
|
30
|
+
- Working With Versions
|
|
31
|
+
- [Reverting And Undeleting A Model](#reverting-and-undeleting-a-model)
|
|
32
|
+
- [Navigating Versions](#navigating-versions)
|
|
33
|
+
- [Diffing Versions](#diffing-versions)
|
|
34
|
+
- [Deleting Old Versions](#deleting-old-versions)
|
|
35
|
+
- Saving More Information About Versions
|
|
36
|
+
- [Finding Out Who Was Responsible For A Change](#finding-out-who-was-responsible-for-a-change)
|
|
37
|
+
- [Associations](#associations)
|
|
38
|
+
- [Storing metadata](#storing-metadata)
|
|
39
|
+
- Extensibility
|
|
40
|
+
- [Custom Version Classes](#custom-version-classes)
|
|
41
|
+
- [Custom Serializer](#using-a-custom-serializer)
|
|
42
|
+
- [SerializedAttributes support](#serializedattributes-support)
|
|
43
|
+
- [Testing](#testing)
|
|
44
|
+
- [Sinatra](#sinatra)
|
|
30
45
|
|
|
31
46
|
## Compatibility
|
|
32
47
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
48
|
+
| paper_trail | branch | tags | ruby | activerecord |
|
|
49
|
+
| ----------- | ---------- | ------ | -------- | ------------ |
|
|
50
|
+
| 4 | 4-stable | v4.x | >= 1.8.7 | >= 3.0, < 6 |
|
|
51
|
+
| 3 | 3.0-stable | v3.x | >= 1.8.7 | >= 3.0, < 5 |
|
|
52
|
+
| 2 | 2.7-stable | v2.x | >= 1.8.7 | >= 3.0, < 4 |
|
|
53
|
+
| 1 | rails2 | v1.x | >= 1.8.7 | >= 2.3, < 3 |
|
|
38
54
|
|
|
39
55
|
## Installation
|
|
40
56
|
|
|
41
|
-
### Rails 3 & 4
|
|
42
|
-
|
|
43
57
|
1. Add PaperTrail to your `Gemfile`.
|
|
44
58
|
|
|
45
|
-
`gem 'paper_trail', '~>
|
|
59
|
+
`gem 'paper_trail', '~> 4.2.0'`
|
|
46
60
|
|
|
47
|
-
|
|
61
|
+
1. Add a `versions` table to your database.
|
|
48
62
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
`bundle exec rake db:migrate`
|
|
63
|
+
```
|
|
64
|
+
bundle exec rails generate paper_trail:install
|
|
65
|
+
bundle exec rake db:migrate
|
|
66
|
+
```
|
|
54
67
|
|
|
55
|
-
|
|
68
|
+
1. Add `has_paper_trail` to the models you want to track.
|
|
56
69
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
In order to configure PaperTrail for usage with [Sinatra](http://www.sinatrarb.com),
|
|
60
|
-
your `Sinatra` app must be using `ActiveRecord` 3 or 4. It is also recommended to use the
|
|
61
|
-
[Sinatra ActiveRecord Extension](https://github.com/janko-m/sinatra-activerecord) or something similar for managing
|
|
62
|
-
your applications `ActiveRecord` connection in a manner similar to the way `Rails` does. If using the aforementioned
|
|
63
|
-
`Sinatra ActiveRecord Extension`, steps for setting up your app with PaperTrail will look something like this:
|
|
70
|
+
## Basic Usage
|
|
64
71
|
|
|
65
|
-
|
|
72
|
+
Add `has_paper_trail` to your model to record every `create`, `update`,
|
|
73
|
+
and `destroy`.
|
|
66
74
|
|
|
67
|
-
|
|
75
|
+
```ruby
|
|
76
|
+
class Widget < ActiveRecord::Base
|
|
77
|
+
has_paper_trail
|
|
78
|
+
end
|
|
79
|
+
```
|
|
68
80
|
|
|
69
|
-
|
|
81
|
+
This gives you a `versions` method which returns the "paper trail" of changes to
|
|
82
|
+
your model.
|
|
70
83
|
|
|
71
|
-
|
|
84
|
+
```ruby
|
|
85
|
+
widget = Widget.find 42
|
|
86
|
+
widget.versions
|
|
87
|
+
# [<PaperTrail::Version>, <PaperTrail::Version>, ...]
|
|
88
|
+
```
|
|
72
89
|
|
|
73
|
-
|
|
74
|
-
into the `create_versions` migration that was generated into your `db/migrate` directory.
|
|
90
|
+
Once you have a version, you can find out what happened:
|
|
75
91
|
|
|
76
|
-
|
|
92
|
+
```ruby
|
|
93
|
+
v = widget.versions.last
|
|
94
|
+
v.event # 'update', 'create', or 'destroy'
|
|
95
|
+
v.created_at # When the `event` occurred
|
|
96
|
+
v.whodunnit # If the update was via a controller and the
|
|
97
|
+
# controller has a current_user method, returns the
|
|
98
|
+
# id of the current user as a string.
|
|
99
|
+
widget = v.reify # The widget as it was before the update
|
|
100
|
+
# (nil for a create event)
|
|
101
|
+
```
|
|
77
102
|
|
|
78
|
-
|
|
103
|
+
PaperTrail stores the pre-change version of the model, unlike some other
|
|
104
|
+
auditing/versioning plugins, so you can retrieve the original version. This is
|
|
105
|
+
useful when you start keeping a paper trail for models that already have records
|
|
106
|
+
in the database.
|
|
79
107
|
|
|
80
|
-
|
|
108
|
+
```ruby
|
|
109
|
+
widget = Widget.find 153
|
|
110
|
+
widget.name # 'Doobly'
|
|
81
111
|
|
|
112
|
+
# Add has_paper_trail to Widget model.
|
|
82
113
|
|
|
83
|
-
|
|
114
|
+
widget.versions # []
|
|
115
|
+
widget.update_attributes :name => 'Wotsit'
|
|
116
|
+
widget.versions.last.reify.name # 'Doobly'
|
|
117
|
+
widget.versions.last.event # 'update'
|
|
118
|
+
```
|
|
84
119
|
|
|
85
|
-
|
|
120
|
+
This also means that PaperTrail does not waste space storing a version of the
|
|
121
|
+
object as it currently stands. The `versions` method gives you previous
|
|
122
|
+
versions; to get the current one just call a finder on your `Widget` model as
|
|
123
|
+
usual.
|
|
86
124
|
|
|
87
|
-
|
|
125
|
+
Here's a helpful table showing what PaperTrail stores:
|
|
88
126
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
127
|
+
| *Event* | *create* | *update* | *destroy* |
|
|
128
|
+
| -------------- | -------- | -------- | --------- |
|
|
129
|
+
| *Model Before* | nil | widget | widget |
|
|
130
|
+
| *Model After* | widget | widget | nil |
|
|
92
131
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
end
|
|
96
|
-
```
|
|
132
|
+
PaperTrail stores the values in the Model Before column. Most other
|
|
133
|
+
auditing/versioning plugins store the After column.
|
|
97
134
|
|
|
98
135
|
## API Summary
|
|
99
136
|
|
|
@@ -101,21 +138,23 @@ When you declare `has_paper_trail` in your model, you get these methods:
|
|
|
101
138
|
|
|
102
139
|
```ruby
|
|
103
140
|
class Widget < ActiveRecord::Base
|
|
104
|
-
has_paper_trail
|
|
141
|
+
has_paper_trail
|
|
105
142
|
end
|
|
106
143
|
|
|
107
|
-
# Returns this widget's versions. You can customise the name of the
|
|
144
|
+
# Returns this widget's versions. You can customise the name of the
|
|
145
|
+
# association.
|
|
108
146
|
widget.versions
|
|
109
147
|
|
|
110
148
|
# Return the version this widget was reified from, or nil if it is live.
|
|
111
149
|
# You can customise the name of the method.
|
|
112
150
|
widget.version
|
|
113
151
|
|
|
114
|
-
# Returns true if this widget is the current, live one; or false if it is from
|
|
152
|
+
# Returns true if this widget is the current, live one; or false if it is from
|
|
153
|
+
# a previous version.
|
|
115
154
|
widget.live?
|
|
116
155
|
|
|
117
156
|
# Returns who put the widget into its current state.
|
|
118
|
-
widget.
|
|
157
|
+
widget.paper_trail_originator
|
|
119
158
|
|
|
120
159
|
# Returns the widget (not a version) as it looked at the given timestamp.
|
|
121
160
|
widget.version_at(timestamp)
|
|
@@ -126,7 +165,8 @@ widget.previous_version
|
|
|
126
165
|
# Returns the widget (not a version) as it became next.
|
|
127
166
|
widget.next_version
|
|
128
167
|
|
|
129
|
-
# Generates a version for a `touch` event (`widget.touch` does NOT generate a
|
|
168
|
+
# Generates a version for a `touch` event (`widget.touch` does NOT generate a
|
|
169
|
+
# version)
|
|
130
170
|
widget.touch_with_version
|
|
131
171
|
|
|
132
172
|
# Turn PaperTrail off for all widgets.
|
|
@@ -135,8 +175,10 @@ Widget.paper_trail_off!
|
|
|
135
175
|
# Turn PaperTrail on for all widgets.
|
|
136
176
|
Widget.paper_trail_on!
|
|
137
177
|
|
|
138
|
-
#
|
|
178
|
+
# Is PaperTrail enabled for Widget, the class?
|
|
139
179
|
Widget.paper_trail_enabled_for_model?
|
|
180
|
+
|
|
181
|
+
# Is PaperTrail enabled for widget, the instance?
|
|
140
182
|
widget.paper_trail_enabled_for_model?
|
|
141
183
|
```
|
|
142
184
|
|
|
@@ -146,8 +188,11 @@ And a `PaperTrail::Version` instance has these methods:
|
|
|
146
188
|
# Returns the item restored from this version.
|
|
147
189
|
version.reify(options = {})
|
|
148
190
|
|
|
191
|
+
# Return a new item from this version
|
|
192
|
+
version.reify(dup: true)
|
|
193
|
+
|
|
149
194
|
# Returns who put the item into the state stored in this version.
|
|
150
|
-
version.
|
|
195
|
+
version.paper_trail_originator
|
|
151
196
|
|
|
152
197
|
# Returns who changed the item from the state it had in this version.
|
|
153
198
|
version.terminator
|
|
@@ -165,6 +210,15 @@ version.index
|
|
|
165
210
|
|
|
166
211
|
# Returns the event that caused this version (create|update|destroy).
|
|
167
212
|
version.event
|
|
213
|
+
|
|
214
|
+
# Query versions objects by attributes.
|
|
215
|
+
PaperTrail::Version.where_object(attr1: val1, attr2: val2)
|
|
216
|
+
|
|
217
|
+
# Query versions object_changes field by attributes (requires
|
|
218
|
+
# `object_changes` column on versions table).
|
|
219
|
+
# Also can't guarantee consistent query results for numeric values
|
|
220
|
+
# due to limitations of SQL wildcard matchers against the serialized objects.
|
|
221
|
+
PaperTrail::Version.where_object_changes(attr1: val1)
|
|
168
222
|
```
|
|
169
223
|
|
|
170
224
|
In your controllers you can override these methods:
|
|
@@ -179,110 +233,70 @@ user_for_paper_trail
|
|
|
179
233
|
info_for_paper_trail
|
|
180
234
|
```
|
|
181
235
|
|
|
182
|
-
##
|
|
236
|
+
## Choosing Lifecycle Events To Monitor
|
|
183
237
|
|
|
184
|
-
|
|
238
|
+
You can choose which events to track with the `on` option. For example, to
|
|
239
|
+
ignore `create` events:
|
|
185
240
|
|
|
186
241
|
```ruby
|
|
187
|
-
class
|
|
188
|
-
has_paper_trail
|
|
242
|
+
class Article < ActiveRecord::Base
|
|
243
|
+
has_paper_trail :on => [:update, :destroy]
|
|
189
244
|
end
|
|
190
245
|
```
|
|
191
246
|
|
|
192
|
-
|
|
247
|
+
`has_paper_trail` installs callbacks for these lifecycle events. If there are
|
|
248
|
+
other callbacks in your model, their order relative to those installed by
|
|
249
|
+
PaperTrail may matter, so be aware of any potential interactions.
|
|
193
250
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
Once you have a version, you can find out what happened:
|
|
251
|
+
You may also have the `PaperTrail::Version` model save a custom string in it's
|
|
252
|
+
`event` field instead of the typical `create`, `update`, `destroy`. PaperTrail
|
|
253
|
+
supplies a custom accessor method called `paper_trail_event`, which it will
|
|
254
|
+
attempt to use to fill the `event` field before falling back on one of the
|
|
255
|
+
default events.
|
|
200
256
|
|
|
201
257
|
```ruby
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
258
|
+
a = Article.create
|
|
259
|
+
a.versions.size # 1
|
|
260
|
+
a.versions.last.event # 'create'
|
|
261
|
+
a.paper_trail_event = 'update title'
|
|
262
|
+
a.update_attributes :title => 'My Title'
|
|
263
|
+
a.versions.size # 2
|
|
264
|
+
a.versions.last.event # 'update title'
|
|
265
|
+
a.paper_trail_event = nil
|
|
266
|
+
a.update_attributes :title => "Alternate"
|
|
267
|
+
a.versions.size # 3
|
|
268
|
+
a.versions.last.event # 'update'
|
|
210
269
|
```
|
|
211
270
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
```ruby
|
|
215
|
-
>> widget = Widget.find 153
|
|
216
|
-
>> widget.name # 'Doobly'
|
|
217
|
-
|
|
218
|
-
# Add has_paper_trail to Widget model.
|
|
219
|
-
|
|
220
|
-
>> widget.versions # []
|
|
221
|
-
>> widget.update_attributes :name => 'Wotsit'
|
|
222
|
-
>> widget.versions.last.reify.name # 'Doobly'
|
|
223
|
-
>> widget.versions.last.event # 'update'
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
This also means that PaperTrail does not waste space storing a version of the object as it currently stands. The `versions` method gives you previous versions; to get the current one just call a finder on your `Widget` model as usual.
|
|
227
|
-
|
|
228
|
-
Here's a helpful table showing what PaperTrail stores:
|
|
229
|
-
|
|
230
|
-
<table>
|
|
231
|
-
<tr>
|
|
232
|
-
<th>Event</th>
|
|
233
|
-
<th>Model Before</th>
|
|
234
|
-
<th>Model After</th>
|
|
235
|
-
</tr>
|
|
236
|
-
<tr>
|
|
237
|
-
<td>create</td>
|
|
238
|
-
<td>nil</td>
|
|
239
|
-
<td>widget</td>
|
|
240
|
-
</tr>
|
|
241
|
-
<tr>
|
|
242
|
-
<td>update</td>
|
|
243
|
-
<td>widget</td>
|
|
244
|
-
<td>widget'</td>
|
|
245
|
-
<tr>
|
|
246
|
-
<td>destroy</td>
|
|
247
|
-
<td>widget</td>
|
|
248
|
-
<td>nil</td>
|
|
249
|
-
</tr>
|
|
250
|
-
</table>
|
|
251
|
-
|
|
252
|
-
PaperTrail stores the values in the Model Before column. Most other auditing/versioning plugins store the After column.
|
|
271
|
+
### Controlling the Order of AR Callbacks
|
|
253
272
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
You can choose which events to track with the `on` option. For example, to ignore `create` events:
|
|
273
|
+
The `has_paper_trail` method installs AR callbacks. If you need to control
|
|
274
|
+
their order, use the `paper_trail_on_*` methods.
|
|
258
275
|
|
|
259
276
|
```ruby
|
|
260
277
|
class Article < ActiveRecord::Base
|
|
261
|
-
has_paper_trail :on => [:update, :destroy]
|
|
262
|
-
end
|
|
263
|
-
```
|
|
264
278
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
279
|
+
# Include PaperTrail, but do not add any callbacks yet. Passing the
|
|
280
|
+
# empty array to `:on` omits callbacks.
|
|
281
|
+
has_paper_trail :on => []
|
|
268
282
|
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
>> a.update_attributes :title => 'My Title'
|
|
275
|
-
>> a.versions.size # 2
|
|
276
|
-
>> a.versions.last.event # 'update title'
|
|
277
|
-
>> a.paper_trail_event = nil
|
|
278
|
-
>> a.update_attributes :title => "Alternate"
|
|
279
|
-
>> a.versions.size # 3
|
|
280
|
-
>> a.versions.last.event # 'update'
|
|
283
|
+
# Add callbacks in the order you need.
|
|
284
|
+
paper_trail_on_destroy # add destroy callback
|
|
285
|
+
paper_trail_on_update # etc.
|
|
286
|
+
paper_trail_on_create
|
|
287
|
+
end
|
|
281
288
|
```
|
|
282
289
|
|
|
290
|
+
The `paper_trail_on_destroy` method can be further configured to happen
|
|
291
|
+
`:before` or `:after` the destroy event. In PaperTrail 4, the default is
|
|
292
|
+
`:after`. In PaperTrail 5, the default will be `:before`, to support
|
|
293
|
+
ActiveRecord 5. (see https://github.com/airblade/paper_trail/pull/683)
|
|
294
|
+
|
|
283
295
|
## Choosing When To Save New Versions
|
|
284
296
|
|
|
285
|
-
You can choose the conditions when to add new versions with the `if` and
|
|
297
|
+
You can choose the conditions when to add new versions with the `if` and
|
|
298
|
+
`unless` options. For example, to save versions only for US non-draft
|
|
299
|
+
translations:
|
|
286
300
|
|
|
287
301
|
```ruby
|
|
288
302
|
class Translation < ActiveRecord::Base
|
|
@@ -302,16 +316,19 @@ class Article < ActiveRecord::Base
|
|
|
302
316
|
end
|
|
303
317
|
```
|
|
304
318
|
|
|
305
|
-
This means that changes to just the `title` or `rating` will not store another
|
|
319
|
+
This means that changes to just the `title` or `rating` will not store another
|
|
320
|
+
version of the article. It does not mean that the `title` and `rating`
|
|
321
|
+
attributes will be ignored if some other change causes a new
|
|
322
|
+
`PaperTrail::Version` to be created. For example:
|
|
306
323
|
|
|
307
324
|
```ruby
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
325
|
+
a = Article.create
|
|
326
|
+
a.versions.length # 1
|
|
327
|
+
a.update_attributes :title => 'My Title', :rating => 3
|
|
328
|
+
a.versions.length # 1
|
|
329
|
+
a.update_attributes :title => 'Greeting', :content => 'Hello'
|
|
330
|
+
a.versions.length # 2
|
|
331
|
+
a.previous_version.title # 'My Title'
|
|
315
332
|
```
|
|
316
333
|
|
|
317
334
|
Or, you can specify a list of all attributes you care about:
|
|
@@ -325,13 +342,13 @@ end
|
|
|
325
342
|
This means that only changes to the `title` will save a version of the article:
|
|
326
343
|
|
|
327
344
|
```ruby
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
345
|
+
a = Article.create
|
|
346
|
+
a.versions.length # 1
|
|
347
|
+
a.update_attributes :title => 'My Title'
|
|
348
|
+
a.versions.length # 2
|
|
349
|
+
a.update_attributes :content => 'Hello'
|
|
350
|
+
a.versions.length # 2
|
|
351
|
+
a.previous_version.content # nil
|
|
335
352
|
```
|
|
336
353
|
|
|
337
354
|
The `:ignore` and `:only` options can also accept `Hash` arguments, where the :
|
|
@@ -342,26 +359,31 @@ class Article < ActiveRecord::Base
|
|
|
342
359
|
end
|
|
343
360
|
```
|
|
344
361
|
|
|
345
|
-
This means that if the `title` is not blank, then only changes to the `title`
|
|
362
|
+
This means that if the `title` is not blank, then only changes to the `title`
|
|
363
|
+
will save a version of the article:
|
|
346
364
|
|
|
347
365
|
```ruby
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
366
|
+
a = Article.create
|
|
367
|
+
a.versions.length # 1
|
|
368
|
+
a.update_attributes :content => 'Hello'
|
|
369
|
+
a.versions.length # 2
|
|
370
|
+
a.update_attributes :title => 'My Title'
|
|
371
|
+
a.versions.length # 3
|
|
372
|
+
a.update_attributes :content => 'Hai'
|
|
373
|
+
a.versions.length # 3
|
|
374
|
+
a.previous_version.content # "Hello"
|
|
375
|
+
a.update_attributes :title => 'Dif Title'
|
|
376
|
+
a.versions.length # 4
|
|
377
|
+
a.previous_version.content # "Hai"
|
|
360
378
|
```
|
|
361
379
|
|
|
362
|
-
Passing both `:ignore` and `:only` options will result in the article being
|
|
380
|
+
Passing both `:ignore` and `:only` options will result in the article being
|
|
381
|
+
saved if a changed attribute is included in `:only` but not in `:ignore`.
|
|
363
382
|
|
|
364
|
-
You can skip fields altogether with the `:skip` option. As with `:ignore`,
|
|
383
|
+
You can skip fields altogether with the `:skip` option. As with `:ignore`,
|
|
384
|
+
updates to these fields will not create a new `PaperTrail::Version`. In
|
|
385
|
+
addition, these fields will not be included in the serialized version of the
|
|
386
|
+
object whenever a new `PaperTrail::Version` is created.
|
|
365
387
|
|
|
366
388
|
For example:
|
|
367
389
|
|
|
@@ -371,226 +393,384 @@ class Article < ActiveRecord::Base
|
|
|
371
393
|
end
|
|
372
394
|
```
|
|
373
395
|
|
|
374
|
-
##
|
|
396
|
+
## Turning PaperTrail Off/On
|
|
375
397
|
|
|
376
|
-
|
|
398
|
+
Sometimes you don't want to store changes. Perhaps you are only interested in
|
|
399
|
+
changes made by your users and don't need to store changes you make yourself in,
|
|
400
|
+
say, a migration -- or when testing your application.
|
|
401
|
+
|
|
402
|
+
You can turn PaperTrail on or off in three ways: globally, per request, or per
|
|
403
|
+
class.
|
|
404
|
+
|
|
405
|
+
### Globally
|
|
406
|
+
|
|
407
|
+
On a global level you can turn PaperTrail off like this:
|
|
377
408
|
|
|
378
409
|
```ruby
|
|
379
|
-
|
|
380
|
-
>> widget.update_attributes :name => 'Blah blah'
|
|
381
|
-
# Time passes....
|
|
382
|
-
>> widget = widget.previous_version # the widget as it was before the update
|
|
383
|
-
>> widget.save # reverted
|
|
410
|
+
PaperTrail.enabled = false
|
|
384
411
|
```
|
|
385
412
|
|
|
386
|
-
|
|
413
|
+
For example, you might want to disable PaperTrail in your Rails application's
|
|
414
|
+
test environment to speed up your tests. This will do it (note: this gets done
|
|
415
|
+
automatically for `RSpec` and `Cucumber`, please see the [Testing
|
|
416
|
+
section](#testing)):
|
|
387
417
|
|
|
388
418
|
```ruby
|
|
389
|
-
|
|
390
|
-
|
|
419
|
+
# in config/environments/test.rb
|
|
420
|
+
config.after_initialize do
|
|
421
|
+
PaperTrail.enabled = false
|
|
422
|
+
end
|
|
391
423
|
```
|
|
392
424
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
Undeleting is just as simple:
|
|
425
|
+
If you disable PaperTrail in your test environment but want to enable it for
|
|
426
|
+
specific tests, you can add a helper like this to your test helper:
|
|
396
427
|
|
|
397
428
|
```ruby
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
429
|
+
# in test/test_helper.rb
|
|
430
|
+
def with_versioning
|
|
431
|
+
was_enabled = PaperTrail.enabled?
|
|
432
|
+
was_enabled_for_controller = PaperTrail.enabled_for_controller?
|
|
433
|
+
PaperTrail.enabled = true
|
|
434
|
+
PaperTrail.enabled_for_controller = true
|
|
435
|
+
begin
|
|
436
|
+
yield
|
|
437
|
+
ensure
|
|
438
|
+
PaperTrail.enabled = was_enabled
|
|
439
|
+
PaperTrail.enabled_for_controller = was_enabled_for_controller
|
|
440
|
+
end
|
|
441
|
+
end
|
|
403
442
|
```
|
|
404
443
|
|
|
405
|
-
|
|
444
|
+
And then use it in your tests like this:
|
|
406
445
|
|
|
446
|
+
```ruby
|
|
447
|
+
test "something that needs versioning" do
|
|
448
|
+
with_versioning do
|
|
449
|
+
# your test
|
|
450
|
+
end
|
|
451
|
+
end
|
|
452
|
+
```
|
|
407
453
|
|
|
408
|
-
|
|
454
|
+
### Per request
|
|
409
455
|
|
|
410
|
-
You can
|
|
456
|
+
You can turn PaperTrail on or off per request by adding a
|
|
457
|
+
`paper_trail_enabled_for_controller` method to your controller which returns
|
|
458
|
+
`true` or `false`:
|
|
411
459
|
|
|
412
460
|
```ruby
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
>> widget.next_version # live_widget
|
|
461
|
+
class ApplicationController < ActionController::Base
|
|
462
|
+
def paper_trail_enabled_for_controller
|
|
463
|
+
request.user_agent != 'Disable User-Agent'
|
|
464
|
+
end
|
|
465
|
+
end
|
|
419
466
|
```
|
|
420
467
|
|
|
421
|
-
|
|
468
|
+
### Per class
|
|
469
|
+
|
|
470
|
+
If you are about to change some widgets and you don't want a paper trail of your
|
|
471
|
+
changes, you can turn PaperTrail off like this:
|
|
422
472
|
|
|
423
473
|
```ruby
|
|
424
|
-
|
|
425
|
-
>> version = widget.versions[-2] # assuming widget has several versions
|
|
426
|
-
>> previous = version.previous
|
|
427
|
-
>> next = version.next
|
|
474
|
+
Widget.paper_trail_off!
|
|
428
475
|
```
|
|
429
476
|
|
|
430
|
-
|
|
477
|
+
And on again like this:
|
|
431
478
|
|
|
432
479
|
```ruby
|
|
433
|
-
|
|
480
|
+
Widget.paper_trail_on!
|
|
434
481
|
```
|
|
435
482
|
|
|
436
|
-
|
|
483
|
+
### Per method call
|
|
484
|
+
|
|
485
|
+
You can call a method without creating a new version using `without_versioning`.
|
|
486
|
+
It takes either a method name as a symbol:
|
|
437
487
|
|
|
438
488
|
```ruby
|
|
439
|
-
|
|
440
|
-
>> widget = latest_version.reify
|
|
441
|
-
>> widget.version == latest_version # true
|
|
489
|
+
@widget.without_versioning :destroy
|
|
442
490
|
```
|
|
443
491
|
|
|
444
|
-
|
|
492
|
+
Or a block:
|
|
445
493
|
|
|
446
494
|
```ruby
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
>> widget.live? # false
|
|
495
|
+
@widget.without_versioning do
|
|
496
|
+
@widget.update_attributes :name => 'Ford'
|
|
497
|
+
end
|
|
451
498
|
```
|
|
452
499
|
|
|
453
|
-
##
|
|
500
|
+
## Limiting the Number of Versions Created
|
|
454
501
|
|
|
455
|
-
|
|
502
|
+
Configure `version_limit` to cap the number of versions saved per record. This
|
|
503
|
+
does not apply to `create` events.
|
|
456
504
|
|
|
457
505
|
```ruby
|
|
458
|
-
|
|
459
|
-
|
|
506
|
+
# Limit: 4 versions per record (3 most recent, plus a `create` event)
|
|
507
|
+
PaperTrail.config.version_limit = 3
|
|
508
|
+
# Remove the limit
|
|
509
|
+
PaperTrail.config.version_limit = nil
|
|
460
510
|
```
|
|
461
511
|
|
|
462
|
-
|
|
512
|
+
## Reverting And Undeleting A Model
|
|
513
|
+
|
|
514
|
+
PaperTrail makes reverting to a previous version easy:
|
|
463
515
|
|
|
464
516
|
```ruby
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
517
|
+
widget = Widget.find 42
|
|
518
|
+
widget.update_attributes :name => 'Blah blah'
|
|
519
|
+
# Time passes....
|
|
520
|
+
widget = widget.previous_version # the widget as it was before the update
|
|
521
|
+
widget.save # reverted
|
|
470
522
|
```
|
|
471
523
|
|
|
472
|
-
|
|
524
|
+
Alternatively you can find the version at a given time:
|
|
473
525
|
|
|
474
526
|
```ruby
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
>> widget.versions.last.whodunnit # Andy Stewart
|
|
527
|
+
widget = widget.version_at(1.day.ago) # the widget as it was one day ago
|
|
528
|
+
widget.save # reverted
|
|
478
529
|
```
|
|
479
530
|
|
|
480
|
-
|
|
531
|
+
Note `version_at` gives you the object, not a version, so you don't need to call
|
|
532
|
+
`reify`.
|
|
533
|
+
|
|
534
|
+
Undeleting is just as simple:
|
|
481
535
|
|
|
482
536
|
```ruby
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
end
|
|
537
|
+
widget = Widget.find 42
|
|
538
|
+
widget.destroy
|
|
539
|
+
# Time passes....
|
|
540
|
+
widget = PaperTrail::Version.find(153).reify # the widget as it was before destruction
|
|
541
|
+
widget.save # the widget lives!
|
|
489
542
|
```
|
|
490
543
|
|
|
491
|
-
|
|
544
|
+
You could even use PaperTrail to implement an undo system, [Ryan Bates has!][3]
|
|
545
|
+
|
|
546
|
+
If your model uses [optimistic locking][1] don't forget to [increment your
|
|
547
|
+
`lock_version`][2] before saving or you'll get a `StaleObjectError`.
|
|
548
|
+
|
|
549
|
+
## Navigating Versions
|
|
550
|
+
|
|
551
|
+
You can call `previous_version` and `next_version` on an item to get it as it
|
|
552
|
+
was/became. Note that these methods reify the item for you.
|
|
492
553
|
|
|
493
554
|
```ruby
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
>> widget.versions.last.whodunnit # Andy Stewart
|
|
501
|
-
>> widget.whodunnit('Ben Atkins') { |w| w.update_attributes :name => 'Beth' } # this syntax also works
|
|
502
|
-
>> widget.versions.last.whodunnit # Ben Atkins
|
|
555
|
+
live_widget = Widget.find 42
|
|
556
|
+
live_widget.versions.length # 4 for example
|
|
557
|
+
widget = live_widget.previous_version # => widget == live_widget.versions.last.reify
|
|
558
|
+
widget = widget.previous_version # => widget == live_widget.versions[-2].reify
|
|
559
|
+
widget = widget.next_version # => widget == live_widget.versions.last.reify
|
|
560
|
+
widget.next_version # live_widget
|
|
503
561
|
```
|
|
504
562
|
|
|
505
|
-
|
|
563
|
+
If instead you have a particular `version` of an item you can navigate to the
|
|
564
|
+
previous and next versions.
|
|
506
565
|
|
|
507
|
-
|
|
566
|
+
```ruby
|
|
567
|
+
widget = Widget.find 42
|
|
568
|
+
version = widget.versions[-2] # assuming widget has several versions
|
|
569
|
+
previous = version.previous
|
|
570
|
+
next = version.next
|
|
571
|
+
```
|
|
572
|
+
|
|
573
|
+
You can find out which of an item's versions yours is:
|
|
508
574
|
|
|
509
575
|
```ruby
|
|
510
|
-
|
|
511
|
-
>> PaperTrail.whodunnit = 'Alice'
|
|
512
|
-
>> widget.update_attributes :name => 'Yankee'
|
|
513
|
-
>> widget.originator # 'Alice'
|
|
514
|
-
>> PaperTrail.whodunnit = 'Bob'
|
|
515
|
-
>> widget.update_attributes :name => 'Zulu'
|
|
516
|
-
>> widget.originator # 'Bob'
|
|
517
|
-
>> first_version, last_version = widget.versions.first, widget.versions.last
|
|
518
|
-
>> first_version.whodunnit # 'Alice'
|
|
519
|
-
>> first_version.originator # nil
|
|
520
|
-
>> first_version.terminator # 'Alice'
|
|
521
|
-
>> last_version.whodunnit # 'Bob'
|
|
522
|
-
>> last_version.originator # 'Alice'
|
|
523
|
-
>> last_version.terminator # 'Bob'
|
|
576
|
+
current_version_number = version.index # 0-based
|
|
524
577
|
```
|
|
525
578
|
|
|
526
|
-
|
|
579
|
+
If you got an item by reifying one of its versions, you can navigate back to the
|
|
580
|
+
version it came from:
|
|
527
581
|
|
|
528
|
-
|
|
582
|
+
```ruby
|
|
583
|
+
latest_version = Widget.find(42).versions.last
|
|
584
|
+
widget = latest_version.reify
|
|
585
|
+
widget.version == latest_version # true
|
|
586
|
+
```
|
|
587
|
+
|
|
588
|
+
You can find out whether a model instance is the current, live one -- or whether
|
|
589
|
+
it came instead from a previous version -- with `live?`:
|
|
529
590
|
|
|
530
591
|
```ruby
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
592
|
+
widget = Widget.find 42
|
|
593
|
+
widget.live? # true
|
|
594
|
+
widget = widget.previous_version
|
|
595
|
+
widget.live? # false
|
|
596
|
+
```
|
|
535
597
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
598
|
+
And you can perform `WHERE` queries for object versions based on attributes:
|
|
599
|
+
|
|
600
|
+
```ruby
|
|
601
|
+
# All versions that meet these criteria.
|
|
602
|
+
PaperTrail::Version.where_object(content: "Hello", title: "Article")
|
|
539
603
|
```
|
|
540
604
|
|
|
541
|
-
|
|
605
|
+
## Diffing Versions
|
|
606
|
+
|
|
607
|
+
There are two scenarios: diffing adjacent versions and diffing non-adjacent
|
|
608
|
+
versions.
|
|
609
|
+
|
|
610
|
+
The best way to diff adjacent versions is to get PaperTrail to do it for you.
|
|
611
|
+
If you add an `object_changes` text column to your `versions` table, either at
|
|
612
|
+
installation time with the `rails generate paper_trail:install --with-changes`
|
|
613
|
+
option or manually, PaperTrail will store the `changes` diff (excluding any
|
|
614
|
+
attributes PaperTrail is ignoring) in each `update` version. You can use the
|
|
615
|
+
`version.changeset` method to retrieve it. For example:
|
|
616
|
+
|
|
617
|
+
```ruby
|
|
618
|
+
widget = Widget.create :name => 'Bob'
|
|
619
|
+
widget.versions.last.changeset
|
|
620
|
+
# {
|
|
621
|
+
# "name"=>[nil, "Bob"],
|
|
622
|
+
# "created_at"=>[nil, 2015-08-10 04:10:40 UTC],
|
|
623
|
+
# "updated_at"=>[nil, 2015-08-10 04:10:40 UTC],
|
|
624
|
+
# "id"=>[nil, 1]
|
|
625
|
+
# }
|
|
626
|
+
widget.update_attributes :name => 'Robert'
|
|
627
|
+
widget.versions.last.changeset
|
|
628
|
+
# {
|
|
629
|
+
# "name"=>["Bob", "Robert"],
|
|
630
|
+
# "updated_at"=>[2015-08-10 04:13:19 UTC, 2015-08-10 04:13:19 UTC]
|
|
631
|
+
# }
|
|
632
|
+
widget.destroy
|
|
633
|
+
widget.versions.last.changeset
|
|
634
|
+
# {}
|
|
635
|
+
```
|
|
636
|
+
|
|
637
|
+
The `object_changes` are only stored for creation and updates, not when an
|
|
638
|
+
object is destroyed.
|
|
639
|
+
|
|
640
|
+
Please be aware that PaperTrail doesn't use diffs internally. When I designed
|
|
641
|
+
PaperTrail I wanted simplicity and robustness so I decided to make each version
|
|
642
|
+
of an object self-contained. A version stores all of its object's data, not a
|
|
643
|
+
diff from the previous version. This means you can delete any version without
|
|
644
|
+
affecting any other.
|
|
645
|
+
|
|
646
|
+
To diff non-adjacent versions you'll have to write your own code. These
|
|
647
|
+
libraries may help:
|
|
542
648
|
|
|
543
|
-
|
|
649
|
+
For diffing two strings:
|
|
650
|
+
|
|
651
|
+
* [htmldiff][19]: expects but doesn't require HTML input and produces HTML
|
|
652
|
+
output. Works very well but slows down significantly on large (e.g. 5,000
|
|
653
|
+
word) inputs.
|
|
654
|
+
* [differ][20]: expects plain text input and produces plain
|
|
655
|
+
text/coloured/HTML/any output. Can do character-wise, word-wise, line-wise,
|
|
656
|
+
or arbitrary-boundary-string-wise diffs. Works very well on non-HTML input.
|
|
657
|
+
* [diff-lcs][21]: old-school, line-wise diffs.
|
|
658
|
+
|
|
659
|
+
For diffing two ActiveRecord objects:
|
|
660
|
+
|
|
661
|
+
* [Jeremy Weiskotten's PaperTrail fork][22]: uses ActiveSupport's diff to return
|
|
662
|
+
an array of hashes of the changes.
|
|
663
|
+
* [activerecord-diff][23]: rather like ActiveRecord::Dirty but also allows you
|
|
664
|
+
to specify which columns to compare.
|
|
665
|
+
|
|
666
|
+
If you wish to selectively record changes for some models but not others you
|
|
667
|
+
can opt out of recording changes by passing `:save_changes => false` to your
|
|
668
|
+
`has_paper_trail` method declaration.
|
|
669
|
+
|
|
670
|
+
## Deleting Old Versions
|
|
671
|
+
|
|
672
|
+
Over time your `versions` table will grow to an unwieldy size. Because each
|
|
673
|
+
version is self-contained (see the Diffing section above for more) you can
|
|
674
|
+
simply delete any records you don't want any more. For example:
|
|
675
|
+
|
|
676
|
+
```sql
|
|
677
|
+
sql> delete from versions where created_at < 2010-06-01;
|
|
678
|
+
```
|
|
544
679
|
|
|
545
680
|
```ruby
|
|
546
|
-
|
|
547
|
-
self.table_name = :post_versions
|
|
548
|
-
self.sequence_name = :post_version_id_seq
|
|
549
|
-
end
|
|
681
|
+
PaperTrail::Version.delete_all ["created_at < ?", 1.week.ago]
|
|
550
682
|
```
|
|
551
683
|
|
|
552
|
-
|
|
684
|
+
## Finding Out Who Was Responsible For A Change
|
|
553
685
|
|
|
554
|
-
If
|
|
686
|
+
If your `ApplicationController` has a `current_user` method, PaperTrail will
|
|
687
|
+
attempt to store the value returned by `current_user.id` in the version's
|
|
688
|
+
`whodunnit` column.
|
|
555
689
|
|
|
556
|
-
|
|
690
|
+
You may want PaperTrail to call a different method to find out who is
|
|
691
|
+
responsible. To do so, override the `user_for_paper_trail` method in your
|
|
692
|
+
controller like this:
|
|
557
693
|
|
|
558
694
|
```ruby
|
|
559
|
-
|
|
560
|
-
|
|
695
|
+
class ApplicationController
|
|
696
|
+
def user_for_paper_trail
|
|
697
|
+
logged_in? ? current_member.id : 'Public user' # or whatever
|
|
698
|
+
end
|
|
561
699
|
end
|
|
562
700
|
```
|
|
563
701
|
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
You can also specify custom names for the versions and version associations. This is useful if you already have `versions` or/and `version` methods on your model. For example:
|
|
702
|
+
In a console session you can manually set who is responsible like this:
|
|
567
703
|
|
|
568
704
|
```ruby
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
705
|
+
PaperTrail.whodunnit = 'Andy Stewart'
|
|
706
|
+
widget.update_attributes :name => 'Wibble'
|
|
707
|
+
widget.versions.last.whodunnit # Andy Stewart
|
|
708
|
+
```
|
|
572
709
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
710
|
+
See also: [Setting whodunnit in the rails console][33]
|
|
711
|
+
|
|
712
|
+
Sometimes you want to define who is responsible for a change in a small scope
|
|
713
|
+
without overwriting value of `PaperTrail.whodunnit`. It is possible to define
|
|
714
|
+
the `whodunnit` value for an operation inside a block like this:
|
|
715
|
+
|
|
716
|
+
```ruby
|
|
717
|
+
PaperTrail.whodunnit = 'Andy Stewart'
|
|
718
|
+
widget.whodunnit('Lucas Souza') do
|
|
719
|
+
widget.update_attributes :name => 'Wibble'
|
|
581
720
|
end
|
|
721
|
+
widget.versions.last.whodunnit # Lucas Souza
|
|
722
|
+
widget.update_attributes :name => 'Clair'
|
|
723
|
+
widget.versions.last.whodunnit # Andy Stewart
|
|
724
|
+
widget.whodunnit('Ben Atkins') { |w| w.update_attributes :name => 'Beth' } # this syntax also works
|
|
725
|
+
widget.versions.last.whodunnit # Ben Atkins
|
|
726
|
+
```
|
|
727
|
+
|
|
728
|
+
A version's `whodunnit` records who changed the object causing the `version` to
|
|
729
|
+
be stored. Because a version stores the object as it looked before the change
|
|
730
|
+
(see the table above), `whodunnit` returns who stopped the object looking like
|
|
731
|
+
this -- not who made it look like this. Hence `whodunnit` is aliased as
|
|
732
|
+
`terminator`.
|
|
733
|
+
|
|
734
|
+
To find out who made a version's object look that way, use
|
|
735
|
+
`version.paper_trail_originator`. And to find out who made a "live" object look
|
|
736
|
+
like it does, call `paper_trail_originator` on the object.
|
|
737
|
+
|
|
738
|
+
```ruby
|
|
739
|
+
widget = Widget.find 153 # assume widget has 0 versions
|
|
740
|
+
PaperTrail.whodunnit = 'Alice'
|
|
741
|
+
widget.update_attributes :name => 'Yankee'
|
|
742
|
+
widget.paper_trail_originator # 'Alice'
|
|
743
|
+
PaperTrail.whodunnit = 'Bob'
|
|
744
|
+
widget.update_attributes :name => 'Zulu'
|
|
745
|
+
widget.paper_trail_originator # 'Bob'
|
|
746
|
+
first_version, last_version = widget.versions.first, widget.versions.last
|
|
747
|
+
first_version.whodunnit # 'Alice'
|
|
748
|
+
first_version.paper_trail_originator # nil
|
|
749
|
+
first_version.terminator # 'Alice'
|
|
750
|
+
last_version.whodunnit # 'Bob'
|
|
751
|
+
last_version.paper_trail_originator # 'Alice'
|
|
752
|
+
last_version.terminator # 'Bob'
|
|
582
753
|
```
|
|
583
754
|
|
|
584
755
|
## Associations
|
|
585
756
|
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
If you can think of a good way to achieve this, please let me know.
|
|
589
|
-
|
|
757
|
+
**Experimental feature**, see caveats below.
|
|
590
758
|
|
|
591
|
-
|
|
759
|
+
PaperTrail can restore three types of associations: Has-One, Has-Many, and
|
|
760
|
+
Has-Many-Through. In order to do this, you will need to create a
|
|
761
|
+
`version_associations` table, either at installation time with the `rails
|
|
762
|
+
generate paper_trail:install --with-associations` option or manually. PaperTrail
|
|
763
|
+
will store in that table additional information to correlate versions of the
|
|
764
|
+
association and versions of the model when the associated record is changed.
|
|
765
|
+
When reifying the model, PaperTrail can use this table, together with the
|
|
766
|
+
`transaction_id` to find the correct version of the association and reify it.
|
|
767
|
+
The `transaction_id` is a unique id for version records created in the same
|
|
768
|
+
transaction. It is used to associate the version of the model and the version of
|
|
769
|
+
the association that are created in the same transaction.
|
|
592
770
|
|
|
593
|
-
|
|
771
|
+
To restore Has-One associations as they were at the time, pass option `:has_one
|
|
772
|
+
=> true` to `reify`. To restore Has-Many and Has-Many-Through associations, use
|
|
773
|
+
option `:has_many => true`. For example:
|
|
594
774
|
|
|
595
775
|
```ruby
|
|
596
776
|
class Location < ActiveRecord::Base
|
|
@@ -603,32 +783,90 @@ class Treasure < ActiveRecord::Base
|
|
|
603
783
|
has_paper_trail
|
|
604
784
|
end
|
|
605
785
|
|
|
606
|
-
|
|
607
|
-
|
|
786
|
+
treasure.amount # 100
|
|
787
|
+
treasure.location.latitude # 12.345
|
|
608
788
|
|
|
609
|
-
|
|
610
|
-
|
|
789
|
+
treasure.update_attributes :amount => 153
|
|
790
|
+
treasure.location.update_attributes :latitude => 54.321
|
|
611
791
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
792
|
+
t = treasure.versions.last.reify(:has_one => true)
|
|
793
|
+
t.amount # 100
|
|
794
|
+
t.location.latitude # 12.345
|
|
615
795
|
```
|
|
616
796
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
797
|
+
If the parent and child are updated in one go, PaperTrail can use the
|
|
798
|
+
aforementioned `transaction_id` to reify the models as they were before the
|
|
799
|
+
transaction (instead of before the update to the model).
|
|
620
800
|
|
|
621
801
|
```ruby
|
|
622
|
-
|
|
623
|
-
|
|
802
|
+
treasure.amount # 100
|
|
803
|
+
treasure.location.latitude # 12.345
|
|
804
|
+
|
|
805
|
+
Treasure.transaction do
|
|
806
|
+
treasure.location.update_attributes :latitude => 54.321
|
|
807
|
+
treasure.update_attributes :amount => 153
|
|
808
|
+
end
|
|
624
809
|
|
|
625
|
-
|
|
810
|
+
t = treasure.versions.last.reify(:has_one => true)
|
|
811
|
+
t.amount # 100
|
|
812
|
+
t.location.latitude # 12.345, instead of 54.321
|
|
813
|
+
```
|
|
626
814
|
|
|
815
|
+
By default, PaperTrail excludes an associated record from the reified parent
|
|
816
|
+
model if the associated record exists in the live model but did not exist as at
|
|
817
|
+
the time the version was created. This is usually what you want if you just want
|
|
818
|
+
to look at the reified version. But if you want to persist it, it would be
|
|
819
|
+
better to pass in option `:mark_for_destruction => true` so that the associated
|
|
820
|
+
record is included and marked for destruction. Note that `mark_for_destruction`
|
|
821
|
+
only has [an effect on associations marked with `autosave: true`][32].
|
|
627
822
|
|
|
823
|
+
```ruby
|
|
824
|
+
class Widget < ActiveRecord::Base
|
|
825
|
+
has_paper_trail
|
|
826
|
+
has_one :wotsit, autosave: true
|
|
827
|
+
end
|
|
628
828
|
|
|
629
|
-
|
|
829
|
+
class Wotsit < ActiveRecord::Base
|
|
830
|
+
has_paper_trail
|
|
831
|
+
belongs_to :widget
|
|
832
|
+
end
|
|
630
833
|
|
|
631
|
-
|
|
834
|
+
widget = Widget.create(:name => 'widget_0')
|
|
835
|
+
widget.update_attributes(:name => 'widget_1')
|
|
836
|
+
widget.create_wotsit(:name => 'wotsit')
|
|
837
|
+
|
|
838
|
+
widget_0 = widget.versions.last.reify(:has_one => true)
|
|
839
|
+
widget_0.wotsit # nil
|
|
840
|
+
|
|
841
|
+
widget_0 = widget.versions.last.reify(:has_one => true, :mark_for_destruction => true)
|
|
842
|
+
widget_0.wotsit.marked_for_destruction? # true
|
|
843
|
+
widget_0.save!
|
|
844
|
+
widget.reload.wotsit # nil
|
|
845
|
+
```
|
|
846
|
+
|
|
847
|
+
**Caveats:**
|
|
848
|
+
|
|
849
|
+
1. Not compatible with [transactional tests][34], aka. transactional fixtures.
|
|
850
|
+
This is a known issue [#542](https://github.com/airblade/paper_trail/issues/542)
|
|
851
|
+
that we'd like to solve.
|
|
852
|
+
1. Requires database timestamp columns with fractional second precision.
|
|
853
|
+
- Sqlite and postgres timestamps have fractional second precision by default.
|
|
854
|
+
[MySQL timestamps do not][35]. Furthermore, MySQL 5.5 and earlier do not
|
|
855
|
+
support fractional second precision at all.
|
|
856
|
+
- Also, support for fractional seconds in MySQL was not added to
|
|
857
|
+
rails until ActiveRecord 4.2 (https://github.com/rails/rails/pull/14359).
|
|
858
|
+
1. PaperTrail can't restore an association properly if the association record
|
|
859
|
+
can be updated to replace its parent model (by replacing the foreign key)
|
|
860
|
+
1. Currently PaperTrail only support single `version_associations` table. The
|
|
861
|
+
implication is that you can only use a single table to store the versions for
|
|
862
|
+
all related models. Sorry for those who use multiple version tables.
|
|
863
|
+
1. PaperTrail only reifies the first level of associations, i.e., it does not
|
|
864
|
+
reify any associations of its associations, and so on.
|
|
865
|
+
1. PaperTrail relies on the callbacks on the association model (and the :through
|
|
866
|
+
association model for Has-Many-Through associations) to record the versions
|
|
867
|
+
and the relationship between the versions. If the association is changed
|
|
868
|
+
without invoking the callbacks, Reification won't work. Below are some
|
|
869
|
+
examples:
|
|
632
870
|
|
|
633
871
|
Given these models:
|
|
634
872
|
|
|
@@ -655,21 +893,23 @@ end
|
|
|
655
893
|
Then each of the following will store authorship versions:
|
|
656
894
|
|
|
657
895
|
```ruby
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
896
|
+
@book.authors << @dostoyevsky
|
|
897
|
+
@book.authors.create :name => 'Tolstoy'
|
|
898
|
+
@book.authorships.last.destroy
|
|
899
|
+
@book.authorships.clear
|
|
900
|
+
@book.author_ids = [@solzhenistyn.id, @dostoyevsky.id]
|
|
662
901
|
```
|
|
663
902
|
|
|
664
903
|
But none of these will:
|
|
665
904
|
|
|
666
905
|
```ruby
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
906
|
+
@book.authors.delete @tolstoy
|
|
907
|
+
@book.author_ids = []
|
|
908
|
+
@book.authors = []
|
|
670
909
|
```
|
|
671
910
|
|
|
672
|
-
Having said that, you can apparently get all these working (I haven't tested it
|
|
911
|
+
Having said that, you can apparently get all these working (I haven't tested it
|
|
912
|
+
myself) with this patch:
|
|
673
913
|
|
|
674
914
|
```ruby
|
|
675
915
|
# In config/initializers/active_record_patch.rb
|
|
@@ -688,13 +928,9 @@ module ActiveRecord
|
|
|
688
928
|
end
|
|
689
929
|
```
|
|
690
930
|
|
|
691
|
-
See [issue 113]
|
|
692
|
-
|
|
693
|
-
There may be a way to store authorship versions, probably using association callbacks, no matter how the collection is manipulated but I haven't found it yet. Let me know if you do.
|
|
931
|
+
See [issue 113][16] for a discussion about this.
|
|
694
932
|
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
## Storing metadata
|
|
933
|
+
## Storing Metadata
|
|
698
934
|
|
|
699
935
|
You can store arbitrary model-level metadata alongside each version like this:
|
|
700
936
|
|
|
@@ -710,33 +946,29 @@ class Article < ActiveRecord::Base
|
|
|
710
946
|
end
|
|
711
947
|
```
|
|
712
948
|
|
|
713
|
-
PaperTrail will call your proc with the current article and store the result in
|
|
714
|
-
|
|
715
|
-
|
|
949
|
+
PaperTrail will call your proc with the current article and store the result in
|
|
950
|
+
the `author_id` column of the `versions` table.
|
|
951
|
+
Don't forget to add any such columns to your `versions` table.
|
|
716
952
|
|
|
717
|
-
|
|
718
|
-
* Declare your metadata columns using `attr_accessible`. (If you are using `ActiveRecord 3`, or `ActiveRecord 4` with the [ProtectedAttributes](https://github.com/rails/protected_attributes) gem)
|
|
953
|
+
### Advantages of Metadata
|
|
719
954
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
end
|
|
728
|
-
end
|
|
729
|
-
```
|
|
730
|
-
|
|
731
|
-
Why would you do this? In this example, `author_id` is an attribute of `Article` and PaperTrail will store it anyway in a serialized form in the `object` column of the `version` record. But let's say you wanted to pull out all versions for a particular author; without the metadata you would have to deserialize (reify) each `version` object to see if belonged to the author in question. Clearly this is inefficient. Using the metadata you can find just those versions you want:
|
|
955
|
+
Why would you do this? In this example, `author_id` is an attribute of
|
|
956
|
+
`Article` and PaperTrail will store it anyway in a serialized form in the
|
|
957
|
+
`object` column of the `version` record. But let's say you wanted to pull out
|
|
958
|
+
all versions for a particular author; without the metadata you would have to
|
|
959
|
+
deserialize (reify) each `version` object to see if belonged to the author in
|
|
960
|
+
question. Clearly this is inefficient. Using the metadata you can find just
|
|
961
|
+
those versions you want:
|
|
732
962
|
|
|
733
963
|
```ruby
|
|
734
964
|
PaperTrail::Version.where(:author_id => author_id)
|
|
735
965
|
```
|
|
736
966
|
|
|
737
|
-
|
|
967
|
+
### Metadata from Controllers
|
|
738
968
|
|
|
739
|
-
You can also store any information you like from your controller.
|
|
969
|
+
You can also store any information you like from your controller. Override
|
|
970
|
+
the `info_for_paper_trail` method in your controller to return a hash whose keys
|
|
971
|
+
correspond to columns in your `versions` table.
|
|
740
972
|
|
|
741
973
|
```ruby
|
|
742
974
|
class ApplicationController
|
|
@@ -746,215 +978,271 @@ class ApplicationController
|
|
|
746
978
|
end
|
|
747
979
|
```
|
|
748
980
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
**NOTE FOR RAILS 4:** If you're using [Strong Parameters](https://github.com/rails/strong_parameters) in Rails 4 and have *not* included the `protected_attributes` gem, there's no need to declare your metadata columns using `attr_accessible`.
|
|
981
|
+
### Protected Attributes and Metadata
|
|
752
982
|
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
There are two scenarios: diffing adjacent versions and diffing non-adjacent versions.
|
|
757
|
-
|
|
758
|
-
The best way to diff adjacent versions is to get PaperTrail to do it for you. If you add an `object_changes` text column to your `versions` table, either at installation time with the `rails generate paper_trail:install --with-changes` option or manually, PaperTrail will store the `changes` diff (excluding any attributes PaperTrail is ignoring) in each `update` version. You can use the `version.changeset` method to retrieve it. For example:
|
|
983
|
+
If you are using rails 3 or the [protected_attributes][17] gem you must declare
|
|
984
|
+
your metadata columns to be `attr_accessible`.
|
|
759
985
|
|
|
760
986
|
```ruby
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
987
|
+
# app/models/paper_trail/version.rb
|
|
988
|
+
module PaperTrail
|
|
989
|
+
class Version < ActiveRecord::Base
|
|
990
|
+
include PaperTrail::VersionConcern
|
|
991
|
+
attr_accessible :author_id, :word_count, :answer
|
|
992
|
+
end
|
|
993
|
+
end
|
|
767
994
|
```
|
|
768
995
|
|
|
769
|
-
|
|
996
|
+
If you're using [strong_parameters][18] instead of [protected_attributes][17]
|
|
997
|
+
then there is no need to use `attr_accessible`.
|
|
770
998
|
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
To diff non-adjacent versions you'll have to write your own code. These libraries may help:
|
|
774
|
-
|
|
775
|
-
For diffing two strings:
|
|
999
|
+
## Custom Version Classes
|
|
776
1000
|
|
|
777
|
-
|
|
778
|
-
* [differ](http://github.com/pvande/differ): expects plain text input and produces plain text/coloured/HTML/any output. Can do character-wise, word-wise, line-wise, or arbitrary-boundary-string-wise diffs. Works very well on non-HTML input.
|
|
779
|
-
* [diff-lcs](https://github.com/halostatue/diff-lcs): old-school, line-wise diffs.
|
|
1001
|
+
You can specify custom version subclasses with the `:class_name` option:
|
|
780
1002
|
|
|
781
|
-
|
|
1003
|
+
```ruby
|
|
1004
|
+
class PostVersion < PaperTrail::Version
|
|
1005
|
+
# custom behaviour, e.g:
|
|
1006
|
+
self.table_name = :post_versions
|
|
1007
|
+
end
|
|
782
1008
|
|
|
783
|
-
|
|
784
|
-
|
|
1009
|
+
class Post < ActiveRecord::Base
|
|
1010
|
+
has_paper_trail :class_name => 'PostVersion'
|
|
1011
|
+
end
|
|
1012
|
+
```
|
|
785
1013
|
|
|
1014
|
+
Unlike ActiveRecord's `class_name`, you'll have to supply the complete module path to the class (e.g. `Foo::BarVersion` if your class is inside the module `Foo`).
|
|
786
1015
|
|
|
787
|
-
|
|
1016
|
+
### Advantages
|
|
788
1017
|
|
|
789
|
-
|
|
1018
|
+
1. For models which have a lot of versions, storing each model's versions in a
|
|
1019
|
+
separate table can improve the performance of certain database queries.
|
|
1020
|
+
1. Store different version [metadata](#storing-metadata) for different models.
|
|
790
1021
|
|
|
791
|
-
|
|
1022
|
+
### Configuration
|
|
792
1023
|
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
On a global level you can turn PaperTrail off like this:
|
|
1024
|
+
If you are using Postgres, you should also define the sequence that your custom
|
|
1025
|
+
version class will use:
|
|
796
1026
|
|
|
797
1027
|
```ruby
|
|
798
|
-
|
|
1028
|
+
class PostVersion < PaperTrail::Version
|
|
1029
|
+
self.table_name = :post_versions
|
|
1030
|
+
self.sequence_name = :post_versions_id_seq
|
|
1031
|
+
end
|
|
799
1032
|
```
|
|
800
1033
|
|
|
801
|
-
|
|
1034
|
+
If you only use custom version classes and don't have a `versions` table, you
|
|
1035
|
+
must let ActiveRecord know that the `PaperTrail::Version` class is an
|
|
1036
|
+
`abstract_class`.
|
|
802
1037
|
|
|
803
1038
|
```ruby
|
|
804
|
-
#
|
|
805
|
-
|
|
806
|
-
|
|
1039
|
+
# app/models/paper_trail/version.rb
|
|
1040
|
+
module PaperTrail
|
|
1041
|
+
class Version < ActiveRecord::Base
|
|
1042
|
+
include PaperTrail::VersionConcern
|
|
1043
|
+
self.abstract_class = true
|
|
1044
|
+
end
|
|
807
1045
|
end
|
|
808
1046
|
```
|
|
809
1047
|
|
|
810
|
-
|
|
1048
|
+
You can also specify custom names for the versions and version associations.
|
|
1049
|
+
This is useful if you already have `versions` or/and `version` methods on your
|
|
1050
|
+
model. For example:
|
|
811
1051
|
|
|
812
1052
|
```ruby
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1053
|
+
class Post < ActiveRecord::Base
|
|
1054
|
+
has_paper_trail :versions => :paper_trail_versions,
|
|
1055
|
+
:version => :paper_trail_version
|
|
1056
|
+
|
|
1057
|
+
# Existing versions method. We don't want to clash.
|
|
1058
|
+
def versions
|
|
1059
|
+
...
|
|
1060
|
+
end
|
|
1061
|
+
# Existing version method. We don't want to clash.
|
|
1062
|
+
def version
|
|
1063
|
+
...
|
|
824
1064
|
end
|
|
825
1065
|
end
|
|
826
1066
|
```
|
|
827
1067
|
|
|
828
|
-
|
|
1068
|
+
## Custom Serializer
|
|
1069
|
+
|
|
1070
|
+
By default, PaperTrail stores your changes as a `YAML` dump. You can override
|
|
1071
|
+
this with the serializer config option:
|
|
829
1072
|
|
|
830
1073
|
```ruby
|
|
831
|
-
|
|
832
|
-
with_versioning do
|
|
833
|
-
# your test
|
|
834
|
-
end
|
|
835
|
-
end
|
|
1074
|
+
PaperTrail.serializer = MyCustomSerializer
|
|
836
1075
|
```
|
|
837
1076
|
|
|
838
|
-
|
|
1077
|
+
A valid serializer is a `module` (or `class`) that defines a `load` and `dump`
|
|
1078
|
+
method. These serializers are included in the gem for your convenience:
|
|
1079
|
+
|
|
1080
|
+
* [PaperTrail::Serializers::YAML][24] - Default
|
|
1081
|
+
* [PaperTrail::Serializers::JSON][25]
|
|
839
1082
|
|
|
840
|
-
|
|
1083
|
+
### PostgreSQL JSON column type support
|
|
1084
|
+
|
|
1085
|
+
If you use PostgreSQL, and would like to store your `object` (and/or
|
|
1086
|
+
`object_changes`) data in a column of [type `json` or type `jsonb`][26], specify
|
|
1087
|
+
`json` instead of `text` for these columns in your migration:
|
|
841
1088
|
|
|
842
1089
|
```ruby
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
1090
|
+
create_table :versions do |t|
|
|
1091
|
+
...
|
|
1092
|
+
t.json :object # Full object changes
|
|
1093
|
+
t.json :object_changes # Optional column-level changes
|
|
1094
|
+
...
|
|
847
1095
|
end
|
|
848
1096
|
```
|
|
849
1097
|
|
|
850
|
-
|
|
1098
|
+
If you use the PostgreSQL `json` or `jsonb` column type, you do not need
|
|
1099
|
+
to specify a `PaperTrail.serializer`.
|
|
1100
|
+
|
|
1101
|
+
#### Convert existing YAML data to JSON
|
|
851
1102
|
|
|
852
|
-
If you
|
|
1103
|
+
If you've been using PaperTrail for a while with the default YAML serializer
|
|
1104
|
+
and you want to switch to JSON or JSONB, you're in a bit of a bind because
|
|
1105
|
+
there's no automatic way to migrate your data. The first (slow) option is to
|
|
1106
|
+
loop over every record and parse it in Ruby, then write to a temporary column:
|
|
853
1107
|
|
|
854
1108
|
```ruby
|
|
855
|
-
|
|
856
|
-
```
|
|
1109
|
+
add_column :versions, :object, :new_object, :jsonb # or :json
|
|
857
1110
|
|
|
858
|
-
|
|
1111
|
+
PaperTrail::Version.reset_column_information
|
|
1112
|
+
PaperTrail::Version.find_each do |version|
|
|
1113
|
+
version.update_column :new_object, YAML.load(version.object)
|
|
1114
|
+
end
|
|
859
1115
|
|
|
860
|
-
|
|
861
|
-
|
|
1116
|
+
remove_column :versions, :object
|
|
1117
|
+
rename_column :versions, :new_object, :object
|
|
862
1118
|
```
|
|
863
1119
|
|
|
864
|
-
|
|
1120
|
+
This technique can be very slow if you have a lot of data. Though slow, it is
|
|
1121
|
+
safe in databases where transactions are protected against DDL, such as
|
|
1122
|
+
Postgres. In databases without such protection, such as MySQL, a table lock may
|
|
1123
|
+
be necessary.
|
|
865
1124
|
|
|
866
|
-
|
|
1125
|
+
If the above technique is too slow for your needs, and you're okay doing without
|
|
1126
|
+
PaperTrail data temporarily, you can create the new column without a converting
|
|
1127
|
+
the data.
|
|
867
1128
|
|
|
868
1129
|
```ruby
|
|
869
|
-
|
|
1130
|
+
rename_column :versions, :object, :old_object
|
|
1131
|
+
add_column :versions, :object, :jsonb # or :json
|
|
870
1132
|
```
|
|
871
1133
|
|
|
872
|
-
|
|
1134
|
+
After that migration, your historical data still exists as YAML, and new data
|
|
1135
|
+
will be stored as JSON. Next, convert records from YAML to JSON using a
|
|
1136
|
+
background script.
|
|
873
1137
|
|
|
874
1138
|
```ruby
|
|
875
|
-
|
|
876
|
-
|
|
1139
|
+
PaperTrail::Version.where.not(old_object: nil).find_each do |version|
|
|
1140
|
+
version.update_columns old_object: nil, object: YAML.load(version.old_object)
|
|
877
1141
|
end
|
|
878
1142
|
```
|
|
879
1143
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
By default, PaperTrail stores your changes as a `YAML` dump. You can override this with the serializer config option:
|
|
1144
|
+
Finally, in another migration, remove the old column.
|
|
883
1145
|
|
|
884
1146
|
```ruby
|
|
885
|
-
|
|
1147
|
+
remove_column :versions, :old_object
|
|
886
1148
|
```
|
|
887
1149
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
* [YAML](https://github.com/airblade/paper_trail/blob/master/lib/paper_trail/serializers/yaml.rb) - Default
|
|
891
|
-
* [JSON](https://github.com/airblade/paper_trail/blob/master/lib/paper_trail/serializers/json.rb)
|
|
1150
|
+
If you use the optional `object_changes` column, don't forget to convert it
|
|
1151
|
+
also, using the same technique.
|
|
892
1152
|
|
|
893
|
-
|
|
1153
|
+
#### Convert a Column from Text to JSON
|
|
894
1154
|
|
|
895
|
-
If
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
```ruby
|
|
900
|
-
# will make it so that a maximum of 4 versions will be stored for each object
|
|
901
|
-
# (the 3 most recent ones plus a `create` event)
|
|
902
|
-
>> PaperTrail.config.version_limit = 3
|
|
903
|
-
# disables/removes the version limit
|
|
904
|
-
>> PaperTrail.config.version_limit = nil
|
|
905
|
-
```
|
|
906
|
-
|
|
907
|
-
## Deleting Old Versions
|
|
1155
|
+
If your `object` column already contains JSON data, and you want to change its
|
|
1156
|
+
data type to `json` or `jsonb`, you can use the following [DDL][36]. Of course,
|
|
1157
|
+
if your `object` column contains YAML, you must first convert the data to JSON
|
|
1158
|
+
(see above) before you can change the column type.
|
|
908
1159
|
|
|
909
|
-
|
|
1160
|
+
Using SQL:
|
|
910
1161
|
|
|
911
1162
|
```sql
|
|
912
|
-
|
|
1163
|
+
alter table versions
|
|
1164
|
+
alter column object type jsonb
|
|
1165
|
+
using object::jsonb;
|
|
913
1166
|
```
|
|
914
1167
|
|
|
1168
|
+
Using ActiveRecord:
|
|
1169
|
+
|
|
915
1170
|
```ruby
|
|
916
|
-
|
|
1171
|
+
class ConvertVersionsObjectToJson < ActiveRecord::Migration
|
|
1172
|
+
def up
|
|
1173
|
+
change_column :versions, :object, 'jsonb USING object::jsonb'
|
|
1174
|
+
end
|
|
1175
|
+
|
|
1176
|
+
def down
|
|
1177
|
+
change_column :versions, :object, 'text USING object::text'
|
|
1178
|
+
end
|
|
1179
|
+
end
|
|
917
1180
|
```
|
|
918
1181
|
|
|
919
1182
|
## Testing
|
|
920
1183
|
|
|
921
|
-
You may want to turn PaperTrail off to speed up your tests. See the [Turning
|
|
1184
|
+
You may want to turn PaperTrail off to speed up your tests. See the [Turning
|
|
1185
|
+
PaperTrail Off/On](#turning-papertrail-offon) section above for tips on usage
|
|
1186
|
+
with `Test::Unit`.
|
|
922
1187
|
|
|
923
1188
|
### RSpec
|
|
924
1189
|
|
|
925
|
-
PaperTrail provides a helper that works with [RSpec]
|
|
926
|
-
|
|
927
|
-
|
|
1190
|
+
PaperTrail provides a helper that works with [RSpec][27] to make it easier to
|
|
1191
|
+
control when `PaperTrail` is enabled during testing.
|
|
1192
|
+
|
|
1193
|
+
If you wish to use the helper, you will need to require it in your RSpec test
|
|
1194
|
+
helper like so:
|
|
1195
|
+
|
|
1196
|
+
```ruby
|
|
1197
|
+
# spec/rails_helper.rb
|
|
1198
|
+
|
|
1199
|
+
ENV["RAILS_ENV"] ||= 'test'
|
|
1200
|
+
require 'spec_helper'
|
|
1201
|
+
require File.expand_path("../../config/environment", __FILE__)
|
|
1202
|
+
require 'rspec/rails'
|
|
1203
|
+
...
|
|
1204
|
+
require 'paper_trail/frameworks/rspec'
|
|
1205
|
+
```
|
|
1206
|
+
|
|
1207
|
+
When the helper is loaded, PaperTrail will be turned off for all tests by
|
|
1208
|
+
default. When you wish to enable PaperTrail for a test you can either wrap the
|
|
1209
|
+
test in a `with_versioning` block, or pass in `:versioning => true` option to a
|
|
1210
|
+
spec block, like so:
|
|
928
1211
|
|
|
929
1212
|
```ruby
|
|
930
1213
|
describe "RSpec test group" do
|
|
931
1214
|
it 'by default, PaperTrail will be turned off' do
|
|
932
|
-
PaperTrail.
|
|
1215
|
+
expect(PaperTrail).to_not be_enabled
|
|
933
1216
|
end
|
|
934
1217
|
|
|
935
1218
|
with_versioning do
|
|
936
1219
|
it 'within a `with_versioning` block it will be turned on' do
|
|
937
|
-
PaperTrail.
|
|
1220
|
+
expect(PaperTrail).to be_enabled
|
|
938
1221
|
end
|
|
939
1222
|
end
|
|
940
1223
|
|
|
941
1224
|
it 'can be turned on at the `it` or `describe` level like this', :versioning => true do
|
|
942
|
-
PaperTrail.
|
|
1225
|
+
expect(PaperTrail).to be_enabled
|
|
943
1226
|
end
|
|
944
1227
|
end
|
|
945
1228
|
```
|
|
946
1229
|
|
|
947
|
-
The helper will also reset the `PaperTrail.whodunnit` value to `nil` before each
|
|
948
|
-
|
|
1230
|
+
The helper will also reset the `PaperTrail.whodunnit` value to `nil` before each
|
|
1231
|
+
test to help prevent data spillover between tests. If you are using PaperTrail
|
|
1232
|
+
with Rails, the helper will automatically set the `PaperTrail.controller_info`
|
|
1233
|
+
value to `{}` as well, again, to help prevent data spillover between tests.
|
|
949
1234
|
|
|
950
|
-
There is also a `be_versioned` matcher provided by PaperTrail's RSpec helper
|
|
1235
|
+
There is also a `be_versioned` matcher provided by PaperTrail's RSpec helper
|
|
1236
|
+
which can be leveraged like so:
|
|
951
1237
|
|
|
952
1238
|
```ruby
|
|
953
1239
|
class Widget < ActiveRecord::Base
|
|
954
1240
|
end
|
|
955
1241
|
|
|
956
1242
|
describe Widget do
|
|
957
|
-
it
|
|
1243
|
+
it "is not versioned by default" do
|
|
1244
|
+
is_expected.to_not be_versioned
|
|
1245
|
+
end
|
|
958
1246
|
|
|
959
1247
|
describe "add versioning to the `Widget` class" do
|
|
960
1248
|
before(:all) do
|
|
@@ -963,16 +1251,52 @@ describe Widget do
|
|
|
963
1251
|
end
|
|
964
1252
|
end
|
|
965
1253
|
|
|
966
|
-
it
|
|
1254
|
+
it "enables paper trail" do
|
|
1255
|
+
is_expected.to be_versioned
|
|
1256
|
+
end
|
|
967
1257
|
end
|
|
968
1258
|
end
|
|
969
1259
|
```
|
|
970
1260
|
|
|
1261
|
+
It is also possible to do assertions on the versions using `have_a_version_with`
|
|
1262
|
+
matcher
|
|
1263
|
+
|
|
1264
|
+
```
|
|
1265
|
+
describe '`have_a_version_with` matcher' do
|
|
1266
|
+
before do
|
|
1267
|
+
widget.update_attributes!(:name => 'Leonard', :an_integer => 1 )
|
|
1268
|
+
widget.update_attributes!(:name => 'Tom')
|
|
1269
|
+
widget.update_attributes!(:name => 'Bob')
|
|
1270
|
+
end
|
|
1271
|
+
|
|
1272
|
+
it "is possible to do assertions on versions" do
|
|
1273
|
+
expect(widget).to have_a_version_with :name => 'Leonard', :an_integer => 1
|
|
1274
|
+
expect(widget).to have_a_version_with :an_integer => 1
|
|
1275
|
+
expect(widget).to have_a_version_with :name => 'Tom'
|
|
1276
|
+
end
|
|
1277
|
+
end
|
|
1278
|
+
|
|
1279
|
+
```
|
|
1280
|
+
|
|
971
1281
|
### Cucumber
|
|
972
1282
|
|
|
973
|
-
PaperTrail provides a helper for [Cucumber]
|
|
974
|
-
|
|
975
|
-
|
|
1283
|
+
PaperTrail provides a helper for [Cucumber][28] that works similar to the RSpec
|
|
1284
|
+
helper.If you wish to use the helper, you will need to require in your cucumber
|
|
1285
|
+
helper like so:
|
|
1286
|
+
|
|
1287
|
+
```ruby
|
|
1288
|
+
# features/support/env.rb
|
|
1289
|
+
|
|
1290
|
+
ENV["RAILS_ENV"] ||= "cucumber"
|
|
1291
|
+
require File.expand_path(File.dirname(__FILE__) + '/../../config/environment')
|
|
1292
|
+
...
|
|
1293
|
+
require 'paper_trail/frameworks/cucumber'
|
|
1294
|
+
```
|
|
1295
|
+
|
|
1296
|
+
When the helper is loaded, PaperTrail will be turned off for all scenarios by a
|
|
1297
|
+
`before` hook added by the helper by default. When you wish to enable PaperTrail
|
|
1298
|
+
for a scenario, you can wrap code in a `with_versioning` block in a step, like
|
|
1299
|
+
so:
|
|
976
1300
|
|
|
977
1301
|
```ruby
|
|
978
1302
|
Given /I want versioning on my model/ do
|
|
@@ -982,25 +1306,28 @@ Given /I want versioning on my model/ do
|
|
|
982
1306
|
end
|
|
983
1307
|
```
|
|
984
1308
|
|
|
985
|
-
The helper will also reset the `PaperTrail.whodunnit` value to `nil` before each
|
|
986
|
-
|
|
1309
|
+
The helper will also reset the `PaperTrail.whodunnit` value to `nil` before each
|
|
1310
|
+
test to help prevent data spillover between tests. If you are using PaperTrail
|
|
1311
|
+
with Rails, the helper will automatically set the `PaperTrail.controller_info`
|
|
1312
|
+
value to `{}` as well, again, to help prevent data spillover between tests.
|
|
987
1313
|
|
|
988
1314
|
### Spork
|
|
989
1315
|
|
|
990
|
-
If you wish to use the `RSpec` or `Cucumber` helpers with [Spork]
|
|
991
|
-
manually require the helper(s) in your `prefork` block on your test
|
|
1316
|
+
If you wish to use the `RSpec` or `Cucumber` helpers with [Spork][29], you will
|
|
1317
|
+
need to manually require the helper(s) in your `prefork` block on your test
|
|
1318
|
+
helper, like so:
|
|
992
1319
|
|
|
993
1320
|
```ruby
|
|
994
|
-
# spec/
|
|
1321
|
+
# spec/rails_helper.rb
|
|
995
1322
|
|
|
996
1323
|
require 'spork'
|
|
997
1324
|
|
|
998
1325
|
Spork.prefork do
|
|
999
1326
|
# This file is copied to spec/ when you run 'rails generate rspec:install'
|
|
1000
1327
|
ENV["RAILS_ENV"] ||= 'test'
|
|
1328
|
+
require 'spec_helper'
|
|
1001
1329
|
require File.expand_path("../../config/environment", __FILE__)
|
|
1002
1330
|
require 'rspec/rails'
|
|
1003
|
-
require 'rspec/autorun'
|
|
1004
1331
|
require 'paper_trail/frameworks/rspec'
|
|
1005
1332
|
require 'paper_trail/frameworks/cucumber'
|
|
1006
1333
|
...
|
|
@@ -1009,13 +1336,15 @@ end
|
|
|
1009
1336
|
|
|
1010
1337
|
### Zeus or Spring
|
|
1011
1338
|
|
|
1012
|
-
If you wish to use the `RSpec` or `Cucumber` helpers with [Zeus]
|
|
1013
|
-
manually require the helper(s) in your test
|
|
1339
|
+
If you wish to use the `RSpec` or `Cucumber` helpers with [Zeus][30] or
|
|
1340
|
+
[Spring][31], you will need to manually require the helper(s) in your test
|
|
1341
|
+
helper, like so:
|
|
1014
1342
|
|
|
1015
1343
|
```ruby
|
|
1016
|
-
# spec/
|
|
1344
|
+
# spec/rails_helper.rb
|
|
1017
1345
|
|
|
1018
1346
|
ENV["RAILS_ENV"] ||= 'test'
|
|
1347
|
+
require 'spec_helper'
|
|
1019
1348
|
require File.expand_path("../../config/environment", __FILE__)
|
|
1020
1349
|
require 'rspec/rails'
|
|
1021
1350
|
require 'paper_trail/frameworks/rspec'
|
|
@@ -1023,9 +1352,13 @@ require 'paper_trail/frameworks/rspec'
|
|
|
1023
1352
|
|
|
1024
1353
|
## Testing PaperTrail
|
|
1025
1354
|
|
|
1026
|
-
Paper Trail has facilities to test
|
|
1355
|
+
Paper Trail has facilities to test against Postgres, Mysql and SQLite. To switch
|
|
1356
|
+
between DB engines you will need to export the DB variable for the engine you
|
|
1357
|
+
wish to test against.
|
|
1027
1358
|
|
|
1028
|
-
Though be aware we do not have the
|
|
1359
|
+
Though be aware we do not have the ability to create the db's (except sqlite) for
|
|
1360
|
+
you. You can look at .travis.yml before_script for an example of how to create
|
|
1361
|
+
the db's needed.
|
|
1029
1362
|
|
|
1030
1363
|
```
|
|
1031
1364
|
export DB=postgres
|
|
@@ -1033,19 +1366,70 @@ export DB=mysql
|
|
|
1033
1366
|
export DB=sqlite # this is default
|
|
1034
1367
|
```
|
|
1035
1368
|
|
|
1036
|
-
##
|
|
1369
|
+
## Sinatra
|
|
1370
|
+
|
|
1371
|
+
In order to configure PaperTrail for usage with [Sinatra][12], your `Sinatra`
|
|
1372
|
+
app must be using `ActiveRecord` 3 or 4. It is also recommended to use the
|
|
1373
|
+
[Sinatra ActiveRecord Extension][13] or something similar for managing your
|
|
1374
|
+
applications `ActiveRecord` connection in a manner similar to the way `Rails`
|
|
1375
|
+
does. If using the aforementioned `Sinatra ActiveRecord Extension`, steps for
|
|
1376
|
+
setting up your app with PaperTrail will look something like this:
|
|
1377
|
+
|
|
1378
|
+
1. Add PaperTrail to your `Gemfile`.
|
|
1379
|
+
|
|
1380
|
+
`gem 'paper_trail', '~> 4.2.0'`
|
|
1037
1381
|
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
* [Keep a Paper Trail with PaperTrail](http://www.linux-mag.com/id/7528), Linux Magazine, 16th September 2009.
|
|
1382
|
+
2. Generate a migration to add a `versions` table to your database.
|
|
1383
|
+
|
|
1384
|
+
`bundle exec rake db:create_migration NAME=create_versions`
|
|
1042
1385
|
|
|
1386
|
+
3. Copy contents of [create_versions.rb][14]
|
|
1387
|
+
into the `create_versions` migration that was generated into your `db/migrate` directory.
|
|
1388
|
+
|
|
1389
|
+
4. Run the migration.
|
|
1390
|
+
|
|
1391
|
+
`bundle exec rake db:migrate`
|
|
1392
|
+
|
|
1393
|
+
5. Add `has_paper_trail` to the models you want to track.
|
|
1394
|
+
|
|
1395
|
+
|
|
1396
|
+
PaperTrail provides a helper extension that acts similar to the controller mixin
|
|
1397
|
+
it provides for `Rails` applications.
|
|
1398
|
+
|
|
1399
|
+
It will set `PaperTrail.whodunnit` to whatever is returned by a method named
|
|
1400
|
+
`user_for_paper_trail` which you can define inside your Sinatra Application. (by
|
|
1401
|
+
default it attempts to invoke a method named `current_user`)
|
|
1402
|
+
|
|
1403
|
+
If you're using the modular [`Sinatra::Base`][15] style of application, you will
|
|
1404
|
+
need to register the extension:
|
|
1405
|
+
|
|
1406
|
+
```ruby
|
|
1407
|
+
# bleh_app.rb
|
|
1408
|
+
require 'sinatra/base'
|
|
1409
|
+
|
|
1410
|
+
class BlehApp < Sinatra::Base
|
|
1411
|
+
register PaperTrail::Sinatra
|
|
1412
|
+
end
|
|
1413
|
+
```
|
|
1414
|
+
|
|
1415
|
+
## Articles
|
|
1416
|
+
|
|
1417
|
+
* [Jutsu #8 - Version your RoR models with PaperTrail](http://samurails.com/gems/papertrail/),
|
|
1418
|
+
[Thibault](http://samurails.com/about-me/), 29th September 2014
|
|
1419
|
+
* [Versioning with PaperTrail](http://www.sitepoint.com/versioning-papertrail),
|
|
1420
|
+
[Ilya Bodrov](http://www.sitepoint.com/author/ibodrov), 10th April 2014
|
|
1421
|
+
* [Using PaperTrail to track stack traces](http://rubyrailsexpert.com/?p=36),
|
|
1422
|
+
T James Corcoran's blog, 1st October 2013.
|
|
1423
|
+
* [RailsCast #255 - Undo with Paper Trail][3], Feb 28, 2011
|
|
1424
|
+
* [RailsCast #255 - Undo with PaperTrail](http://railscasts.com/episodes/255-undo-with-paper-trail),
|
|
1425
|
+
28th February 2011.
|
|
1426
|
+
* [Keep a Paper Trail with PaperTrail](http://www.linux-mag.com/id/7528),
|
|
1427
|
+
Linux Magazine, 16th September 2009.
|
|
1043
1428
|
|
|
1044
1429
|
## Problems
|
|
1045
1430
|
|
|
1046
1431
|
Please use GitHub's [issue tracker](http://github.com/airblade/paper_trail/issues).
|
|
1047
1432
|
|
|
1048
|
-
|
|
1049
1433
|
## Contributors
|
|
1050
1434
|
|
|
1051
1435
|
Many thanks to:
|
|
@@ -1101,15 +1485,51 @@ Many thanks to:
|
|
|
1101
1485
|
* [Chulki Lee](https://github.com/chulkilee)
|
|
1102
1486
|
* [Lucas Souza](https://github.com/lucasas)
|
|
1103
1487
|
* [Russell Osborne](https://github.com/rposborne)
|
|
1104
|
-
|
|
1488
|
+
* [Ben Li](https://github.com/bli)
|
|
1489
|
+
* [Felix Liu](https://github.com/lyfeyaj)
|
|
1105
1490
|
|
|
1106
1491
|
## Inspirations
|
|
1107
1492
|
|
|
1108
1493
|
* [Simply Versioned](http://github.com/github/simply_versioned)
|
|
1109
1494
|
* [Acts As Audited](http://github.com/collectiveidea/acts_as_audited)
|
|
1110
1495
|
|
|
1111
|
-
|
|
1112
1496
|
## Intellectual Property
|
|
1113
1497
|
|
|
1114
1498
|
Copyright (c) 2011 Andy Stewart (boss@airbladesoftware.com).
|
|
1115
1499
|
Released under the MIT licence.
|
|
1500
|
+
|
|
1501
|
+
[1]: http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
|
|
1502
|
+
[2]: https://github.com/airblade/paper_trail/issues/163
|
|
1503
|
+
[3]: http://railscasts.com/episodes/255-undo-with-paper-trail
|
|
1504
|
+
[4]: https://api.travis-ci.org/airblade/paper_trail.svg?branch=master
|
|
1505
|
+
[5]: https://travis-ci.org/airblade/paper_trail
|
|
1506
|
+
[6]: https://img.shields.io/gemnasium/airblade/paper_trail.svg
|
|
1507
|
+
[7]: https://gemnasium.com/airblade/paper_trail
|
|
1508
|
+
[9]: https://github.com/airblade/paper_trail/tree/3.0-stable
|
|
1509
|
+
[10]: https://github.com/airblade/paper_trail/tree/2.7-stable
|
|
1510
|
+
[11]: https://github.com/airblade/paper_trail/tree/rails2
|
|
1511
|
+
[12]: http://www.sinatrarb.com
|
|
1512
|
+
[13]: https://github.com/janko-m/sinatra-activerecord
|
|
1513
|
+
[14]: https://raw.github.com/airblade/paper_trail/master/lib/generators/paper_trail/templates/create_versions.rb
|
|
1514
|
+
[15]: http://www.sinatrarb.com/intro.html#Modular%20vs.%20Classic%20Style
|
|
1515
|
+
[16]: https://github.com/airblade/paper_trail/issues/113
|
|
1516
|
+
[17]: https://github.com/rails/protected_attributes
|
|
1517
|
+
[18]: https://github.com/rails/strong_parameters
|
|
1518
|
+
[19]: http://github.com/myobie/htmldiff
|
|
1519
|
+
[20]: http://github.com/pvande/differ
|
|
1520
|
+
[21]: https://github.com/halostatue/diff-lcs
|
|
1521
|
+
[22]: http://github.com/jeremyw/paper_trail/blob/master/lib/paper_trail/has_paper_trail.rb#L151-156
|
|
1522
|
+
[23]: http://github.com/tim/activerecord-diff
|
|
1523
|
+
[24]: https://github.com/airblade/paper_trail/blob/master/lib/paper_trail/serializers/yaml.rb
|
|
1524
|
+
[25]: https://github.com/airblade/paper_trail/blob/master/lib/paper_trail/serializers/json.rb
|
|
1525
|
+
[26]: http://www.postgresql.org/docs/9.4/static/datatype-json.html
|
|
1526
|
+
[27]: https://github.com/rspec/rspec
|
|
1527
|
+
[28]: http://cukes.info
|
|
1528
|
+
[29]: https://github.com/sporkrb/spork
|
|
1529
|
+
[30]: https://github.com/burke/zeus
|
|
1530
|
+
[31]: https://github.com/rails/spring
|
|
1531
|
+
[32]: http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html#method-i-mark_for_destruction
|
|
1532
|
+
[33]: https://github.com/airblade/paper_trail/wiki/Setting-whodunnit-in-the-rails-console
|
|
1533
|
+
[34]: https://github.com/rails/rails/blob/591a0bb87fff7583e01156696fbbf929d48d3e54/activerecord/lib/active_record/fixtures.rb#L142
|
|
1534
|
+
[35]: https://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html
|
|
1535
|
+
[36]: http://www.postgresql.org/docs/9.4/interactive/ddl.html
|