paper_trail 4.0.2 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -0
  3. data/CHANGELOG.md +27 -0
  4. data/CONTRIBUTING.md +78 -5
  5. data/README.md +328 -268
  6. data/doc/bug_report_template.rb +65 -0
  7. data/gemfiles/3.0.gemfile +7 -4
  8. data/lib/generators/paper_trail/templates/add_object_changes_to_versions.rb +1 -1
  9. data/lib/generators/paper_trail/templates/create_versions.rb +14 -0
  10. data/lib/paper_trail.rb +11 -9
  11. data/lib/paper_trail/attributes_serialization.rb +89 -0
  12. data/lib/paper_trail/cleaner.rb +8 -1
  13. data/lib/paper_trail/config.rb +15 -18
  14. data/lib/paper_trail/frameworks/rails/controller.rb +16 -2
  15. data/lib/paper_trail/has_paper_trail.rb +102 -99
  16. data/lib/paper_trail/record_history.rb +59 -0
  17. data/lib/paper_trail/reifier.rb +270 -0
  18. data/lib/paper_trail/version_association_concern.rb +3 -1
  19. data/lib/paper_trail/version_concern.rb +60 -226
  20. data/lib/paper_trail/version_number.rb +2 -2
  21. data/paper_trail.gemspec +7 -10
  22. data/spec/models/animal_spec.rb +17 -0
  23. data/spec/models/callback_modifier_spec.rb +96 -0
  24. data/spec/models/json_version_spec.rb +20 -17
  25. data/spec/paper_trail/config_spec.rb +52 -0
  26. data/spec/spec_helper.rb +6 -0
  27. data/test/dummy/app/models/callback_modifier.rb +45 -0
  28. data/test/dummy/app/models/chapter.rb +9 -0
  29. data/test/dummy/app/models/citation.rb +5 -0
  30. data/test/dummy/app/models/paragraph.rb +5 -0
  31. data/test/dummy/app/models/quotation.rb +5 -0
  32. data/test/dummy/app/models/section.rb +6 -0
  33. data/test/dummy/config/database.postgres.yml +1 -1
  34. data/test/dummy/config/initializers/paper_trail.rb +3 -1
  35. data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +33 -0
  36. data/test/dummy/db/schema.rb +27 -0
  37. data/test/test_helper.rb +36 -0
  38. data/test/unit/associations_test.rb +726 -0
  39. data/test/unit/inheritance_column_test.rb +6 -6
  40. data/test/unit/model_test.rb +62 -594
  41. data/test/unit/protected_attrs_test.rb +3 -2
  42. data/test/unit/version_test.rb +87 -69
  43. metadata +38 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8a92875e074ba53155be9597997716e75f2dfa33
4
- data.tar.gz: b87bb9cad5f094f61957c287e4a27db3cff0d5d1
3
+ metadata.gz: 80cb56f3a2c0989873fe5086674a3a170c6dbaf0
4
+ data.tar.gz: c88a259524d7ec2f8298360d7505ed17b23da9a8
5
5
  SHA512:
6
- metadata.gz: f5e16e1229742e460df8c0d1c6315a8207653f756b5df16d88b82e17ae07530260a0dab228afc894aeb3dcc765a7aa15f2b1b46f9366364abbba1bcb12ed61f5
7
- data.tar.gz: e5a3ad55b824efa44f70924a47e840ccc16733308298d66eb1de4ba07010eff61ba1ad6a54c50288c18b60d92462d94d025bbd1d14bf4f496349c1a39e43ea89
6
+ metadata.gz: 2ca33cf4ce53eb49d4ff3fbe795efd4c21f14e0b5600e4051c9d69896c5f69119b73cfa4417a340e1cf4821da4a59549d84a79d85a1975b4d4664d2d7bcef673
7
+ data.tar.gz: a74706949f90071143f97f04f22a069afd0c4b7981b0436d4f947c75a713afdf273683a095ea556f56f3927e33630ab0f17e6426bf9381edb5f7b9bc1c324782
@@ -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"
@@ -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) -
@@ -1,11 +1,84 @@
1
1
  # Contributing
2
2
 
3
- Thanks for your interest in PaperTrail! We appreciate bug reports, feature
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
- Please do not use github issues to ask usage questions. We simply don't have
7
- time to answer them all. Instead, please ask usage questions on Stack Overflow.
13
+ Thanks, and happy (paper) trails :)
8
14
 
