paper_trail 4.0.2 → 4.1.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/.travis.yml +2 -0
- data/CHANGELOG.md +27 -0
- data/CONTRIBUTING.md +78 -5
- data/README.md +328 -268
- data/doc/bug_report_template.rb +65 -0
- data/gemfiles/3.0.gemfile +7 -4
- data/lib/generators/paper_trail/templates/add_object_changes_to_versions.rb +1 -1
- data/lib/generators/paper_trail/templates/create_versions.rb +14 -0
- data/lib/paper_trail.rb +11 -9
- data/lib/paper_trail/attributes_serialization.rb +89 -0
- data/lib/paper_trail/cleaner.rb +8 -1
- data/lib/paper_trail/config.rb +15 -18
- data/lib/paper_trail/frameworks/rails/controller.rb +16 -2
- data/lib/paper_trail/has_paper_trail.rb +102 -99
- data/lib/paper_trail/record_history.rb +59 -0
- data/lib/paper_trail/reifier.rb +270 -0
- data/lib/paper_trail/version_association_concern.rb +3 -1
- data/lib/paper_trail/version_concern.rb +60 -226
- data/lib/paper_trail/version_number.rb +2 -2
- data/paper_trail.gemspec +7 -10
- data/spec/models/animal_spec.rb +17 -0
- data/spec/models/callback_modifier_spec.rb +96 -0
- data/spec/models/json_version_spec.rb +20 -17
- data/spec/paper_trail/config_spec.rb +52 -0
- data/spec/spec_helper.rb +6 -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/paragraph.rb +5 -0
- data/test/dummy/app/models/quotation.rb +5 -0
- data/test/dummy/app/models/section.rb +6 -0
- data/test/dummy/config/database.postgres.yml +1 -1
- data/test/dummy/config/initializers/paper_trail.rb +3 -1
- data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +33 -0
- data/test/dummy/db/schema.rb +27 -0
- data/test/test_helper.rb +36 -0
- data/test/unit/associations_test.rb +726 -0
- data/test/unit/inheritance_column_test.rb +6 -6
- data/test/unit/model_test.rb +62 -594
- data/test/unit/protected_attrs_test.rb +3 -2
- data/test/unit/version_test.rb +87 -69
- metadata +38 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 80cb56f3a2c0989873fe5086674a3a170c6dbaf0
|
4
|
+
data.tar.gz: c88a259524d7ec2f8298360d7505ed17b23da9a8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ca33cf4ce53eb49d4ff3fbe795efd4c21f14e0b5600e4051c9d69896c5f69119b73cfa4417a340e1cf4821da4a59549d84a79d85a1975b4d4664d2d7bcef673
|
7
|
+
data.tar.gz: a74706949f90071143f97f04f22a069afd0c4b7981b0436d4f947c75a713afdf273683a095ea556f56f3927e33630ab0f17e6426bf9381edb5f7b9bc1c324782
|
data/.travis.yml
CHANGED
@@ -16,9 +16,11 @@ env:
|
|
16
16
|
sudo: false
|
17
17
|
|
18
18
|
before_script:
|
19
|
+
- mysql --version
|
19
20
|
- sh -c "if [ \"$DB\" = 'mysql' ]; then mysql -e 'create database paper_trail_test;'; fi"
|
20
21
|
- sh -c "if [ \"$DB\" = 'mysql' ]; then mysql -e 'create database paper_trail_bar; '; fi"
|
21
22
|
- sh -c "if [ \"$DB\" = 'mysql' ]; then mysql -e 'create database paper_trail_foo; '; fi"
|
23
|
+
- psql --version
|
22
24
|
- sh -c "if [ \"$DB\" = 'postgres' ]; then psql -c 'create database paper_trail_test;' -U postgres; fi"
|
23
25
|
- sh -c "if [ \"$DB\" = 'postgres' ]; then psql -c 'create database paper_trail_bar;' -U postgres; fi"
|
24
26
|
- sh -c "if [ \"$DB\" = 'postgres' ]; then psql -c 'create database paper_trail_foo;' -U postgres; fi"
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,20 @@
|
|
1
|
+
## 4.1.0
|
2
|
+
|
3
|
+
### Breaking Changes
|
4
|
+
|
5
|
+
- None
|
6
|
+
|
7
|
+
### Added
|
8
|
+
|
9
|
+
- A way to control the order of AR callbacks.
|
10
|
+
[#614](https://github.com/airblade/paper_trail/pull/614)
|
11
|
+
- Added `unversioned_attributes` option to `reify`.
|
12
|
+
[#579](https://github.com/airblade/paper_trail/pull/579)
|
13
|
+
|
14
|
+
### Fixed
|
15
|
+
|
16
|
+
- None
|
17
|
+
|
1
18
|
## 4.0.2
|
2
19
|
|
3
20
|
### Breaking Changes
|
@@ -152,6 +169,16 @@ candidates.
|
|
152
169
|
- [#479](https://github.com/airblade/paper_trail/issues/479) - Deprecated
|
153
170
|
`originator` method, use `paper_trail_originator`.
|
154
171
|
|
172
|
+
## 3.0.9
|
173
|
+
|
174
|
+
- [#479](https://github.com/airblade/paper_trail/issues/479) - Deprecated
|
175
|
+
`originator` method in favor of `paper_trail_originator` Deprecation warning
|
176
|
+
informs users that the `originator` of the methods will be removed in
|
177
|
+
version `4.0`. (Backported from v4)
|
178
|
+
- Updated deprecation warnings for `Model.paper_trail_on` and
|
179
|
+
`Model.paper_trail_off` to have display correct version number the methods
|
180
|
+
will be removed (`4.0`)
|
181
|
+
|
155
182
|
## 3.0.8
|
156
183
|
|
157
184
|
- [#525](https://github.com/airblade/paper_trail/issues/525) / [#512](https://github.com/airblade/paper_trail/pull/512) -
|
data/CONTRIBUTING.md
CHANGED
@@ -1,11 +1,84 @@
|
|
1
1
|
# Contributing
|
2
2
|
|
3
|
-
Thanks for your interest in PaperTrail!
|
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
|
4
11
|
suggestions, and especially pull requests.
|
5
12
|
|
6
|
-
|
7
|
-
time to answer them all. Instead, please ask usage questions on Stack Overflow.
|
13
|
+
Thanks, and happy (paper) trails :)
|
8
14
|
|
9
|
-
|
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
|
+
```
|
10
83
|
|
11
|
-
|
84
|
+
[1]: https://github.com/airblade/paper_trail/blob/master/doc/bug_report_template.rb
|
data/README.md
CHANGED
@@ -6,11 +6,22 @@ Track changes to your models, for auditing or versioning. See how a model looked
|
|
6
6
|
at any stage in its lifecycle, revert it to any version, or restore it after it
|
7
7
|
has been destroyed.
|
8
8
|
|
9
|
-
|
9
|
+
## Documentation
|
10
|
+
|
11
|
+
| Version | Documentation |
|
12
|
+
| -------------- | ------------- |
|
13
|
+
| 4.1 | https://github.com/airblade/paper_trail/blob/4.1-stable/README.md |
|
14
|
+
| 4.0 | https://github.com/airblade/paper_trail/blob/4.0-stable/README.md |
|
15
|
+
| 3 | https://github.com/airblade/paper_trail/blob/3.0-stable/README.md |
|
16
|
+
| 2 | https://github.com/airblade/paper_trail/blob/2.7-stable/README.md |
|
17
|
+
| 1 | https://github.com/airblade/paper_trail/blob/rails2/README.md |
|
18
|
+
|
19
|
+
## Table of Contents
|
20
|
+
|
10
21
|
- [Compatibility](#compatibility)
|
11
22
|
- [Installation](#installation)
|
12
|
-
- [API Summary](#api-summary)
|
13
23
|
- [Basic Usage](#basic-usage)
|
24
|
+
- [API Summary](#api-summary)
|
14
25
|
- Limiting What is Versioned, and When
|
15
26
|
- [Choosing Lifecycle Events To Monitor](#choosing-lifecycle-events-to-monitor)
|
16
27
|
- [Choosing When To Save New Versions](#choosing-when-to-save-new-versions)
|
@@ -22,38 +33,16 @@ has been destroyed.
|
|
22
33
|
- [Navigating Versions](#navigating-versions)
|
23
34
|
- [Diffing Versions](#diffing-versions)
|
24
35
|
- [Deleting Old Versions](#deleting-old-versions)
|
25
|
-
-
|
26
|
-
- [
|
27
|
-
- [Associations](#associations)
|
28
|
-
- [Storing metadata](#storing-metadata)
|
29
|
-
-
|
36
|
+
- Saving More Information About Versions
|
37
|
+
- [Finding Out Who Was Responsible For A Change](#finding-out-who-was-responsible-for-a-change)
|
38
|
+
- [Associations](#associations)
|
39
|
+
- [Storing metadata](#storing-metadata)
|
40
|
+
- Extensibility
|
41
|
+
- [Custom Version Classes](#custom-version-classes)
|
42
|
+
- [Custom Serializer](#using-a-custom-serializer)
|
30
43
|
- [SerializedAttributes support](#serializedattributes-support)
|
31
44
|
- [Testing](#testing)
|
32
|
-
|
33
|
-
## Features
|
34
|
-
|
35
|
-
* Stores create, update and destroy events
|
36
|
-
* Does not store updates which don't change anything
|
37
|
-
* Support for versioning associated records
|
38
|
-
* Can store metadata with each version record
|
39
|
-
* Who was responsible for a change
|
40
|
-
* Arbitrary model-level metadata (useful for filtering versions)
|
41
|
-
* Arbitrary controller-level information e.g. remote IP
|
42
|
-
* Configurable
|
43
|
-
* No configuration necessary, but if you want to ..
|
44
|
-
* Configure which events (create, update and destroy) are versioned
|
45
|
-
* Configure which attributes must change for an update to be versioned
|
46
|
-
* Turn off/on by model, request, or globally
|
47
|
-
* Use separate tables for separate models
|
48
|
-
* Extensible
|
49
|
-
* Write a custom version class for complete control
|
50
|
-
* Write custom version classes for each of your models
|
51
|
-
* Work with versions
|
52
|
-
* Restore any version, including the original, even once destroyed
|
53
|
-
* Restore any version even if the schema has since changed
|
54
|
-
* Restore the version as of a particular time
|
55
|
-
* Thoroughly tested
|
56
|
-
* Threadsafe
|
45
|
+
- [Sinatra](#sinatra)
|
57
46
|
|
58
47
|
## Compatibility
|
59
48
|
|
@@ -66,67 +55,83 @@ has been destroyed.
|
|
66
55
|
|
67
56
|
## Installation
|
68
57
|
|
69
|
-
### Rails 3 and 4
|
70
|
-
|
71
58
|
1. Add PaperTrail to your `Gemfile`.
|
72
59
|
|
73
|
-
`gem 'paper_trail', '~> 4.0
|
74
|
-
|
75
|
-
2. Generate a migration which will add a `versions` table to your database.
|
76
|
-
|
77
|
-
`bundle exec rails generate paper_trail:install`
|
60
|
+
`gem 'paper_trail', '~> 4.1.0'`
|
78
61
|
|
79
|
-
|
62
|
+
1. Add a `versions` table to your database.
|
80
63
|
|
81
|
-
|
82
|
-
|
83
|
-
|
64
|
+
```
|
65
|
+
bundle exec rails generate paper_trail:install
|
66
|
+
bundle exec rake db:migrate
|
67
|
+
```
|
84
68
|
|
85
|
-
|
69
|
+
1. Add `has_paper_trail` to the models you want to track.
|
86
70
|
|
87
|
-
|
88
|
-
app must be using `ActiveRecord` 3 or 4. It is also recommended to use the
|
89
|
-
[Sinatra ActiveRecord Extension][13] or something similar for managing your
|
90
|
-
applications `ActiveRecord` connection in a manner similar to the way `Rails`
|
91
|
-
does. If using the aforementioned `Sinatra ActiveRecord Extension`, steps for
|
92
|
-
setting up your app with PaperTrail will look something like this:
|
71
|
+
## Basic Usage
|
93
72
|
|
94
|
-
|
73
|
+
Add `has_paper_trail` to your model to record every `create`, `update`,
|
74
|
+
and `destroy`.
|
95
75
|
|
96
|
-
|
76
|
+
```ruby
|
77
|
+
class Widget < ActiveRecord::Base
|
78
|
+
has_paper_trail
|
79
|
+
end
|
80
|
+
```
|
97
81
|
|
98
|
-
|
82
|
+
This gives you a `versions` method which returns the "paper trail" of changes to
|
83
|
+
your model.
|
99
84
|
|
100
|
-
|
85
|
+
```ruby
|
86
|
+
widget = Widget.find 42
|
87
|
+
widget.versions
|
88
|
+
# [<PaperTrail::Version>, <PaperTrail::Version>, ...]
|
89
|
+
```
|
101
90
|
|
102
|
-
|
103
|
-
into the `create_versions` migration that was generated into your `db/migrate` directory.
|
91
|
+
Once you have a version, you can find out what happened:
|
104
92
|
|
105
|
-
|
93
|
+
```ruby
|
94
|
+
v = widget.versions.last
|
95
|
+
v.event # 'update', 'create', or 'destroy'
|
96
|
+
v.created_at # When the `event` occurred
|
97
|
+
v.whodunnit # If the update was via a controller and the
|
98
|
+
# controller has a current_user method, returns the
|
99
|
+
# id of the current user as a string.
|
100
|
+
widget = v.reify # The widget as it was before the update
|
101
|
+
# (nil for a create event)
|
102
|
+
```
|
106
103
|
|
107
|
-
|
104
|
+
PaperTrail stores the pre-change version of the model, unlike some other
|
105
|
+
auditing/versioning plugins, so you can retrieve the original version. This is
|
106
|
+
useful when you start keeping a paper trail for models that already have records
|
107
|
+
in the database.
|
108
108
|
|
109
|
-
|
109
|
+
```ruby
|
110
|
+
widget = Widget.find 153
|
111
|
+
widget.name # 'Doobly'
|
110
112
|
|
113
|
+
# Add has_paper_trail to Widget model.
|
111
114
|
|
112
|
-
|
113
|
-
|
115
|
+
widget.versions # []
|
116
|
+
widget.update_attributes :name => 'Wotsit'
|
117
|
+
widget.versions.last.reify.name # 'Doobly'
|
118
|
+
widget.versions.last.event # 'update'
|
119
|
+
```
|
114
120
|
|
115
|
-
|
116
|
-
|
117
|
-
|
121
|
+
This also means that PaperTrail does not waste space storing a version of the
|
122
|
+
object as it currently stands. The `versions` method gives you previous
|
123
|
+
versions; to get the current one just call a finder on your `Widget` model as
|
124
|
+
usual.
|
118
125
|
|
119
|
-
|
120
|
-
need to register the extension:
|
126
|
+
Here's a helpful table showing what PaperTrail stores:
|
121
127
|
|
122
|
-
|
123
|
-
|
124
|
-
|
128
|
+
| *Event* | *create* | *update* | *destroy* |
|
129
|
+
| -------------- | -------- | -------- | --------- |
|
130
|
+
| *Model Before* | nil | widget | widget |
|
131
|
+
| *Model After* | widget | widget | nil |
|
125
132
|
|
126
|
-
|
127
|
-
|
128
|
-
end
|
129
|
-
```
|
133
|
+
PaperTrail stores the values in the Model Before column. Most other
|
134
|
+
auditing/versioning plugins store the After column.
|
130
135
|
|
131
136
|
## API Summary
|
132
137
|
|
@@ -134,17 +139,19 @@ When you declare `has_paper_trail` in your model, you get these methods:
|
|
134
139
|
|
135
140
|
```ruby
|
136
141
|
class Widget < ActiveRecord::Base
|
137
|
-
has_paper_trail
|
142
|
+
has_paper_trail
|
138
143
|
end
|
139
144
|
|
140
|
-
# Returns this widget's versions. You can customise the name of the
|
145
|
+
# Returns this widget's versions. You can customise the name of the
|
146
|
+
# association.
|
141
147
|
widget.versions
|
142
148
|
|
143
149
|
# Return the version this widget was reified from, or nil if it is live.
|
144
150
|
# You can customise the name of the method.
|
145
151
|
widget.version
|
146
152
|
|
147
|
-
# Returns true if this widget is the current, live one; or false if it is from
|
153
|
+
# Returns true if this widget is the current, live one; or false if it is from
|
154
|
+
# a previous version.
|
148
155
|
widget.live?
|
149
156
|
|
150
157
|
# Returns who put the widget into its current state.
|
@@ -159,7 +166,8 @@ widget.previous_version
|
|
159
166
|
# Returns the widget (not a version) as it became next.
|
160
167
|
widget.next_version
|
161
168
|
|
162
|
-
# Generates a version for a `touch` event (`widget.touch` does NOT generate a
|
169
|
+
# Generates a version for a `touch` event (`widget.touch` does NOT generate a
|
170
|
+
# version)
|
163
171
|
widget.touch_with_version
|
164
172
|
|
165
173
|
# Turn PaperTrail off for all widgets.
|
@@ -168,9 +176,11 @@ Widget.paper_trail_off!
|
|
168
176
|
# Turn PaperTrail on for all widgets.
|
169
177
|
Widget.paper_trail_on!
|
170
178
|
|
171
|
-
#
|
179
|
+
# Is PaperTrail enabled for Widget, the class?
|
172
180
|
Widget.paper_trail_enabled_for_model?
|
173
|
-
|
181
|
+
|
182
|
+
# Is PaperTrail enabled for widget, the instance?
|
183
|
+
widget.paper_trail_enabled_for_model?
|
174
184
|
```
|
175
185
|
|
176
186
|
And a `PaperTrail::Version` instance has these methods:
|
@@ -224,89 +234,6 @@ user_for_paper_trail
|
|
224
234
|
info_for_paper_trail
|
225
235
|
```
|
226
236
|
|
227
|
-
## Basic Usage
|
228
|
-
|
229
|
-
PaperTrail is simple to use. Just add 15 characters to a model to get a paper
|
230
|
-
trail of every `create`, `update`, and `destroy`.
|
231
|
-
|
232
|
-
```ruby
|
233
|
-
class Widget < ActiveRecord::Base
|
234
|
-
has_paper_trail
|
235
|
-
end
|
236
|
-
```
|
237
|
-
|
238
|
-
This gives you a `versions` method which returns the paper trail of changes to
|
239
|
-
your model.
|
240
|
-
|
241
|
-
```ruby
|
242
|
-
widget = Widget.find 42
|
243
|
-
widget.versions
|
244
|
-
# [<PaperTrail::Version>, <PaperTrail::Version>, ...]
|
245
|
-
```
|
246
|
-
|
247
|
-
Once you have a version, you can find out what happened:
|
248
|
-
|
249
|
-
```ruby
|
250
|
-
v = widget.versions.last
|
251
|
-
v.event # 'update' (or 'create' or 'destroy')
|
252
|
-
v.whodunnit # '153' (if the update was via a controller and
|
253
|
-
# the controller has a current_user method,
|
254
|
-
# here returning the id of the current user)
|
255
|
-
v.created_at # when the update occurred
|
256
|
-
widget = v.reify # the widget as it was before the update;
|
257
|
-
# would be nil for a create event
|
258
|
-
```
|
259
|
-
|
260
|
-
PaperTrail stores the pre-change version of the model, unlike some other
|
261
|
-
auditing/versioning plugins, so you can retrieve the original version. This is
|
262
|
-
useful when you start keeping a paper trail for models that already have records
|
263
|
-
in the database.
|
264
|
-
|
265
|
-
```ruby
|
266
|
-
widget = Widget.find 153
|
267
|
-
widget.name # 'Doobly'
|
268
|
-
|
269
|
-
# Add has_paper_trail to Widget model.
|
270
|
-
|
271
|
-
widget.versions # []
|
272
|
-
widget.update_attributes :name => 'Wotsit'
|
273
|
-
widget.versions.last.reify.name # 'Doobly'
|
274
|
-
widget.versions.last.event # 'update'
|
275
|
-
```
|
276
|
-
|
277
|
-
This also means that PaperTrail does not waste space storing a version of the
|
278
|
-
object as it currently stands. The `versions` method gives you previous
|
279
|
-
versions; to get the current one just call a finder on your `Widget` model as
|
280
|
-
usual.
|
281
|
-
|
282
|
-
Here's a helpful table showing what PaperTrail stores:
|
283
|
-
|
284
|
-
<table>
|
285
|
-
<tr>
|
286
|
-
<th>Event</th>
|
287
|
-
<th>Model Before</th>
|
288
|
-
<th>Model After</th>
|
289
|
-
</tr>
|
290
|
-
<tr>
|
291
|
-
<td>create</td>
|
292
|
-
<td>nil</td>
|
293
|
-
<td>widget</td>
|
294
|
-
</tr>
|
295
|
-
<tr>
|
296
|
-
<td>update</td>
|
297
|
-
<td>widget</td>
|
298
|
-
<td>widget</td>
|
299
|
-
<tr>
|
300
|
-
<td>destroy</td>
|
301
|
-
<td>widget</td>
|
302
|
-
<td>nil</td>
|
303
|
-
</tr>
|
304
|
-
</table>
|
305
|
-
|
306
|
-
PaperTrail stores the values in the Model Before column. Most other
|
307
|
-
auditing/versioning plugins store the After column.
|
308
|
-
|
309
|
-
|
310
237
|
## Choosing Lifecycle Events To Monitor
|
311
238
|
|
312
239
|
You can choose which events to track with the `on` option. For example, to
|
@@ -318,6 +245,10 @@ class Article < ActiveRecord::Base
|
|
318
245
|
end
|
319
246
|
```
|
320
247
|
|
248
|
+
`has_paper_trail` installs callbacks for these lifecycle events. If there are
|
249
|
+
other callbacks in your model, their order relative to those installed by
|
250
|
+
PaperTrail may matter, so be aware of any potential interactions.
|
251
|
+
|
321
252
|
You may also have the `PaperTrail::Version` model save a custom string in it's
|
322
253
|
`event` field instead of the typical `create`, `update`, `destroy`. PaperTrail
|
323
254
|
supplies a custom accessor method called `paper_trail_event`, which it will
|
@@ -338,6 +269,30 @@ a.versions.size # 3
|
|
338
269
|
a.versions.last.event # 'update'
|
339
270
|
```
|
340
271
|
|
272
|
+
### Controlling the Order of AR Callbacks
|
273
|
+
|
274
|
+
The `has_paper_trail` method installs AR callbacks. If you need to control
|
275
|
+
their order, use the `paper_trail_on_*` methods.
|
276
|
+
|
277
|
+
```ruby
|
278
|
+
class Article < ActiveRecord::Base
|
279
|
+
|
280
|
+
# Include PaperTrail, but do not add any callbacks yet. Passing the
|
281
|
+
# empty array to `:on` omits callbacks.
|
282
|
+
has_paper_trail :on => []
|
283
|
+
|
284
|
+
# Add callbacks in the order you need.
|
285
|
+
paper_trail_on_destroy # add destroy callback
|
286
|
+
paper_trail_on_update # etc.
|
287
|
+
paper_trail_on_create
|
288
|
+
end
|
289
|
+
```
|
290
|
+
|
291
|
+
The `paper_trail_on_destroy` method can be further configured to happen
|
292
|
+
`:before` or `:after` the destroy event. In PaperTrail 4, the default is
|
293
|
+
`:after`. In PaperTrail 5, the default will be `:before`, to support
|
294
|
+
ActiveRecord 5. (see https://github.com/airblade/paper_trail/pull/683)
|
295
|
+
|
341
296
|
## Choosing When To Save New Versions
|
342
297
|
|
343
298
|
You can choose the conditions when to add new versions with the `if` and
|
@@ -785,7 +740,7 @@ like it does, call `paper_trail_originator` on the object.
|
|
785
740
|
widget = Widget.find 153 # assume widget has 0 versions
|
786
741
|
PaperTrail.whodunnit = 'Alice'
|
787
742
|
widget.update_attributes :name => 'Yankee'
|
788
|
-
widget
|
743
|
+
widget.paper_trail_originator # 'Alice'
|
789
744
|
PaperTrail.whodunnit = 'Bob'
|
790
745
|
widget.update_attributes :name => 'Zulu'
|
791
746
|
widget.paper_trail_originator # 'Bob'
|
@@ -798,81 +753,9 @@ last_version.paper_trail_originator # 'Alice'
|
|
798
753
|
last_version.terminator # 'Bob'
|
799
754
|
```
|
800
755
|
|
801
|
-
## Custom Version Classes
|
802
|
-
|
803
|
-
You can specify custom version subclasses with the `:class_name` option:
|
804
|
-
|
805
|
-
```ruby
|
806
|
-
class PostVersion < PaperTrail::Version
|
807
|
-
# custom behaviour, e.g:
|
808
|
-
self.table_name = :post_versions
|
809
|
-
end
|
810
|
-
|
811
|
-
class Post < ActiveRecord::Base
|
812
|
-
has_paper_trail :class_name => 'PostVersion'
|
813
|
-
end
|
814
|
-
```
|
815
|
-
|
816
|
-
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`).
|
817
|
-
|
818
|
-
### Advantages
|
819
|
-
|
820
|
-
1. For models which have a lot of versions, storing each model's versions in a
|
821
|
-
separate table can improve the performance of certain database queries.
|
822
|
-
1. Store different version [metadata](#storing-metadata) for different models.
|
823
|
-
|
824
|
-
### Configuration
|
825
|
-
|
826
|
-
If you are using Postgres, you should also define the sequence that your custom
|
827
|
-
version class will use:
|
828
|
-
|
829
|
-
```ruby
|
830
|
-
class PostVersion < PaperTrail::Version
|
831
|
-
self.table_name = :post_versions
|
832
|
-
self.sequence_name = :post_versions_id_seq
|
833
|
-
end
|
834
|
-
```
|
835
|
-
|
836
|
-
If you only use custom version classes and don't have a `versions` table, you
|
837
|
-
must let ActiveRecord know that the `PaperTrail::Version` class is an
|
838
|
-
`abstract_class`.
|
839
|
-
|
840
|
-
```ruby
|
841
|
-
# app/models/paper_trail/version.rb
|
842
|
-
module PaperTrail
|
843
|
-
class Version < ActiveRecord::Base
|
844
|
-
include PaperTrail::VersionConcern
|
845
|
-
self.abstract_class = true
|
846
|
-
end
|
847
|
-
end
|
848
|
-
```
|
849
|
-
|
850
|
-
You can also specify custom names for the versions and version associations.
|
851
|
-
This is useful if you already have `versions` or/and `version` methods on your
|
852
|
-
model. For example:
|
853
|
-
|
854
|
-
```ruby
|
855
|
-
class Post < ActiveRecord::Base
|
856
|
-
has_paper_trail :versions => :paper_trail_versions,
|
857
|
-
:version => :paper_trail_version
|
858
|
-
|
859
|
-
# Existing versions method. We don't want to clash.
|
860
|
-
def versions
|
861
|
-
...
|
862
|
-
end
|
863
|
-
# Existing version method. We don't want to clash.
|
864
|
-
def version
|
865
|
-
...
|
866
|
-
end
|
867
|
-
end
|
868
|
-
```
|
869
|
-
|
870
756
|
## Associations
|
871
757
|
|
872
|
-
**Experimental
|
873
|
-
[#542](https://github.com/airblade/paper_trail/issues/542),
|
874
|
-
[#590](https://github.com/airblade/paper_trail/issues/590).
|
875
|
-
See also: Caveats below.
|
758
|
+
**Experimental feature**, see caveats below.
|
876
759
|
|
877
760
|
PaperTrail can restore three types of associations: Has-One, Has-Many, and
|
878
761
|
Has-Many-Through. In order to do this, you will need to create a
|
@@ -964,14 +847,23 @@ widget.reload.wotsit # nil
|
|
964
847
|
|
965
848
|
**Caveats:**
|
966
849
|
|
850
|
+
1. Not compatible with [transactional tests][34], aka. transactional fixtures.
|
851
|
+
This is a known issue [#542](https://github.com/airblade/paper_trail/issues/542)
|
852
|
+
that we'd like to solve.
|
853
|
+
1. Requires database timestamp columns with fractional second precision.
|
854
|
+
- Sqlite and postgres timestamps have fractional second precision by default.
|
855
|
+
[MySQL timestamps do not][35]. Furthermore, MySQL 5.5 and earlier do not
|
856
|
+
support fractional second precision at all.
|
857
|
+
- Also, support for fractional seconds in MySQL was not added to
|
858
|
+
rails until ActiveRecord 4.2 (https://github.com/rails/rails/pull/14359).
|
967
859
|
1. PaperTrail can't restore an association properly if the association record
|
968
860
|
can be updated to replace its parent model (by replacing the foreign key)
|
969
|
-
|
861
|
+
1. Currently PaperTrail only support single `version_associations` table. The
|
970
862
|
implication is that you can only use a single table to store the versions for
|
971
863
|
all related models. Sorry for those who use multiple version tables.
|
972
|
-
|
864
|
+
1. PaperTrail only reifies the first level of associations, i.e., it does not
|
973
865
|
reify any associations of its associations, and so on.
|
974
|
-
|
866
|
+
1. PaperTrail relies on the callbacks on the association model (and the :through
|
975
867
|
association model for Has-Many-Through associations) to record the versions
|
976
868
|
and the relationship between the versions. If the association is changed
|
977
869
|
without invoking the callbacks, Reification won't work. Below are some
|
@@ -1057,6 +949,7 @@ end
|
|
1057
949
|
|
1058
950
|
PaperTrail will call your proc with the current article and store the result in
|
1059
951
|
the `author_id` column of the `versions` table.
|
952
|
+
Don't forget to add any such columns to your `versions` table.
|
1060
953
|
|
1061
954
|
### Advantages of Metadata
|
1062
955
|
|
@@ -1104,7 +997,76 @@ end
|
|
1104
997
|
If you're using [strong_parameters][18] instead of [protected_attributes][17]
|
1105
998
|
then there is no need to use `attr_accessible`.
|
1106
999
|
|
1107
|
-
##
|
1000
|
+
## Custom Version Classes
|
1001
|
+
|
1002
|
+
You can specify custom version subclasses with the `:class_name` option:
|
1003
|
+
|
1004
|
+
```ruby
|
1005
|
+
class PostVersion < PaperTrail::Version
|
1006
|
+
# custom behaviour, e.g:
|
1007
|
+
self.table_name = :post_versions
|
1008
|
+
end
|
1009
|
+
|
1010
|
+
class Post < ActiveRecord::Base
|
1011
|
+
has_paper_trail :class_name => 'PostVersion'
|
1012
|
+
end
|
1013
|
+
```
|
1014
|
+
|
1015
|
+
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`).
|
1016
|
+
|
1017
|
+
### Advantages
|
1018
|
+
|
1019
|
+
1. For models which have a lot of versions, storing each model's versions in a
|
1020
|
+
separate table can improve the performance of certain database queries.
|
1021
|
+
1. Store different version [metadata](#storing-metadata) for different models.
|
1022
|
+
|
1023
|
+
### Configuration
|
1024
|
+
|
1025
|
+
If you are using Postgres, you should also define the sequence that your custom
|
1026
|
+
version class will use:
|
1027
|
+
|
1028
|
+
```ruby
|
1029
|
+
class PostVersion < PaperTrail::Version
|
1030
|
+
self.table_name = :post_versions
|
1031
|
+
self.sequence_name = :post_versions_id_seq
|
1032
|
+
end
|
1033
|
+
```
|
1034
|
+
|
1035
|
+
If you only use custom version classes and don't have a `versions` table, you
|
1036
|
+
must let ActiveRecord know that the `PaperTrail::Version` class is an
|
1037
|
+
`abstract_class`.
|
1038
|
+
|
1039
|
+
```ruby
|
1040
|
+
# app/models/paper_trail/version.rb
|
1041
|
+
module PaperTrail
|
1042
|
+
class Version < ActiveRecord::Base
|
1043
|
+
include PaperTrail::VersionConcern
|
1044
|
+
self.abstract_class = true
|
1045
|
+
end
|
1046
|
+
end
|
1047
|
+
```
|
1048
|
+
|
1049
|
+
You can also specify custom names for the versions and version associations.
|
1050
|
+
This is useful if you already have `versions` or/and `version` methods on your
|
1051
|
+
model. For example:
|
1052
|
+
|
1053
|
+
```ruby
|
1054
|
+
class Post < ActiveRecord::Base
|
1055
|
+
has_paper_trail :versions => :paper_trail_versions,
|
1056
|
+
:version => :paper_trail_version
|
1057
|
+
|
1058
|
+
# Existing versions method. We don't want to clash.
|
1059
|
+
def versions
|
1060
|
+
...
|
1061
|
+
end
|
1062
|
+
# Existing version method. We don't want to clash.
|
1063
|
+
def version
|
1064
|
+
...
|
1065
|
+
end
|
1066
|
+
end
|
1067
|
+
```
|
1068
|
+
|
1069
|
+
## Custom Serializer
|
1108
1070
|
|
1109
1071
|
By default, PaperTrail stores your changes as a `YAML` dump. You can override
|
1110
1072
|
this with the serializer config option:
|
@@ -1122,7 +1084,7 @@ method. These serializers are included in the gem for your convenience:
|
|
1122
1084
|
### PostgreSQL JSON column type support
|
1123
1085
|
|
1124
1086
|
If you use PostgreSQL, and would like to store your `object` (and/or
|
1125
|
-
`object_changes`) data in a column of [type `
|
1087
|
+
`object_changes`) data in a column of [type `json` or type `jsonb`][26], specify
|
1126
1088
|
`json` instead of `text` for these columns in your migration:
|
1127
1089
|
|
1128
1090
|
```ruby
|
@@ -1134,37 +1096,88 @@ create_table :versions do |t|
|
|
1134
1096
|
end
|
1135
1097
|
```
|
1136
1098
|
|
1137
|
-
|
1138
|
-
|
1099
|
+
If you use the PostgreSQL `json` or `jsonb` column type, you do not need
|
1100
|
+
to specify a `PaperTrail.serializer`.
|
1101
|
+
|
1102
|
+
#### Convert existing YAML data to JSON
|
1103
|
+
|
1104
|
+
If you've been using PaperTrail for a while with the default YAML serializer
|
1105
|
+
and you want to switch to JSON or JSONB, you're in a bit of a bind because
|
1106
|
+
there's no automatic way to migrate your data. The first (slow) option is to
|
1107
|
+
loop over every record and parse it in Ruby, then write to a temporary column:
|
1108
|
+
|
1109
|
+
```ruby
|
1110
|
+
add_column :versions, :object, :new_object, :jsonb # or :json
|
1111
|
+
|
1112
|
+
PaperTrail::Version.reset_column_information
|
1113
|
+
PaperTrail::Version.find_each do |version|
|
1114
|
+
version.update_column :new_object, YAML.load(version.object)
|
1115
|
+
end
|
1116
|
+
|
1117
|
+
remove_column :versions, :object
|
1118
|
+
rename_column :versions, :new_object, :object
|
1119
|
+
```
|
1120
|
+
|
1121
|
+
This technique can be very slow if you have a lot of data. Though slow, it is
|
1122
|
+
safe in databases where transactions are protected against DDL, such as
|
1123
|
+
Postgres. In databases without such protection, such as MySQL, a table lock may
|
1124
|
+
be necessary.
|
1125
|
+
|
1126
|
+
If the above technique is too slow for your needs, and you're okay doing without
|
1127
|
+
PaperTrail data temporarily, you can create the new column without a converting
|
1128
|
+
the data.
|
1129
|
+
|
1130
|
+
```ruby
|
1131
|
+
rename_column :versions, :object, :old_object
|
1132
|
+
add_column :versions, :object, :jsonb # or :json
|
1133
|
+
```
|
1134
|
+
|
1135
|
+
After that migration, your historical data still exists as YAML, and new data
|
1136
|
+
will be stored as JSON. Next, convert records from YAML to JSON using a
|
1137
|
+
background script.
|
1138
|
+
|
1139
|
+
```ruby
|
1140
|
+
PaperTrail::Version.where.not(old_object: nil).find_each do |version|
|
1141
|
+
version.update_columns old_object: nil, object: YAML.load(version.old_object)
|
1142
|
+
end
|
1143
|
+
```
|
1144
|
+
|
1145
|
+
Finally, in another migration, remove the old column.
|
1146
|
+
|
1147
|
+
```ruby
|
1148
|
+
remove_column :versions, :old_object
|
1149
|
+
```
|
1150
|
+
|
1151
|
+
If you use the optional `object_changes` column, don't forget to convert it
|
1152
|
+
also, using the same technique.
|
1139
1153
|
|
1140
|
-
#### Convert a
|
1154
|
+
#### Convert a Column from Text to JSON
|
1141
1155
|
|
1142
|
-
|
1143
|
-
|
1156
|
+
If your `object` column already contains JSON data, and you want to change its
|
1157
|
+
data type to `json` or `jsonb`, you can use the following [DDL][36]. Of course,
|
1158
|
+
if your `object` column contains YAML, you must first convert the data to JSON
|
1159
|
+
(see above) before you can change the column type.
|
1160
|
+
|
1161
|
+
Using SQL:
|
1144
1162
|
|
1145
1163
|
```sql
|
1146
1164
|
alter table versions
|
1147
|
-
alter column object type
|
1148
|
-
using object::
|
1165
|
+
alter column object type jsonb
|
1166
|
+
using object::jsonb;
|
1149
1167
|
```
|
1150
1168
|
|
1151
|
-
|
1152
|
-
|
1153
|
-
PaperTrail has a config option that can be used to enable/disable whether
|
1154
|
-
PaperTrail attempts to utilize `ActiveRecord`'s `serialized_attributes` feature.
|
1155
|
-
Note: This is enabled by default when PaperTrail is used with `ActiveRecord`
|
1156
|
-
version < `4.2`, and disabled by default when used with ActiveRecord `4.2.x`.
|
1157
|
-
Since `serialized_attributes` will be removed in `ActiveRecord` version `5.0`,
|
1158
|
-
this configuration value has no functionality when PaperTrail is used with
|
1159
|
-
version `5.0` or greater.
|
1169
|
+
Using ActiveRecord:
|
1160
1170
|
|
1161
1171
|
```ruby
|
1162
|
-
|
1163
|
-
|
1164
|
-
|
1165
|
-
|
1166
|
-
|
1167
|
-
|
1172
|
+
class ConvertVersionsObjectToJson < ActiveRecord::Migration
|
1173
|
+
def up
|
1174
|
+
change_column :versions, :object, 'jsonb USING object::jsonb'
|
1175
|
+
end
|
1176
|
+
|
1177
|
+
def down
|
1178
|
+
change_column :versions, :object, 'text USING object::text'
|
1179
|
+
end
|
1180
|
+
end
|
1168
1181
|
```
|
1169
1182
|
|
1170
1183
|
## Testing
|
@@ -1342,9 +1355,9 @@ require 'paper_trail/frameworks/rspec'
|
|
1342
1355
|
|
1343
1356
|
Paper Trail has facilities to test against Postgres, Mysql and SQLite. To switch
|
1344
1357
|
between DB engines you will need to export the DB variable for the engine you
|
1345
|
-
wish to test
|
1358
|
+
wish to test against.
|
1346
1359
|
|
1347
|
-
Though be aware we do not have the
|
1360
|
+
Though be aware we do not have the ability to create the db's (except sqlite) for
|
1348
1361
|
you. You can look at .travis.yml before_script for an example of how to create
|
1349
1362
|
the db's needed.
|
1350
1363
|
|
@@ -1354,6 +1367,52 @@ export DB=mysql
|
|
1354
1367
|
export DB=sqlite # this is default
|
1355
1368
|
```
|
1356
1369
|
|
1370
|
+
## Sinatra
|
1371
|
+
|
1372
|
+
In order to configure PaperTrail for usage with [Sinatra][12], your `Sinatra`
|
1373
|
+
app must be using `ActiveRecord` 3 or 4. It is also recommended to use the
|
1374
|
+
[Sinatra ActiveRecord Extension][13] or something similar for managing your
|
1375
|
+
applications `ActiveRecord` connection in a manner similar to the way `Rails`
|
1376
|
+
does. If using the aforementioned `Sinatra ActiveRecord Extension`, steps for
|
1377
|
+
setting up your app with PaperTrail will look something like this:
|
1378
|
+
|
1379
|
+
1. Add PaperTrail to your `Gemfile`.
|
1380
|
+
|
1381
|
+
`gem 'paper_trail', '~> 4.1.0'`
|
1382
|
+
|
1383
|
+
2. Generate a migration to add a `versions` table to your database.
|
1384
|
+
|
1385
|
+
`bundle exec rake db:create_migration NAME=create_versions`
|
1386
|
+
|
1387
|
+
3. Copy contents of [create_versions.rb][14]
|
1388
|
+
into the `create_versions` migration that was generated into your `db/migrate` directory.
|
1389
|
+
|
1390
|
+
4. Run the migration.
|
1391
|
+
|
1392
|
+
`bundle exec rake db:migrate`
|
1393
|
+
|
1394
|
+
5. Add `has_paper_trail` to the models you want to track.
|
1395
|
+
|
1396
|
+
|
1397
|
+
PaperTrail provides a helper extension that acts similar to the controller mixin
|
1398
|
+
it provides for `Rails` applications.
|
1399
|
+
|
1400
|
+
It will set `PaperTrail.whodunnit` to whatever is returned by a method named
|
1401
|
+
`user_for_paper_trail` which you can define inside your Sinatra Application. (by
|
1402
|
+
default it attempts to invoke a method named `current_user`)
|
1403
|
+
|
1404
|
+
If you're using the modular [`Sinatra::Base`][15] style of application, you will
|
1405
|
+
need to register the extension:
|
1406
|
+
|
1407
|
+
```ruby
|
1408
|
+
# bleh_app.rb
|
1409
|
+
require 'sinatra/base'
|
1410
|
+
|
1411
|
+
class BlehApp < Sinatra::Base
|
1412
|
+
register PaperTrail::Sinatra
|
1413
|
+
end
|
1414
|
+
```
|
1415
|
+
|
1357
1416
|
## Articles
|
1358
1417
|
|
1359
1418
|
* [Jutsu #8 - Version your RoR models with PaperTrail](http://samurails.com/gems/papertrail/),
|
@@ -1372,7 +1431,6 @@ export DB=sqlite # this is default
|
|
1372
1431
|
|
1373
1432
|
Please use GitHub's [issue tracker](http://github.com/airblade/paper_trail/issues).
|
1374
1433
|
|
1375
|
-
|
1376
1434
|
## Contributors
|
1377
1435
|
|
1378
1436
|
Many thanks to:
|
@@ -1436,7 +1494,6 @@ Many thanks to:
|
|
1436
1494
|
* [Simply Versioned](http://github.com/github/simply_versioned)
|
1437
1495
|
* [Acts As Audited](http://github.com/collectiveidea/acts_as_audited)
|
1438
1496
|
|
1439
|
-
|
1440
1497
|
## Intellectual Property
|
1441
1498
|
|
1442
1499
|
Copyright (c) 2011 Andy Stewart (boss@airbladesoftware.com).
|
@@ -1445,7 +1502,7 @@ Released under the MIT licence.
|
|
1445
1502
|
[1]: http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
|
1446
1503
|
[2]: https://github.com/airblade/paper_trail/issues/163
|
1447
1504
|
[3]: http://railscasts.com/episodes/255-undo-with-paper-trail
|
1448
|
-
[4]: https://
|
1505
|
+
[4]: https://api.travis-ci.org/airblade/paper_trail.svg?branch=master
|
1449
1506
|
[5]: https://travis-ci.org/airblade/paper_trail
|
1450
1507
|
[6]: https://img.shields.io/gemnasium/airblade/paper_trail.svg
|
1451
1508
|
[7]: https://gemnasium.com/airblade/paper_trail
|
@@ -1474,3 +1531,6 @@ Released under the MIT licence.
|
|
1474
1531
|
[31]: https://github.com/rails/spring
|
1475
1532
|
[32]: http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html#method-i-mark_for_destruction
|
1476
1533
|
[33]: https://github.com/airblade/paper_trail/wiki/Setting-whodunnit-in-the-rails-console
|
1534
|
+
[34]: https://github.com/rails/rails/blob/591a0bb87fff7583e01156696fbbf929d48d3e54/activerecord/lib/active_record/fixtures.rb#L142
|
1535
|
+
[35]: https://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html
|
1536
|
+
[36]: http://www.postgresql.org/docs/9.4/interactive/ddl.html
|