9
- http://stackoverflow.com/tags/papertrail
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
- Thanks, and happy versioning :)
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
- - [Features](#features)
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
- - [Finding Out Who Was Responsible For A Change](#finding-out-who-was-responsible-for-a-change)
26
- - [Custom Version Classes](#custom-version-classes)
27
- - [Associations](#associations)
28
- - [Storing metadata](#storing-metadata)
29
- - [Using a custom serializer](#using-a-custom-serializer)
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.2'`
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
- 3. Run the migration.
62
+ 1. Add a `versions` table to your database.
80
63
 
81
- `bundle exec rake db:migrate`
82
-
83
- 4. Add `has_paper_trail` to the models you want to track.
64
+ ```
65
+ bundle exec rails generate paper_trail:install
66
+ bundle exec rake db:migrate
67
+ ```
84
68
 
85
- ### Sinatra
69
+ 1. Add `has_paper_trail` to the models you want to track.
86
70
 
87
- In order to configure PaperTrail for usage with [Sinatra][12], your `Sinatra`
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
- 1. Add PaperTrail to your `Gemfile`.
73
+ Add `has_paper_trail` to your model to record every `create`, `update`,
74
+ and `destroy`.
95
75
 
96
- `gem 'paper_trail', '~> 4.0.2'`
76
+ ```ruby
77
+ class Widget < ActiveRecord::Base
78
+ has_paper_trail
79
+ end
80
+ ```
97
81
 
98
- 2. Generate a migration to add a `versions` table to your database.
82
+ This gives you a `versions` method which returns the "paper trail" of changes to
83
+ your model.
99
84
 
100
- `bundle exec rake db:create_migration NAME=create_versions`
85
+ ```ruby
86
+ widget = Widget.find 42
87
+ widget.versions
88
+ # [<PaperTrail::Version>, <PaperTrail::Version>, ...]
89
+ ```
101
90
 
102
- 3. Copy contents of [create_versions.rb][14]
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
- 4. Run the migration.
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
- `bundle exec rake db:migrate`
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
- 5. Add `has_paper_trail` to the models you want to track.
109
+ ```ruby
110
+ widget = Widget.find 153
111
+ widget.name # 'Doobly'
110
112
 
113
+ # Add has_paper_trail to Widget model.
111
114
 
112
- PaperTrail provides a helper extension that acts similar to the controller mixin
113
- it provides for `Rails` applications.
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
- It will set `PaperTrail.whodunnit` to whatever is returned by a method named
116
- `user_for_paper_trail` which you can define inside your Sinatra Application. (by
117
- default it attempts to invoke a method named `current_user`)
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
- If you're using the modular [`Sinatra::Base`][15] style of application, you will
120
- need to register the extension:
126
+ Here's a helpful table showing what PaperTrail stores:
121
127
 
122
- ```ruby
123
- # bleh_app.rb
124
- require 'sinatra/base'
128
+ | *Event* | *create* | *update* | *destroy* |
129
+ | -------------- | -------- | -------- | --------- |
130
+ | *Model Before* | nil | widget | widget |
131
+ | *Model After* | widget | widget | nil |
125
132
 
126
- class BlehApp < Sinatra::Base
127
- register PaperTrail::Sinatra
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 # you can pass various options here
142
+ has_paper_trail
138
143
  end
139
144
 
140
- # Returns this widget's versions. You can customise the name of the association.
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 a previous version.
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 version)
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
- # Check whether PaperTrail is enabled for all widgets.
179
+ # Is PaperTrail enabled for Widget, the class?
172
180
  Widget.paper_trail_enabled_for_model?
173
- widget.paper_trail_enabled_for_model? # only available on instances of versioned models
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..paper_trail_originator # 'Alice'
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 Feature: Known Issues:**
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
- 2. Currently PaperTrail only support single `version_associations` table. The
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
- 3. PaperTrail only reifies the first level of associations, i.e., it does not
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
- 4. PaperTrail relies on the callbacks on the association model (and the :through
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
- ## Using a custom serializer
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 `JSON` or type `JSONB`][26], specify
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
- Note: You don't need to use a particular serializer for the PostgreSQL `JSON`
1138
- column type.
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 column from text to json
1154
+ #### Convert a Column from Text to JSON
1141
1155
 
1142
- Postgres' `alter column` command will not automatically convert a `text`
1143
- column to `json`, but it can still be done with plain SQL.
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 json
1148
- using object::json;
1165
+ alter column object type jsonb
1166
+ using object::jsonb;
1149
1167
  ```
1150
1168
 
1151
- ## SerializedAttributes support
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
- # Enable support
1163
- PaperTrail.config.serialized_attributes = true
1164
- # Disable support
1165
- PaperTrail.config.serialized_attributes = false
1166
- # Get current setting
1167
- PaperTrail.serialized_attributes?
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 aganist.
1358
+ wish to test against.
1346
1359
 
1347
- Though be aware we do not have the abilty to create the db's (except sqlite) for
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://img.shields.io/travis/airblade/paper_trail/master.svg
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