paper_trail 4.0.0 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (158) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CONTRIBUTING.md +105 -0
  3. data/.github/ISSUE_TEMPLATE.md +13 -0
  4. data/.gitignore +2 -0
  5. data/.rubocop.yml +100 -0
  6. data/.rubocop_todo.yml +14 -0
  7. data/.travis.yml +11 -10
  8. data/Appraisals +37 -0
  9. data/CHANGELOG.md +173 -8
  10. data/Gemfile +1 -1
  11. data/README.md +641 -470
  12. data/Rakefile +19 -19
  13. data/doc/bug_report_template.rb +71 -0
  14. data/doc/warning_about_not_setting_whodunnit.md +32 -0
  15. data/gemfiles/ar3.gemfile +18 -0
  16. data/gemfiles/ar4.gemfile +7 -0
  17. data/gemfiles/ar5.gemfile +13 -0
  18. data/lib/generators/paper_trail/install_generator.rb +26 -18
  19. data/lib/generators/paper_trail/templates/add_object_changes_to_versions.rb +3 -1
  20. data/lib/generators/paper_trail/templates/add_transaction_id_column_to_versions.rb +2 -0
  21. data/lib/generators/paper_trail/templates/create_version_associations.rb +9 -4
  22. data/lib/generators/paper_trail/templates/create_versions.rb +53 -5
  23. data/lib/paper_trail/attribute_serializers/README.md +10 -0
  24. data/lib/paper_trail/attribute_serializers/cast_attribute_serializer.rb +58 -0
  25. data/lib/paper_trail/attribute_serializers/legacy_active_record_shim.rb +48 -0
  26. data/lib/paper_trail/attribute_serializers/object_attribute.rb +39 -0
  27. data/lib/paper_trail/attribute_serializers/object_changes_attribute.rb +42 -0
  28. data/lib/paper_trail/cleaner.rb +41 -18
  29. data/lib/paper_trail/config.rb +42 -26
  30. data/lib/paper_trail/frameworks/active_record/models/paper_trail/version.rb +5 -1
  31. data/lib/paper_trail/frameworks/active_record/models/paper_trail/version_association.rb +6 -2
  32. data/lib/paper_trail/frameworks/active_record.rb +2 -2
  33. data/lib/paper_trail/frameworks/cucumber.rb +1 -0
  34. data/lib/paper_trail/frameworks/rails/controller.rb +50 -14
  35. data/lib/paper_trail/frameworks/rails/engine.rb +6 -1
  36. data/lib/paper_trail/frameworks/rails.rb +2 -7
  37. data/lib/paper_trail/frameworks/rspec/helpers.rb +3 -1
  38. data/lib/paper_trail/frameworks/rspec.rb +5 -5
  39. data/lib/paper_trail/frameworks/sinatra.rb +8 -5
  40. data/lib/paper_trail/has_paper_trail.rb +381 -221
  41. data/lib/paper_trail/record_history.rb +57 -0
  42. data/lib/paper_trail/reifier.rb +450 -0
  43. data/lib/paper_trail/serializers/json.rb +7 -7
  44. data/lib/paper_trail/serializers/yaml.rb +31 -12
  45. data/lib/paper_trail/version_association_concern.rb +6 -2
  46. data/lib/paper_trail/version_concern.rb +200 -287
  47. data/lib/paper_trail/version_number.rb +6 -9
  48. data/lib/paper_trail.rb +169 -137
  49. data/paper_trail.gemspec +41 -43
  50. data/spec/generators/install_generator_spec.rb +24 -25
  51. data/spec/generators/paper_trail/templates/create_versions_spec.rb +51 -0
  52. data/spec/models/animal_spec.rb +23 -6
  53. data/spec/models/boolit_spec.rb +8 -8
  54. data/spec/models/callback_modifier_spec.rb +96 -0
  55. data/spec/models/car_spec.rb +13 -0
  56. data/spec/models/fluxor_spec.rb +3 -3
  57. data/spec/models/gadget_spec.rb +19 -19
  58. data/spec/models/joined_version_spec.rb +3 -3
  59. data/spec/models/json_version_spec.rb +50 -28
  60. data/spec/models/kitchen/banana_spec.rb +3 -3
  61. data/spec/models/not_on_update_spec.rb +7 -4
  62. data/spec/models/post_with_status_spec.rb +13 -3
  63. data/spec/models/skipper_spec.rb +40 -11
  64. data/spec/models/thing_spec.rb +4 -4
  65. data/spec/models/truck_spec.rb +5 -0
  66. data/spec/models/vehicle_spec.rb +5 -0
  67. data/spec/models/version_spec.rb +103 -59
  68. data/spec/models/widget_spec.rb +86 -55
  69. data/spec/modules/paper_trail_spec.rb +2 -2
  70. data/spec/modules/version_concern_spec.rb +11 -12
  71. data/spec/modules/version_number_spec.rb +3 -4
  72. data/spec/paper_trail/config_spec.rb +33 -0
  73. data/spec/paper_trail_spec.rb +16 -14
  74. data/spec/rails_helper.rb +10 -9
  75. data/spec/requests/articles_spec.rb +11 -7
  76. data/spec/spec_helper.rb +42 -17
  77. data/spec/support/alt_db_init.rb +8 -13
  78. data/test/custom_json_serializer.rb +3 -3
  79. data/test/dummy/Rakefile +2 -2
  80. data/test/dummy/app/controllers/application_controller.rb +21 -8
  81. data/test/dummy/app/controllers/articles_controller.rb +11 -8
  82. data/test/dummy/app/controllers/widgets_controller.rb +13 -12
  83. data/test/dummy/app/models/animal.rb +1 -1
  84. data/test/dummy/app/models/article.rb +19 -11
  85. data/test/dummy/app/models/authorship.rb +1 -1
  86. data/test/dummy/app/models/bar_habtm.rb +4 -0
  87. data/test/dummy/app/models/book.rb +4 -4
  88. data/test/dummy/app/models/boolit.rb +1 -1
  89. data/test/dummy/app/models/callback_modifier.rb +45 -0
  90. data/test/dummy/app/models/car.rb +3 -0
  91. data/test/dummy/app/models/chapter.rb +9 -0
  92. data/test/dummy/app/models/citation.rb +5 -0
  93. data/test/dummy/app/models/customer.rb +1 -1
  94. data/test/dummy/app/models/document.rb +2 -2
  95. data/test/dummy/app/models/editor.rb +1 -1
  96. data/test/dummy/app/models/foo_habtm.rb +5 -0
  97. data/test/dummy/app/models/fruit.rb +2 -2
  98. data/test/dummy/app/models/gadget.rb +1 -1
  99. data/test/dummy/app/models/kitchen/banana.rb +1 -1
  100. data/test/dummy/app/models/legacy_widget.rb +2 -2
  101. data/test/dummy/app/models/line_item.rb +1 -1
  102. data/test/dummy/app/models/not_on_update.rb +1 -1
  103. data/test/dummy/app/models/paragraph.rb +5 -0
  104. data/test/dummy/app/models/person.rb +6 -6
  105. data/test/dummy/app/models/post.rb +1 -1
  106. data/test/dummy/app/models/post_with_status.rb +1 -1
  107. data/test/dummy/app/models/quotation.rb +5 -0
  108. data/test/dummy/app/models/section.rb +6 -0
  109. data/test/dummy/app/models/skipper.rb +2 -2
  110. data/test/dummy/app/models/song.rb +13 -4
  111. data/test/dummy/app/models/thing.rb +2 -2
  112. data/test/dummy/app/models/translation.rb +2 -2
  113. data/test/dummy/app/models/truck.rb +4 -0
  114. data/test/dummy/app/models/vehicle.rb +4 -0
  115. data/test/dummy/app/models/whatchamajigger.rb +1 -1
  116. data/test/dummy/app/models/widget.rb +7 -6
  117. data/test/dummy/app/versions/joined_version.rb +4 -3
  118. data/test/dummy/app/versions/json_version.rb +1 -1
  119. data/test/dummy/app/versions/kitchen/banana_version.rb +1 -1
  120. data/test/dummy/app/versions/post_version.rb +2 -2
  121. data/test/dummy/config/application.rb +20 -9
  122. data/test/dummy/config/boot.rb +5 -5
  123. data/test/dummy/config/database.postgres.yml +1 -1
  124. data/test/dummy/config/environment.rb +1 -1
  125. data/test/dummy/config/environments/development.rb +4 -3
  126. data/test/dummy/config/environments/production.rb +3 -2
  127. data/test/dummy/config/environments/test.rb +15 -5
  128. data/test/dummy/config/initializers/backtrace_silencers.rb +4 -2
  129. data/test/dummy/config/initializers/paper_trail.rb +4 -3
  130. data/test/dummy/config/initializers/secret_token.rb +3 -1
  131. data/test/dummy/config/initializers/session_store.rb +1 -1
  132. data/test/dummy/config/routes.rb +2 -2
  133. data/test/dummy/config.ru +1 -1
  134. data/test/dummy/db/migrate/20110208155312_set_up_test_tables.rb +148 -68
  135. data/test/dummy/db/schema.rb +119 -31
  136. data/test/dummy/script/rails +6 -4
  137. data/test/functional/controller_test.rb +34 -35
  138. data/test/functional/enabled_for_controller_test.rb +6 -7
  139. data/test/functional/modular_sinatra_test.rb +43 -38
  140. data/test/functional/sinatra_test.rb +49 -40
  141. data/test/functional/thread_safety_test.rb +4 -6
  142. data/test/paper_trail_test.rb +15 -14
  143. data/test/test_helper.rb +78 -18
  144. data/test/time_travel_helper.rb +1 -15
  145. data/test/unit/associations_test.rb +1016 -0
  146. data/test/unit/cleaner_test.rb +66 -60
  147. data/test/unit/inheritance_column_test.rb +19 -19
  148. data/test/unit/model_test.rb +646 -1071
  149. data/test/unit/protected_attrs_test.rb +19 -14
  150. data/test/unit/serializer_test.rb +44 -43
  151. data/test/unit/serializers/json_test.rb +28 -21
  152. data/test/unit/serializers/mixin_json_test.rb +15 -14
  153. data/test/unit/serializers/mixin_yaml_test.rb +20 -16
  154. data/test/unit/serializers/yaml_test.rb +16 -14
  155. data/test/unit/timestamp_test.rb +10 -12
  156. data/test/unit/version_test.rb +88 -70
  157. metadata +166 -72
  158. data/gemfiles/3.0.gemfile +0 -52
data/README.md CHANGED
@@ -1,146 +1,176 @@
1
- # PaperTrail [![Build Status][4]][5] [![Dependency Status][6]][7]
2
-
3
- PaperTrail lets you track changes to your models' data. It's good for auditing
4
- or versioning. You can see how a model looked at any stage in its lifecycle,
5
- revert it to any version, and even undelete it after it's been destroyed.
6
-
7
- - [Features](#features)
8
- - [Compatibility](#compatibility)
9
- - [Installation](#installation)
10
- - [API Summary](#api-summary)
11
- - [Basic Usage](#basic-usage)
12
- - [Choosing Lifecycle Events To Monitor](#choosing-lifecycle-events-to-monitor)
13
- - [Choosing When To Save New Versions](#choosing-when-to-save-new-versions)
14
- - [Choosing Attributes To Monitor](#choosing-attributes-to-monitor)
15
- - [Reverting And Undeleting A Model](#reverting-and-undeleting-a-model)
16
- - [Navigating Versions](#navigating-versions)
17
- - [Finding Out Who Was Responsible For A Change](#finding-out-who-was-responsible-for-a-change)
18
- - [Custom Version Classes](#custom-version-classes)
19
- - [Associations](#associations)
20
- - [Storing metadata](#storing-metadata)
21
- - [Diffing Versions](#diffing-versions)
22
- - [Turning PaperTrail Off/On](#turning-papertrail-offon)
23
- - [Using a custom serializer](#using-a-custom-serializer)
24
- - [SerializedAttributes support](#serializedattributes-support)
25
- - [Limiting the Number of Versions Created](#limiting-the-number-of-versions-created)
26
- - [Deleting Old Versions](#deleting-old-versions)
27
- - [Testing](#testing)
28
-
29
- ## Features
30
-
31
- * Stores every create, update and destroy (or only the lifecycle events you specify).
32
- * Does not store updates which don't change anything.
33
- * Allows you to specify attributes (by inclusion or exclusion) which must change for a Version to be stored.
34
- * Allows you to get at every version, including the original, even once destroyed.
35
- * Allows you to get at every version even if the schema has since changed.
36
- * Allows you to get at the version as of a particular time.
37
- * Option to automatically restore `has_one`, `has_many` and `has_many :through` associations as they were at the time.
38
- * Automatically records who was responsible via your controller. PaperTrail calls `current_user` by default, if it exists, but you can have it call any method you like.
39
- * Allows you to set who is responsible at model-level (useful for migrations).
40
- * Allows you to store arbitrary model-level metadata with each version (useful for filtering versions).
41
- * Allows you to store arbitrary controller-level information with each version, e.g. remote IP.
42
- * Can be turned off/on per class (useful for migrations).
43
- * Can be turned off/on per request (useful for testing with an external service).
44
- * Can be turned off/on globally (useful for testing).
45
- * No configuration necessary.
46
- * Stores everything in a single database table by default (generates migration for you), or can use separate tables for separate models.
47
- * Supports custom version classes so different models' versions can have different behaviour.
48
- * Supports custom name for versions association.
49
- * Thoroughly tested.
50
- * Threadsafe.
51
-
52
-
53
- ## Compatibility
54
-
55
- Works with `ActiveRecord` 3+. Note: this code is on the `master` branch and tagged `v4.x`.
56
-
57
- Version 3 is on the branch named [`3.0-stable`][9] and is tagged `v3.x`, and works ActiveRecord 4 and ActiveRecord 3.
58
-
59
- Version 2 is on the branch named [`2.7-stable`][10] and is tagged `v2.x`, and works with Rails 3.
60
-
61
- The Rails 2.3 code is on the [`rails2`][11] branch and tagged `v1.x`. These branches are both stable with their respective versions of Rails but will not have new features added/backported to them.
62
-
63
- ## Installation
64
-
65
- ### Rails 3 & 4
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
+ | 5 | https://github.com/airblade/paper_trail/blob/master/README.md |
14
+ | 4.1 | https://github.com/airblade/paper_trail/blob/4.1-stable/README.md |
15
+ | 4.0 | https://github.com/airblade/paper_trail/blob/4.0-stable/README.md |
16
+ | 3 | https://github.com/airblade/paper_trail/blob/3.0-stable/README.md |
17
+ | 2 | https://github.com/airblade/paper_trail/blob/2.7-stable/README.md |
18
+ | 1 | https://github.com/airblade/paper_trail/blob/rails2/README.md |
19
+
20
+ ## Table of Contents
21
+
22
+ - [1. Introduction](#1-introduction)
23
+ - [1.a. Compatibility](#1a-compatibility)
24
+ - [1.b. Installation](#1b-installation)
25
+ - [1.c. Basic Usage](#1c-basic-usage)
26
+ - [1.d. API Summary](#1d-api-summary)
27
+ - [2. Limiting What is Versioned, and When](#2-limiting-what-is-versioned-and-when)
28
+ - [2.a. Choosing Lifecycle Events To Monitor](#2a-choosing-lifecycle-events-to-monitor)
29
+ - [2.b. Choosing When To Save New Versions](#2b-choosing-when-to-save-new-versions)
30
+ - [2.c. Choosing Attributes To Monitor](#2c-choosing-attributes-to-monitor)
31
+ - [2.d. Turning PaperTrail Off](#2d-turning-papertrail-off)
32
+ - [2.e. Limiting the Number of Versions Created](#2e-limiting-the-number-of-versions-created)
33
+ - [3. Working With Versions](#3-working-with-versions)
34
+ - [3.a. Reverting And Undeleting A Model](#3a-reverting-and-undeleting-a-model)
35
+ - [3.b. Navigating Versions](#3b-navigating-versions)
36
+ - [3.c. Diffing Versions](#3c-diffing-versions)
37
+ - [3.d. Deleting Old Versions](#3d-deleting-old-versions)
38
+ - [4. Saving More Information About Versions](#4-saving-more-information-about-versions)
39
+ - [4.a. Finding Out Who Was Responsible For A Change](#4a-finding-out-who-was-responsible-for-a-change)
40
+ - [4.b. Associations](#4b-associations)
41
+ - [4.c. Storing metadata](#4c-storing-metadata)
42
+ - [5. ActiveRecord](#5-activerecord)
43
+ - [5.a. Single Table Inheritance](#5a-single-table-inheritance-sti)
44
+ - [5.b. Configuring the `versions` Association](#5b-configuring-the-versions-association)
45
+ - [5.c. Generators](#5c-generators)
46
+ - [6. Extensibility](#6-extensibility)
47
+ - [6.a. Custom Version Classes](#6a-custom-version-classes)
48
+ - [6.b. Custom Serializer](#6b-custom-serializer)
49
+ - [7. Testing](#7-testing)
50
+ - [8. Sinatra](#8-sinatra)
51
+
52
+ ## 1. Introduction
53
+
54
+ ### 1.a. Compatibility
55
+
56
+ | paper_trail | branch | tags | ruby | activerecord |
57
+ | -------------- | ---------- | ------ | -------- | ------------ |
58
+ | 5 | master | v5.x | >= 1.9.3 | >= 3.0, < 6 |
59
+ | 4 | 4.0-stable | v4.x | >= 1.8.7 | >= 3.0, < 6 |
60
+ | 3 | 3.0-stable | v3.x | >= 1.8.7 | >= 3.0, < 5 |
61
+ | 2 | 2.7-stable | v2.x | >= 1.8.7 | >= 3.0, < 4 |
62
+ | 1 | rails2 | v1.x | >= 1.8.7 | >= 2.3, < 3 |
63
+
64
+ ### 1.b. Installation
66
65
 
67
66
  1. Add PaperTrail to your `Gemfile`.
68
67
 
69
- `gem 'paper_trail', '~> 4.0.0'`
68
+ `gem 'paper_trail'`
70
69
 
71
- 2. Generate a migration which will add a `versions` table to your database.
70
+ 1. Add a `versions` table to your database.
72
71
 
73
- `bundle exec rails generate paper_trail:install`
72
+ ```
73
+ bundle exec rails generate paper_trail:install
74
+ bundle exec rake db:migrate
75
+ ```
74
76
 
75
- 3. Run the migration.
77
+ If using [rails_admin][38], you must enable the experimental
78
+ [Associations](#associations) feature. For more information on this
79
+ generator, see [section 5.c. Generators](#5c-generators).
76
80
 
77
- `bundle exec rake db:migrate`
78
-
79
- 4. Add `has_paper_trail` to the models you want to track.
81
+ 1. Add `has_paper_trail` to the models you want to track.
80
82
 
81
- ### Sinatra
83
+ ```ruby
84
+ class Widget < ActiveRecord::Base
85
+ has_paper_trail
86
+ end
87
+ ```
82
88
 
83
- In order to configure PaperTrail for usage with [Sinatra][12], your `Sinatra`
84
- app must be using `ActiveRecord` 3 or 4. It is also recommended to use the
85
- [Sinatra ActiveRecord Extension][13] or something similar for managing your
86
- applications `ActiveRecord` connection in a manner similar to the way `Rails`
87
- does. If using the aforementioned `Sinatra ActiveRecord Extension`, steps for
88
- setting up your app with PaperTrail will look something like this:
89
+ 1. If your controllers have a `current_user` method, you can easily [track who
90
+ is responsible for changes](#finding-out-who-was-responsible-for-a-change)
91
+ by adding a controller callback.
89
92
 
90
- 1. Add PaperTrail to your `Gemfile`.
93
+ ```ruby
94
+ class ApplicationController
95
+ before_filter :set_paper_trail_whodunnit
96
+ end
97
+ ```
91
98
 
92
- `gem 'paper_trail', '~> 4.0.0'`
99
+ ### 1.c. Basic Usage
93
100
 
94
- 2. Generate a migration to add a `versions` table to your database.
101
+ Your models now have a `versions` method which returns the "paper trail" of
102
+ changes to your model.
95
103
 
96
- `bundle exec rake db:create_migration NAME=create_versions`
104
+ ```ruby
105
+ widget = Widget.find 42
106
+ widget.versions
107
+ # [<PaperTrail::Version>, <PaperTrail::Version>, ...]
108
+ ```
97
109
 
98
- 3. Copy contents of [create_versions.rb][14]
99
- into the `create_versions` migration that was generated into your `db/migrate` directory.
110
+ Once you have a version, you can find out what happened:
100
111
 
101
- 4. Run the migration.
112
+ ```ruby
113
+ v = widget.versions.last
114
+ v.event # 'update', 'create', or 'destroy'
115
+ v.created_at # When the `event` occurred
116
+ v.whodunnit # If the update was via a controller and the
117
+ # controller has a current_user method, returns the
118
+ # id of the current user as a string.
119
+ widget = v.reify # The widget as it was before the update
120
+ # (nil for a create event)
121
+ ```
102
122
 
103
- `bundle exec rake db:migrate`
123
+ PaperTrail stores the pre-change version of the model, unlike some other
124
+ auditing/versioning plugins, so you can retrieve the original version. This is
125
+ useful when you start keeping a paper trail for models that already have records
126
+ in the database.
104
127
 
105
- 5. Add `has_paper_trail` to the models you want to track.
128
+ ```ruby
129
+ widget = Widget.find 153
130
+ widget.name # 'Doobly'
106
131
 
132
+ # Add has_paper_trail to Widget model.
107
133
 
108
- PaperTrail provides a helper extension that acts similar to the controller mixin
109
- it provides for `Rails` applications.
134
+ widget.versions # []
135
+ widget.update_attributes :name => 'Wotsit'
136
+ widget.versions.last.reify.name # 'Doobly'
137
+ widget.versions.last.event # 'update'
138
+ ```
110
139
 
111
- It will set `PaperTrail.whodunnit` to whatever is returned by a method named
112
- `user_for_paper_trail` which you can define inside your Sinatra Application. (by
113
- default it attempts to invoke a method named `current_user`)
140
+ This also means that PaperTrail does not waste space storing a version of the
141
+ object as it currently stands. The `versions` method gives you previous
142
+ versions; to get the current one just call a finder on your `Widget` model as
143
+ usual.
114
144
 
115
- If you're using the modular [`Sinatra::Base`][15] style of application, you will
116
- need to register the extension:
145
+ Here's a helpful table showing what PaperTrail stores:
117
146
 
118
- ```ruby
119
- # bleh_app.rb
120
- require 'sinatra/base'
147
+ | *Event* | *create* | *update* | *destroy* |
148
+ | -------------- | -------- | -------- | --------- |
149
+ | *Model Before* | nil | widget | widget |
150
+ | *Model After* | widget | widget | nil |
121
151
 
122
- class BlehApp < Sinatra::Base
123
- register PaperTrail::Sinatra
124
- end
125
- ```
152
+ PaperTrail stores the values in the Model Before column. Most other
153
+ auditing/versioning plugins store the After column.
126
154
 
127
- ## API Summary
155
+ ### 1.d. API Summary
128
156
 
129
157
  When you declare `has_paper_trail` in your model, you get these methods:
130
158
 
131
159
  ```ruby
132
160
  class Widget < ActiveRecord::Base
133
- has_paper_trail # you can pass various options here
161
+ has_paper_trail
134
162
  end
135
163
 
136
- # Returns this widget's versions. You can customise the name of the association.
164
+ # Returns this widget's versions. You can customise the name of the
165
+ # association, but overriding this method is not supported.
137
166
  widget.versions
138
167
 
139
168
  # Return the version this widget was reified from, or nil if it is live.
140
169
  # You can customise the name of the method.
141
170
  widget.version
142
171
 
143
- # Returns true if this widget is the current, live one; or false if it is from a previous version.
172
+ # Returns true if this widget is the current, live one; or false if it is from
173
+ # a previous version.
144
174
  widget.live?
145
175
 
146
176
  # Returns who put the widget into its current state.
@@ -155,7 +185,8 @@ widget.previous_version
155
185
  # Returns the widget (not a version) as it became next.
156
186
  widget.next_version
157
187
 
158
- # Generates a version for a `touch` event (`widget.touch` does NOT generate a version)
188
+ # Generates a version for a `touch` event (`widget.touch` does NOT generate a
189
+ # version)
159
190
  widget.touch_with_version
160
191
 
161
192
  # Turn PaperTrail off for all widgets.
@@ -164,12 +195,15 @@ Widget.paper_trail_off!
164
195
  # Turn PaperTrail on for all widgets.
165
196
  Widget.paper_trail_on!
166
197
 
167
- # Check whether PaperTrail is enabled for all widgets.
198
+ # Is PaperTrail enabled for Widget, the class?
168
199
  Widget.paper_trail_enabled_for_model?
169
- widget.paper_trail_enabled_for_model? # only available on instances of versioned models
200
+
201
+ # Is PaperTrail enabled for widget, the instance?
202
+ widget.paper_trail_enabled_for_model?
170
203
  ```
171
204
 
172
- And a `PaperTrail::Version` instance has these methods:
205
+ And a `PaperTrail::Version` instance (which is just an ordinary ActiveRecord
206
+ instance, with all the usual methods) adds these methods:
173
207
 
174
208
  ```ruby
175
209
  # Returns the item restored from this version.
@@ -198,13 +232,11 @@ version.index
198
232
  # Returns the event that caused this version (create|update|destroy).
199
233
  version.event
200
234
 
201
- # Query versions objects by attributes.
235
+ # Query the `versions.object` column (or `object_changes` column), by
236
+ # attributes, using the SQL LIKE operator. Known issue: inconsistent results for
237
+ # numeric values due to limitations of SQL wildcard matchers against the
238
+ # serialized objects.
202
239
  PaperTrail::Version.where_object(attr1: val1, attr2: val2)
203
-
204
- # Query versions object_changes field by attributes (requires
205
- # `object_changes` column on versions table).
206
- # Also can't guarantee consistent query results for numeric values
207
- # due to limitations of SQL wildcard matchers against the serialized objects.
208
240
  PaperTrail::Version.where_object_changes(attr1: val1)
209
241
  ```
210
242
 
@@ -220,90 +252,9 @@ user_for_paper_trail
220
252
  info_for_paper_trail
221
253
  ```
222
254
 
223
- ## Basic Usage
224
-
225
- PaperTrail is simple to use. Just add 15 characters to a model to get a paper
226
- trail of every `create`, `update`, and `destroy`.
255
+ ## 2. Limiting What is Versioned, and When
227
256
 
228
- ```ruby
229
- class Widget < ActiveRecord::Base
230
- has_paper_trail
231
- end
232
- ```
233
-
234
- This gives you a `versions` method which returns the paper trail of changes to
235
- your model.
236
-
237
- ```ruby
238
- widget = Widget.find 42
239
- widget.versions
240
- # [<PaperTrail::Version>, <PaperTrail::Version>, ...]
241
- ```
242
-
243
- Once you have a version, you can find out what happened:
244
-
245
- ```ruby
246
- v = widget.versions.last
247
- v.event # 'update' (or 'create' or 'destroy')
248
- v.whodunnit # '153' (if the update was via a controller and
249
- # the controller has a current_user method,
250
- # here returning the id of the current user)
251
- v.created_at # when the update occurred
252
- widget = v.reify # the widget as it was before the update;
253
- # would be nil for a create event
254
- ```
255
-
256
- PaperTrail stores the pre-change version of the model, unlike some other
257
- auditing/versioning plugins, so you can retrieve the original version. This is
258
- useful when you start keeping a paper trail for models that already have records
259
- in the database.
260
-
261
- ```ruby
262
- widget = Widget.find 153
263
- widget.name # 'Doobly'
264
-
265
- # Add has_paper_trail to Widget model.
266
-
267
- widget.versions # []
268
- widget.update_attributes :name => 'Wotsit'
269
- widget.versions.last.reify.name # 'Doobly'
270
- widget.versions.last.event # 'update'
271
- ```
272
-
273
- This also means that PaperTrail does not waste space storing a version of the
274
- object as it currently stands. The `versions` method gives you previous
275
- versions; to get the current one just call a finder on your `Widget` model as
276
- usual.
277
-
278
- Here's a helpful table showing what PaperTrail stores:
279
-
280
- <table>
281
- <tr>
282
- <th>Event</th>
283
- <th>Model Before</th>
284
- <th>Model After</th>
285
- </tr>
286
- <tr>
287
- <td>create</td>
288
- <td>nil</td>
289
- <td>widget</td>
290
- </tr>
291
- <tr>
292
- <td>update</td>
293
- <td>widget</td>
294
- <td>widget</td>
295
- <tr>
296
- <td>destroy</td>
297
- <td>widget</td>
298
- <td>nil</td>
299
- </tr>
300
- </table>
301
-
302
- PaperTrail stores the values in the Model Before column. Most other
303
- auditing/versioning plugins store the After column.
304
-
305
-
306
- ## Choosing Lifecycle Events To Monitor
257
+ ### 2.a. Choosing Lifecycle Events To Monitor
307
258
 
308
259
  You can choose which events to track with the `on` option. For example, to
309
260
  ignore `create` events:
@@ -314,6 +265,10 @@ class Article < ActiveRecord::Base
314
265
  end
315
266
  ```
316
267
 
268
+ `has_paper_trail` installs callbacks for these lifecycle events. If there are
269
+ other callbacks in your model, their order relative to those installed by
270
+ PaperTrail may matter, so be aware of any potential interactions.
271
+
317
272
  You may also have the `PaperTrail::Version` model save a custom string in it's
318
273
  `event` field instead of the typical `create`, `update`, `destroy`. PaperTrail
319
274
  supplies a custom accessor method called `paper_trail_event`, which it will
@@ -334,7 +289,31 @@ a.versions.size # 3
334
289
  a.versions.last.event # 'update'
335
290
  ```
336
291
 
337
- ## Choosing When To Save New Versions
292
+ #### Controlling the Order of AR Callbacks
293
+
294
+ The `has_paper_trail` method installs AR callbacks. If you need to control
295
+ their order, use the `paper_trail_on_*` methods.
296
+
297
+ ```ruby
298
+ class Article < ActiveRecord::Base
299
+
300
+ # Include PaperTrail, but do not add any callbacks yet. Passing the
301
+ # empty array to `:on` omits callbacks.
302
+ has_paper_trail :on => []
303
+
304
+ # Add callbacks in the order you need.
305
+ paper_trail_on_destroy # add destroy callback
306
+ paper_trail_on_update # etc.
307
+ paper_trail_on_create
308
+ end
309
+ ```
310
+
311
+ The `paper_trail_on_destroy` method can be further configured to happen
312
+ `:before` or `:after` the destroy event. In PaperTrail 4, the default is
313
+ `:after`. In PaperTrail 5, the default will be `:before`, to support
314
+ ActiveRecord 5. (see https://github.com/airblade/paper_trail/pull/683)
315
+
316
+ ### 2.b. Choosing When To Save New Versions
338
317
 
339
318
  You can choose the conditions when to add new versions with the `if` and
340
319
  `unless` options. For example, to save versions only for US non-draft
@@ -347,8 +326,13 @@ class Translation < ActiveRecord::Base
347
326
  end
348
327
  ```
349
328
 
329
+ #### Choosing Based on Changed Attributes
330
+
331
+ Starting with PaperTrail 4.0, versions are saved during an after-callback. If
332
+ you decide whether to save a new version based on changed attributes, please
333
+ use attribute_name_was instead of attribute_name.
350
334
 
351
- ## Choosing Attributes To Monitor
335
+ ### 2.c. Choosing Attributes To Monitor
352
336
 
353
337
  You can ignore changes to certain attributes like this:
354
338
 
@@ -435,7 +419,78 @@ class Article < ActiveRecord::Base
435
419
  end
436
420
  ```
437
421
 
438
- ## Reverting And Undeleting A Model
422
+ ### 2.d. Turning PaperTrail Off
423
+
424
+ PaperTrail is on by default, but sometimes you don't want to record versions.
425
+
426
+ #### Per Process
427
+
428
+ Turn PaperTrail off for all threads in a `ruby` process.
429
+
430
+ ```ruby
431
+ PaperTrail.enabled = false
432
+ ```
433
+
434
+ This is commonly used to speed up tests. See [Testing](#testing) below.
435
+
436
+ There is also a rails config option that does the same thing.
437
+
438
+ ```ruby
439
+ # in config/environments/test.rb
440
+ config.paper_trail.enabled = false
441
+ ```
442
+
443
+ #### Per Request
444
+
445
+ Add a `paper_trail_enabled_for_controller` method to your controller.
446
+
447
+ ```ruby
448
+ class ApplicationController < ActionController::Base
449
+ def paper_trail_enabled_for_controller
450
+ request.user_agent != 'Disable User-Agent'
451
+ end
452
+ end
453
+ ```
454
+
455
+ #### Per Class
456
+
457
+ ```ruby
458
+ Widget.paper_trail_off!
459
+ Widget.paper_trail_on!
460
+ ```
461
+
462
+ #### Per Method
463
+
464
+ You can call a method without creating a new version using `without_versioning`.
465
+ It takes either a method name as a symbol:
466
+
467
+ ```ruby
468
+ @widget.without_versioning :destroy
469
+ ```
470
+
471
+ Or a block:
472
+
473
+ ```ruby
474
+ @widget.without_versioning do
475
+ @widget.update_attributes :name => 'Ford'
476
+ end
477
+ ```
478
+
479
+ ### 2.e. Limiting the Number of Versions Created
480
+
481
+ Configure `version_limit` to cap the number of versions saved per record. This
482
+ does not apply to `create` events.
483
+
484
+ ```ruby
485
+ # Limit: 4 versions per record (3 most recent, plus a `create` event)
486
+ PaperTrail.config.version_limit = 3
487
+ # Remove the limit
488
+ PaperTrail.config.version_limit = nil
489
+ ```
490
+
491
+ ## 3. Working With Versions
492
+
493
+ ### 3.a. Reverting And Undeleting A Model
439
494
 
440
495
  PaperTrail makes reverting to a previous version easy:
441
496
 
@@ -472,7 +527,7 @@ You could even use PaperTrail to implement an undo system, [Ryan Bates has!][3]
472
527
  If your model uses [optimistic locking][1] don't forget to [increment your
473
528
  `lock_version`][2] before saving or you'll get a `StaleObjectError`.
474
529
 
475
- ## Navigating Versions
530
+ ### 3.b. Navigating Versions
476
531
 
477
532
  You can call `previous_version` and `next_version` on an item to get it as it
478
533
  was/became. Note that these methods reify the item for you.
@@ -528,25 +583,91 @@ And you can perform `WHERE` queries for object versions based on attributes:
528
583
  PaperTrail::Version.where_object(content: "Hello", title: "Article")
529
584
  ```
530
585
 
531
- ## Finding Out Who Was Responsible For A Change
586
+ ### 3.c. Diffing Versions
532
587
 
533
- If your `ApplicationController` has a `current_user` method, PaperTrail will
534
- attempt to store the value returned by `current_user.id` in the version's
535
- `whodunnit` column.
588
+ There are two scenarios: diffing adjacent versions and diffing non-adjacent
589
+ versions.
536
590
 
537
- You may want PaperTrail to call a different method to find out who is
538
- responsible. To do so, override the `user_for_paper_trail` method in your
539
- controller like this:
591
+ The best way to diff adjacent versions is to get PaperTrail to do it for you.
592
+ If you add an `object_changes` text column to your `versions` table, either at
593
+ installation time with the `rails generate paper_trail:install --with-changes`
594
+ option or manually, PaperTrail will store the `changes` diff (excluding any
595
+ attributes PaperTrail is ignoring) in each `update` version. You can use the
596
+ `version.changeset` method to retrieve it. For example:
540
597
 
541
598
  ```ruby
542
- class ApplicationController
543
- def user_for_paper_trail
544
- logged_in? ? current_member.id : 'Public user' # or whatever
545
- end
546
- end
599
+ widget = Widget.create :name => 'Bob'
600
+ widget.versions.last.changeset
601
+ # {
602
+ # "name"=>[nil, "Bob"],
603
+ # "created_at"=>[nil, 2015-08-10 04:10:40 UTC],
604
+ # "updated_at"=>[nil, 2015-08-10 04:10:40 UTC],
605
+ # "id"=>[nil, 1]
606
+ # }
607
+ widget.update_attributes :name => 'Robert'
608
+ widget.versions.last.changeset
609
+ # {
610
+ # "name"=>["Bob", "Robert"],
611
+ # "updated_at"=>[2015-08-10 04:13:19 UTC, 2015-08-10 04:13:19 UTC]
612
+ # }
613
+ widget.destroy
614
+ widget.versions.last.changeset
615
+ # {}
616
+ ```
617
+
618
+ The `object_changes` are only stored for creation and updates, not when an
619
+ object is destroyed.
620
+
621
+ Please be aware that PaperTrail doesn't use diffs internally. When I designed
622
+ PaperTrail I wanted simplicity and robustness so I decided to make each version
623
+ of an object self-contained. A version stores all of its object's data, not a
624
+ diff from the previous version. This means you can delete any version without
625
+ affecting any other.
626
+
627
+ To diff non-adjacent versions you'll have to write your own code. These
628
+ libraries may help:
629
+
630
+ For diffing two strings:
631
+
632
+ * [htmldiff][19]: expects but doesn't require HTML input and produces HTML
633
+ output. Works very well but slows down significantly on large (e.g. 5,000
634
+ word) inputs.
635
+ * [differ][20]: expects plain text input and produces plain
636
+ text/coloured/HTML/any output. Can do character-wise, word-wise, line-wise,
637
+ or arbitrary-boundary-string-wise diffs. Works very well on non-HTML input.
638
+ * [diff-lcs][21]: old-school, line-wise diffs.
639
+
640
+ For diffing two ActiveRecord objects:
641
+
642
+ * [Jeremy Weiskotten's PaperTrail fork][22]: uses ActiveSupport's diff to return
643
+ an array of hashes of the changes.
644
+ * [activerecord-diff][23]: rather like ActiveRecord::Dirty but also allows you
645
+ to specify which columns to compare.
646
+
647
+ If you wish to selectively record changes for some models but not others you
648
+ can opt out of recording changes by passing `:save_changes => false` to your
649
+ `has_paper_trail` method declaration.
650
+
651
+ ### 3.d. Deleting Old Versions
652
+
653
+ Over time your `versions` table will grow to an unwieldy size. Because each
654
+ version is self-contained (see the Diffing section above for more) you can
655
+ simply delete any records you don't want any more. For example:
656
+
657
+ ```sql
658
+ sql> delete from versions where created_at < 2010-06-01;
659
+ ```
660
+
661
+ ```ruby
662
+ PaperTrail::Version.delete_all ["created_at < ?", 1.week.ago]
547
663
  ```
548
664
 
549
- In a console session you can manually set who is responsible like this:
665
+ ## 4. Saving More Information About Versions
666
+
667
+ ### 4.a. Finding Out Who Was Responsible For A Change
668
+
669
+ Set `PaperTrail.whodunnit=`, and that value will be stored in the version's
670
+ `whodunnit` column.
550
671
 
551
672
  ```ruby
552
673
  PaperTrail.whodunnit = 'Andy Stewart'
@@ -554,6 +675,28 @@ widget.update_attributes :name => 'Wibble'
554
675
  widget.versions.last.whodunnit # Andy Stewart
555
676
  ```
556
677
 
678
+ If your controller has a `current_user` method, PaperTrail provides a
679
+ `before_filter` that will assign `current_user.id` to `PaperTrail.whodunnit`.
680
+ You can add this `before_filter` to your `ApplicationController`.
681
+
682
+ ```ruby
683
+ class ApplicationController
684
+ before_filter :set_paper_trail_whodunnit
685
+ end
686
+ ```
687
+
688
+ You may want `set_paper_trail_whodunnit` to call a different method to find out
689
+ who is responsible. To do so, override the `user_for_paper_trail` method in
690
+ your controller like this:
691
+
692
+ ```ruby
693
+ class ApplicationController
694
+ def user_for_paper_trail
695
+ logged_in? ? current_member.id : 'Public user' # or whatever
696
+ end
697
+ end
698
+ ```
699
+
557
700
  See also: [Setting whodunnit in the rails console][33]
558
701
 
559
702
  Sometimes you want to define who is responsible for a change in a small scope
@@ -586,7 +729,7 @@ like it does, call `paper_trail_originator` on the object.
586
729
  widget = Widget.find 153 # assume widget has 0 versions
587
730
  PaperTrail.whodunnit = 'Alice'
588
731
  widget.update_attributes :name => 'Yankee'
589
- widget..paper_trail_originator # 'Alice'
732
+ widget.paper_trail_originator # 'Alice'
590
733
  PaperTrail.whodunnit = 'Bob'
591
734
  widget.update_attributes :name => 'Zulu'
592
735
  widget.paper_trail_originator # 'Bob'
@@ -595,80 +738,18 @@ first_version.whodunnit # 'Alice'
595
738
  first_version.paper_trail_originator # nil
596
739
  first_version.terminator # 'Alice'
597
740
  last_version.whodunnit # 'Bob'
598
- last_version.paper_trail_originator # 'Alice'
599
- last_version.terminator # 'Bob'
600
- ```
601
-
602
- ## Custom Version Classes
603
-
604
- You can specify custom version subclasses with the `:class_name` option:
605
-
606
- ```ruby
607
- class PostVersion < PaperTrail::Version
608
- # custom behaviour, e.g:
609
- self.table_name = :post_versions
610
- end
611
-
612
- class Post < ActiveRecord::Base
613
- has_paper_trail :class_name => 'PostVersion'
614
- end
615
- ```
616
-
617
- 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`).
618
-
619
- ### Advantages
620
-
621
- 1. For models which have a lot of versions, storing each model's versions in a
622
- separate table can improve the performance of certain database queries.
623
- 1. Store different version [metadata](#storing-metadata) for different models.
624
-
625
- ### Configuration
626
-
627
- If you are using Postgres, you should also define the sequence that your custom
628
- version class will use:
629
-
630
- ```ruby
631
- class PostVersion < PaperTrail::Version
632
- self.table_name = :post_versions
633
- self.sequence_name = :post_versions_id_seq
634
- end
635
- ```
636
-
637
- If you only use custom version classes and don't have a `versions` table, you
638
- must let ActiveRecord know that the `PaperTrail::Version` class is an
639
- `abstract_class`.
640
-
641
- ```ruby
642
- # app/models/paper_trail/version.rb
643
- module PaperTrail
644
- class Version < ActiveRecord::Base
645
- include PaperTrail::VersionConcern
646
- self.abstract_class = true
647
- end
648
- end
741
+ last_version.paper_trail_originator # 'Alice'
742
+ last_version.terminator # 'Bob'
649
743
  ```
650
744
 
651
- You can also specify custom names for the versions and version associations.
652
- This is useful if you already have `versions` or/and `version` methods on your
653
- model. For example:
745
+ #### Storing an ActiveRecord globalid in whodunnit
654
746
 
655
- ```ruby
656
- class Post < ActiveRecord::Base
657
- has_paper_trail :versions => :paper_trail_versions,
658
- :version => :paper_trail_version
747
+ If you would like `whodunnit` to return an `ActiveRecord` object instead of a
748
+ string, please try the [paper_trail-globalid][37] gem.
659
749
 
660
- # Existing versions method. We don't want to clash.
661
- def versions
662
- ...
663
- end
664
- # Existing version method. We don't want to clash.
665
- def version
666
- ...
667
- end
668
- end
669
- ```
750
+ ### 4.b. Associations
670
751
 
671
- ## Associations
752
+ **Experimental feature**, see caveats below.
672
753
 
673
754
  PaperTrail can restore three types of associations: Has-One, Has-Many, and
674
755
  Has-Many-Through. In order to do this, you will need to create a
@@ -684,7 +765,8 @@ the association that are created in the same transaction.
684
765
 
685
766
  To restore Has-One associations as they were at the time, pass option `:has_one
686
767
  => true` to `reify`. To restore Has-Many and Has-Many-Through associations, use
687
- option `:has_many => true`. For example:
768
+ option `:has_many => true`. To restore Belongs-To association, use
769
+ option `:belongs_to => true`. For example:
688
770
 
689
771
  ```ruby
690
772
  class Location < ActiveRecord::Base
@@ -760,14 +842,23 @@ widget.reload.wotsit # nil
760
842
 
761
843
  **Caveats:**
762
844
 
845
+ 1. Not compatible with [transactional tests][34], aka. transactional fixtures.
846
+ This is a known issue [#542](https://github.com/airblade/paper_trail/issues/542)
847
+ that we'd like to solve.
848
+ 1. Requires database timestamp columns with fractional second precision.
849
+ - Sqlite and postgres timestamps have fractional second precision by default.
850
+ [MySQL timestamps do not][35]. Furthermore, MySQL 5.5 and earlier do not
851
+ support fractional second precision at all.
852
+ - Also, support for fractional seconds in MySQL was not added to
853
+ rails until ActiveRecord 4.2 (https://github.com/rails/rails/pull/14359).
763
854
  1. PaperTrail can't restore an association properly if the association record
764
855
  can be updated to replace its parent model (by replacing the foreign key)
765
- 2. Currently PaperTrail only support single `version_associations` table. The
856
+ 1. Currently PaperTrail only supports a single `version_associations` table. The
766
857
  implication is that you can only use a single table to store the versions for
767
858
  all related models. Sorry for those who use multiple version tables.
768
- 3. PaperTrail only reifies the first level of associations, i.e., it does not
859
+ 1. PaperTrail only reifies the first level of associations, i.e., it does not
769
860
  reify any associations of its associations, and so on.
770
- 4. PaperTrail relies on the callbacks on the association model (and the :through
861
+ 1. PaperTrail relies on the callbacks on the association model (and the :through
771
862
  association model for Has-Many-Through associations) to record the versions
772
863
  and the relationship between the versions. If the association is changed
773
864
  without invoking the callbacks, Reification won't work. Below are some
@@ -835,7 +926,7 @@ end
835
926
 
836
927
  See [issue 113][16] for a discussion about this.
837
928
 
838
- ## Storing Metadata
929
+ ### 4.c. Storing Metadata
839
930
 
840
931
  You can store arbitrary model-level metadata alongside each version like this:
841
932
 
@@ -853,8 +944,9 @@ end
853
944
 
854
945
  PaperTrail will call your proc with the current article and store the result in
855
946
  the `author_id` column of the `versions` table.
947
+ Don't forget to add any such columns to your `versions` table.
856
948
 
857
- ### Advantages of Metadata
949
+ #### Advantages of Metadata
858
950
 
859
951
  Why would you do this? In this example, `author_id` is an attribute of
860
952
  `Article` and PaperTrail will store it anyway in a serialized form in the
@@ -868,7 +960,7 @@ those versions you want:
868
960
  PaperTrail::Version.where(:author_id => author_id)
869
961
  ```
870
962
 
871
- ### Metadata from Controllers
963
+ #### Metadata from Controllers
872
964
 
873
965
  You can also store any information you like from your controller. Override
874
966
  the `info_for_paper_trail` method in your controller to return a hash whose keys
@@ -882,9 +974,9 @@ class ApplicationController
882
974
  end
883
975
  ```
884
976
 
885
- ### Protected Attributes and Metadata
977
+ #### Protected Attributes and Metadata
886
978
 
887
- If you are using rails 3 or the [protected_attributes][17] gem you must declare
979
+ If you are using rails 3 or the [protected_attributes][17] gem you must declare
888
980
  your metadata columns to be `attr_accessible`.
889
981
 
890
982
  ```ruby
@@ -900,165 +992,138 @@ end
900
992
  If you're using [strong_parameters][18] instead of [protected_attributes][17]
901
993
  then there is no need to use `attr_accessible`.
902
994
 
903
- ## Diffing Versions
995
+ ## 5. ActiveRecord
904
996
 
905
- There are two scenarios: diffing adjacent versions and diffing non-adjacent
906
- versions.
997
+ ### 5.a. Single Table Inheritance (STI)
907
998
 
908
- The best way to diff adjacent versions is to get PaperTrail to do it for you.
909
- If you add an `object_changes` text column to your `versions` table, either at
910
- installation time with the `rails generate paper_trail:install --with-changes`
911
- option or manually, PaperTrail will store the `changes` diff (excluding any
912
- attributes PaperTrail is ignoring) in each `update` version. You can use the
913
- `version.changeset` method to retrieve it. For example:
999
+ PaperTrail supports [Single Table Inheritance][39], and even supports an
1000
+ un-versioned base model, as of 23ffbdc7e1.
914
1001
 
915
1002
  ```ruby
916
- widget = Widget.create :name => 'Bob'
917
- widget.versions.last.changeset # {'name' => [nil, 'Bob']}
918
- widget.update_attributes :name => 'Robert'
919
- widget.versions.last.changeset # {'name' => ['Bob', 'Robert']}
920
- widget.destroy
921
- widget.versions.last.changeset # {}
1003
+ class Fruit < ActiveRecord::Base
1004
+ # un-versioned base model
1005
+ end
1006
+ class Banana < Fruit
1007
+ has_paper_trail
1008
+ end
922
1009
  ```
923
1010
 
924
- Note PaperTrail only stores the changes for creation and updates; it doesn't
925
- store anything when an object is destroyed.
926
-
927
- Please be aware that PaperTrail doesn't use diffs internally. When I designed
928
- PaperTrail I wanted simplicity and robustness so I decided to make each version
929
- of an object self-contained. A version stores all of its object's data, not a
930
- diff from the previous version. This means you can delete any version without
931
- affecting any other.
932
-
933
- To diff non-adjacent versions you'll have to write your own code. These
934
- libraries may help:
1011
+ However, there is a known issue when reifying [associations](#associations),
1012
+ see https://github.com/airblade/paper_trail/issues/594
935
1013
 
936
- For diffing two strings:
1014
+ ### 5.b. Configuring the `versions` Association
937
1015
 
938
- * [htmldiff][19]: expects but doesn't require HTML input and produces HTML
939
- output. Works very well but slows down significantly on large (e.g. 5,000
940
- word) inputs.
941
- * [differ][20]: expects plain text input and produces plain
942
- text/coloured/HTML/any output. Can do character-wise, word-wise, line-wise,
943
- or arbitrary-boundary-string-wise diffs. Works very well on non-HTML input.
944
- * [diff-lcs][21]: old-school, line-wise diffs.
1016
+ You may configure the name of the `versions` association by passing
1017
+ a different name to `has_paper_trail`.
945
1018
 
946
- For diffing two ActiveRecord objects:
1019
+ ```ruby
1020
+ class Post < ActiveRecord::Base
1021
+ has_paper_trail class_name: 'Version', versions: :drafts
1022
+ end
947
1023
 
948
- * [Jeremy Weiskotten's PaperTrail fork][22]: uses ActiveSupport's diff to return
949
- an array of hashes of the changes.
950
- * [activerecord-diff][23]: rather like ActiveRecord::Dirty but also allows you
951
- to specify which columns to compare.
1024
+ Post.new.versions # => NoMethodError
1025
+ ```
952
1026
 
953
- If you wish to selectively record changes for some models but not others you
954
- can opt out of recording changes by passing `:save_changes => false` to your
955
- `has_paper_trail` method declaration.
1027
+ Overriding (instead of configuring) the `versions` method is not supported.
1028
+ Overriding associations is not recommended in general.
956
1029
 
957
- ## Turning PaperTrail Off/On
1030
+ ### 5.c. Generators
958
1031
 
959
- Sometimes you don't want to store changes. Perhaps you are only interested in
960
- changes made by your users and don't need to store changes you make yourself in,
961
- say, a migration -- or when testing your application.
1032
+ PaperTrail has one generator, `paper_trail:install`. It writes, but does not
1033
+ run, a migration file. This migration adds (at least) the `versions` table. The
1034
+ most up-to-date documentation for this generator can be found by running `rails
1035
+ generate paper_trail:install --help`, but a copy is included here for
1036
+ convenience.
962
1037
 
963
- You can turn PaperTrail on or off in three ways: globally, per request, or per
964
- class.
1038
+ ```
1039
+ Usage:
1040
+ rails generate paper_trail:install [options]
965
1041
 
966
- ### Globally
1042
+ Options:
1043
+ [--with-changes], [--no-with-changes] # Store changeset (diff) with each version
1044
+ [--with-associations], [--no-with-associations] # Store transactional IDs to support association restoration
967
1045
 
968
- On a global level you can turn PaperTrail off like this:
1046
+ Runtime options:
1047
+ -f, [--force] # Overwrite files that already exist
1048
+ -p, [--pretend], [--no-pretend] # Run but do not make any changes
1049
+ -q, [--quiet], [--no-quiet] # Suppress status output
1050
+ -s, [--skip], [--no-skip] # Skip files that already exist
969
1051
 
970
- ```ruby
971
- PaperTrail.enabled = false
1052
+ Generates (but does not run) a migration to add a versions table.
972
1053
  ```
973
1054
 
974
- For example, you might want to disable PaperTrail in your Rails application's
975
- test environment to speed up your tests. This will do it (note: this gets done
976
- automatically for `RSpec` and `Cucumber`, please see the [Testing
977
- section](#testing)):
1055
+ ## 6. Extensibility
978
1056
 
979
- ```ruby
980
- # in config/environments/test.rb
981
- config.after_initialize do
982
- PaperTrail.enabled = false
983
- end
984
- ```
1057
+ ### 6.a. Custom Version Classes
985
1058
 
986
- If you disable PaperTrail in your test environment but want to enable it for
987
- specific tests, you can add a helper like this to your test helper:
1059
+ You can specify custom version subclasses with the `:class_name` option:
988
1060
 
989
1061
  ```ruby
990
- # in test/test_helper.rb
991
- def with_versioning
992
- was_enabled = PaperTrail.enabled?
993
- was_enabled_for_controller = PaperTrail.enabled_for_controller?
994
- PaperTrail.enabled = true
995
- PaperTrail.enabled_for_controller = true
996
- begin
997
- yield
998
- ensure
999
- PaperTrail.enabled = was_enabled
1000
- PaperTrail.enabled_for_controller = was_enabled_for_controller
1001
- end
1062
+ class PostVersion < PaperTrail::Version
1063
+ # custom behaviour, e.g:
1064
+ self.table_name = :post_versions
1002
1065
  end
1003
- ```
1004
1066
 
1005
- And then use it in your tests like this:
1006
-
1007
- ```ruby
1008
- test "something that needs versioning" do
1009
- with_versioning do
1010
- # your test
1011
- end
1067
+ class Post < ActiveRecord::Base
1068
+ has_paper_trail :class_name => 'PostVersion'
1012
1069
  end
1013
1070
  ```
1014
1071
 
1015
- ### Per request
1072
+ 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
1073
 
1017
- You can turn PaperTrail on or off per request by adding a
1018
- `paper_trail_enabled_for_controller` method to your controller which returns
1019
- `true` or `false`:
1074
+ #### Advantages
1020
1075
 
1021
- ```ruby
1022
- class ApplicationController < ActionController::Base
1023
- def paper_trail_enabled_for_controller
1024
- request.user_agent != 'Disable User-Agent'
1025
- end
1026
- end
1027
- ```
1076
+ 1. For models which have a lot of versions, storing each model's versions in a
1077
+ separate table can improve the performance of certain database queries.
1078
+ 1. Store different version [metadata](#storing-metadata) for different models.
1028
1079
 
1029
- ### Per class
1080
+ #### Configuration
1030
1081
 
1031
- If you are about to change some widgets and you don't want a paper trail of your
1032
- changes, you can turn PaperTrail off like this:
1082
+ If you are using Postgres, you should also define the sequence that your custom
1083
+ version class will use:
1033
1084
 
1034
1085
  ```ruby
1035
- Widget.paper_trail_off!
1086
+ class PostVersion < PaperTrail::Version
1087
+ self.table_name = :post_versions
1088
+ self.sequence_name = :post_versions_id_seq
1089
+ end
1036
1090
  ```
1037
1091
 
1038
- And on again like this:
1092
+ If you only use custom version classes and don't have a `versions` table, you
1093
+ must let ActiveRecord know that the `PaperTrail::Version` class is an
1094
+ `abstract_class`.
1039
1095
 
1040
1096
  ```ruby
1041
- Widget.paper_trail_on!
1097
+ # app/models/paper_trail/version.rb
1098
+ module PaperTrail
1099
+ class Version < ActiveRecord::Base
1100
+ include PaperTrail::VersionConcern
1101
+ self.abstract_class = true
1102
+ end
1103
+ end
1042
1104
  ```
1043
1105
 
1044
- ### Per method call
1045
-
1046
- You can call a method without creating a new version using `without_versioning`.
1047
- It takes either a method name as a symbol:
1106
+ You can also specify custom names for the versions and version associations.
1107
+ This is useful if you already have `versions` or/and `version` methods on your
1108
+ model. For example:
1048
1109
 
1049
1110
  ```ruby
1050
- @widget.without_versioning :destroy
1051
- ```
1052
-
1053
- Or a block:
1111
+ class Post < ActiveRecord::Base
1112
+ has_paper_trail :versions => :paper_trail_versions,
1113
+ :version => :paper_trail_version
1054
1114
 
1055
- ```ruby
1056
- @widget.without_versioning do
1057
- @widget.update_attributes :name => 'Ford'
1115
+ # Existing versions method. We don't want to clash.
1116
+ def versions
1117
+ ...
1118
+ end
1119
+ # Existing version method. We don't want to clash.
1120
+ def version
1121
+ ...
1122
+ end
1058
1123
  end
1059
1124
  ```
1060
1125
 
1061
- ## Using a custom serializer
1126
+ ### 6.b. Custom Serializer
1062
1127
 
1063
1128
  By default, PaperTrail stores your changes as a `YAML` dump. You can override
1064
1129
  this with the serializer config option:
@@ -1073,10 +1138,10 @@ method. These serializers are included in the gem for your convenience:
1073
1138
  * [PaperTrail::Serializers::YAML][24] - Default
1074
1139
  * [PaperTrail::Serializers::JSON][25]
1075
1140
 
1076
- ### PostgreSQL JSON column type support
1141
+ #### PostgreSQL JSON column type support
1077
1142
 
1078
1143
  If you use PostgreSQL, and would like to store your `object` (and/or
1079
- `object_changes`) data in a column of [type `JSON` or type `JSONB`][26], specify
1144
+ `object_changes`) data in a column of [type `json` or type `jsonb`][26], specify
1080
1145
  `json` instead of `text` for these columns in your migration:
1081
1146
 
1082
1147
  ```ruby
@@ -1088,71 +1153,143 @@ create_table :versions do |t|
1088
1153
  end
1089
1154
  ```
1090
1155
 
1091
- Note: You don't need to use a particular serializer for the PostgreSQL `JSON`
1092
- column type.
1156
+ If you use the PostgreSQL `json` or `jsonb` column type, you do not need
1157
+ to specify a `PaperTrail.serializer`.
1093
1158
 
1094
- ## SerializedAttributes support
1159
+ ##### Convert existing YAML data to JSON
1095
1160
 
1096
- PaperTrail has a config option that can be used to enable/disable whether
1097
- PaperTrail attempts to utilize `ActiveRecord`'s `serialized_attributes` feature.
1098
- Note: This is enabled by default when PaperTrail is used with `ActiveRecord`
1099
- version < `4.2`, and disabled by default when used with ActiveRecord `4.2.x`.
1100
- Since `serialized_attributes` will be removed in `ActiveRecord` version `5.0`,
1101
- this configuration value has no functionality when PaperTrail is used with
1102
- version `5.0` or greater.
1161
+ If you've been using PaperTrail for a while with the default YAML serializer
1162
+ and you want to switch to JSON or JSONB, you're in a bit of a bind because
1163
+ there's no automatic way to migrate your data. The first (slow) option is to
1164
+ loop over every record and parse it in Ruby, then write to a temporary column:
1103
1165
 
1104
1166
  ```ruby
1105
- # Enable support
1106
- PaperTrail.config.serialized_attributes = true
1107
- # Disable support
1108
- PaperTrail.config.serialized_attributes = false
1109
- # Get current setting
1110
- PaperTrail.serialized_attributes?
1167
+ add_column :versions, :object, :new_object, :jsonb # or :json
1168
+
1169
+ PaperTrail::Version.reset_column_information
1170
+ PaperTrail::Version.find_each do |version|
1171
+ version.update_column :new_object, YAML.load(version.object)
1172
+ end
1173
+
1174
+ remove_column :versions, :object
1175
+ rename_column :versions, :new_object, :object
1111
1176
  ```
1112
1177
 
1113
- ## Limiting the Number of Versions Created
1178
+ This technique can be very slow if you have a lot of data. Though slow, it is
1179
+ safe in databases where transactions are protected against DDL, such as
1180
+ Postgres. In databases without such protection, such as MySQL, a table lock may
1181
+ be necessary.
1114
1182
 
1115
- Configure `version_limit` to cap the number of versions saved per record. This
1116
- does not apply to `create` events.
1183
+ If the above technique is too slow for your needs, and you're okay doing without
1184
+ PaperTrail data temporarily, you can create the new column without converting
1185
+ the data.
1117
1186
 
1118
1187
  ```ruby
1119
- # Limit: 4 versions per record (3 most recent, plus a `create` event)
1120
- PaperTrail.config.version_limit = 3
1121
- # Remove the limit
1122
- PaperTrail.config.version_limit = nil
1188
+ rename_column :versions, :object, :old_object
1189
+ add_column :versions, :object, :jsonb # or :json
1123
1190
  ```
1124
1191
 
1125
- ## Deleting Old Versions
1192
+ After that migration, your historical data still exists as YAML, and new data
1193
+ will be stored as JSON. Next, convert records from YAML to JSON using a
1194
+ background script.
1126
1195
 
1127
- Over time your `versions` table will grow to an unwieldy size. Because each
1128
- version is self-contained (see the Diffing section above for more) you can
1129
- simply delete any records you don't want any more. For example:
1196
+ ```ruby
1197
+ PaperTrail::Version.where.not(old_object: nil).find_each do |version|
1198
+ version.update_columns old_object: nil, object: YAML.load(version.old_object)
1199
+ end
1200
+ ```
1201
+
1202
+ Finally, in another migration, remove the old column.
1203
+
1204
+ ```ruby
1205
+ remove_column :versions, :old_object
1206
+ ```
1207
+
1208
+ If you use the optional `object_changes` column, don't forget to convert it
1209
+ also, using the same technique.
1210
+
1211
+ ##### Convert a Column from Text to JSON
1212
+
1213
+ If your `object` column already contains JSON data, and you want to change its
1214
+ data type to `json` or `jsonb`, you can use the following [DDL][36]. Of course,
1215
+ if your `object` column contains YAML, you must first convert the data to JSON
1216
+ (see above) before you can change the column type.
1217
+
1218
+ Using SQL:
1130
1219
 
1131
1220
  ```sql
1132
- sql> delete from versions where created_at < 2010-06-01;
1221
+ alter table versions
1222
+ alter column object type jsonb
1223
+ using object::jsonb;
1133
1224
  ```
1134
1225
 
1226
+ Using ActiveRecord:
1227
+
1135
1228
  ```ruby
1136
- PaperTrail::Version.delete_all ["created_at < ?", 1.week.ago]
1229
+ class ConvertVersionsObjectToJson < ActiveRecord::Migration
1230
+ def up
1231
+ change_column :versions, :object, 'jsonb USING object::jsonb'
1232
+ end
1233
+
1234
+ def down
1235
+ change_column :versions, :object, 'text USING object::text'
1236
+ end
1237
+ end
1137
1238
  ```
1138
1239
 
1139
- ## Testing
1240
+ ## 7. Testing
1140
1241
 
1141
- You may want to turn PaperTrail off to speed up your tests. See the [Turning
1142
- PaperTrail Off/On](#turning-papertrail-offon) section above for tips on usage
1143
- with `Test::Unit`.
1242
+ You may want to turn PaperTrail off to speed up your tests. See [Turning
1243
+ PaperTrail Off](#turning-papertrail-off) above.
1144
1244
 
1145
- ### RSpec
1245
+ ### 7.a. Minitest
1146
1246
 
1147
- PaperTrail provides a helper that works with [RSpec][27] to make it easier to
1148
- control when `PaperTrail` is enabled during testing.
1247
+ First, disable PT for the entire `ruby` process.
1149
1248
 
1150
- If you wish to use the helper, you will need to require it in your RSpec test
1151
- helper like so:
1249
+ ```ruby
1250
+ # in config/environments/test.rb
1251
+ config.after_initialize do
1252
+ PaperTrail.enabled = false
1253
+ end
1254
+ ```
1255
+
1256
+ Then, to enable PT for specific tests, you can add a `with_versioning` test
1257
+ helper method.
1152
1258
 
1153
1259
  ```ruby
1154
- # spec/rails_helper.rb
1260
+ # in test/test_helper.rb
1261
+ def with_versioning
1262
+ was_enabled = PaperTrail.enabled?
1263
+ was_enabled_for_controller = PaperTrail.enabled_for_controller?
1264
+ PaperTrail.enabled = true
1265
+ PaperTrail.enabled_for_controller = true
1266
+ begin
1267
+ yield
1268
+ ensure
1269
+ PaperTrail.enabled = was_enabled
1270
+ PaperTrail.enabled_for_controller = was_enabled_for_controller
1271
+ end
1272
+ end
1273
+ ```
1274
+
1275
+ Then, use the helper in your tests.
1276
+
1277
+ ```ruby
1278
+ test "something that needs versioning" do
1279
+ with_versioning do
1280
+ # your test
1281
+ end
1282
+ end
1283
+ ```
1284
+
1285
+ ### 7.b. RSpec
1155
1286
 
1287
+ PaperTrail provides a helper, `paper_trail/frameworks/rspec.rb`, that works with
1288
+ [RSpec][27] to make it easier to control when `PaperTrail` is enabled during
1289
+ testing.
1290
+
1291
+ ```ruby
1292
+ # spec/rails_helper.rb
1156
1293
  ENV["RAILS_ENV"] ||= 'test'
1157
1294
  require 'spec_helper'
1158
1295
  require File.expand_path("../../config/environment", __FILE__)
@@ -1161,10 +1298,10 @@ require 'rspec/rails'
1161
1298
  require 'paper_trail/frameworks/rspec'
1162
1299
  ```
1163
1300
 
1164
- When the helper is loaded, PaperTrail will be turned off for all tests by
1165
- default. When you wish to enable PaperTrail for a test you can either wrap the
1301
+ With the helper loaded, PaperTrail will be turned off for all tests by
1302
+ default. To enable PaperTrail for a test you can either wrap the
1166
1303
  test in a `with_versioning` block, or pass in `:versioning => true` option to a
1167
- spec block, like so:
1304
+ spec block.
1168
1305
 
1169
1306
  ```ruby
1170
1307
  describe "RSpec test group" do
@@ -1235,7 +1372,7 @@ matcher
1235
1372
 
1236
1373
  ```
1237
1374
 
1238
- ### Cucumber
1375
+ ### 7.c. Cucumber
1239
1376
 
1240
1377
  PaperTrail provides a helper for [Cucumber][28] that works similar to the RSpec
1241
1378
  helper.If you wish to use the helper, you will need to require in your cucumber
@@ -1268,7 +1405,7 @@ test to help prevent data spillover between tests. If you are using PaperTrail
1268
1405
  with Rails, the helper will automatically set the `PaperTrail.controller_info`
1269
1406
  value to `{}` as well, again, to help prevent data spillover between tests.
1270
1407
 
1271
- ### Spork
1408
+ ### 7.d. Spork
1272
1409
 
1273
1410
  If you wish to use the `RSpec` or `Cucumber` helpers with [Spork][29], you will
1274
1411
  need to manually require the helper(s) in your `prefork` block on your test
@@ -1291,7 +1428,7 @@ Spork.prefork do
1291
1428
  end
1292
1429
  ```
1293
1430
 
1294
- ### Zeus or Spring
1431
+ ### 7.e. Zeus or Spring
1295
1432
 
1296
1433
  If you wish to use the `RSpec` or `Cucumber` helpers with [Zeus][30] or
1297
1434
  [Spring][31], you will need to manually require the helper(s) in your test
@@ -1307,20 +1444,50 @@ require 'rspec/rails'
1307
1444
  require 'paper_trail/frameworks/rspec'
1308
1445
  ```
1309
1446
 
1310
- ## Testing PaperTrail
1447
+ ## 8. Sinatra
1448
+
1449
+ To configure PaperTrail for usage with [Sinatra][12], your `Sinatra`
1450
+ app must be using `ActiveRecord` 3 or 4. It is also recommended to use the
1451
+ [Sinatra ActiveRecord Extension][13] or something similar for managing your
1452
+ applications `ActiveRecord` connection in a manner similar to the way `Rails`
1453
+ does. If using the aforementioned `Sinatra ActiveRecord Extension`, steps for
1454
+ setting up your app with PaperTrail will look something like this:
1311
1455
 
1312
- Paper Trail has facilities to test against Postgres, Mysql and SQLite. To switch
1313
- between DB engines you will need to export the DB variable for the engine you
1314
- wish to test aganist.
1456
+ 1. Add PaperTrail to your `Gemfile`.
1315
1457
 
1316
- Though be aware we do not have the abilty to create the db's (except sqlite) for
1317
- you. You can look at .travis.yml before_script for an example of how to create
1318
- the db's needed.
1458
+ `gem 'paper_trail'`
1319
1459
 
1320
- ```
1321
- export DB=postgres
1322
- export DB=mysql
1323
- export DB=sqlite # this is default
1460
+ 2. Generate a migration to add a `versions` table to your database.
1461
+
1462
+ `bundle exec rake db:create_migration NAME=create_versions`
1463
+
1464
+ 3. Copy contents of [create_versions.rb][14]
1465
+ into the `create_versions` migration that was generated into your `db/migrate` directory.
1466
+
1467
+ 4. Run the migration.
1468
+
1469
+ `bundle exec rake db:migrate`
1470
+
1471
+ 5. Add `has_paper_trail` to the models you want to track.
1472
+
1473
+
1474
+ PaperTrail provides a helper extension that acts similar to the controller mixin
1475
+ it provides for `Rails` applications.
1476
+
1477
+ It will set `PaperTrail.whodunnit` to whatever is returned by a method named
1478
+ `user_for_paper_trail` which you can define inside your Sinatra Application. (by
1479
+ default it attempts to invoke a method named `current_user`)
1480
+
1481
+ If you're using the modular [`Sinatra::Base`][15] style of application, you will
1482
+ need to register the extension:
1483
+
1484
+ ```ruby
1485
+ # bleh_app.rb
1486
+ require 'sinatra/base'
1487
+
1488
+ class BlehApp < Sinatra::Base
1489
+ register PaperTrail::Sinatra
1490
+ end
1324
1491
  ```
1325
1492
 
1326
1493
  ## Articles
@@ -1331,7 +1498,6 @@ export DB=sqlite # this is default
1331
1498
  [Ilya Bodrov](http://www.sitepoint.com/author/ibodrov), 10th April 2014
1332
1499
  * [Using PaperTrail to track stack traces](http://rubyrailsexpert.com/?p=36),
1333
1500
  T James Corcoran's blog, 1st October 2013.
1334
- * [RailsCast #255 - Undo with Paper Trail][3], Feb 28, 2011
1335
1501
  * [RailsCast #255 - Undo with PaperTrail](http://railscasts.com/episodes/255-undo-with-paper-trail),
1336
1502
  28th February 2011.
1337
1503
  * [Keep a Paper Trail with PaperTrail](http://www.linux-mag.com/id/7528),
@@ -1341,7 +1507,6 @@ export DB=sqlite # this is default
1341
1507
 
1342
1508
  Please use GitHub's [issue tracker](http://github.com/airblade/paper_trail/issues).
1343
1509
 
1344
-
1345
1510
  ## Contributors
1346
1511
 
1347
1512
  Many thanks to:
@@ -1405,7 +1570,6 @@ Many thanks to:
1405
1570
  * [Simply Versioned](http://github.com/github/simply_versioned)
1406
1571
  * [Acts As Audited](http://github.com/collectiveidea/acts_as_audited)
1407
1572
 
1408
-
1409
1573
  ## Intellectual Property
1410
1574
 
1411
1575
  Copyright (c) 2011 Andy Stewart (boss@airbladesoftware.com).
@@ -1414,7 +1578,7 @@ Released under the MIT licence.
1414
1578
  [1]: http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html
1415
1579
  [2]: https://github.com/airblade/paper_trail/issues/163
1416
1580
  [3]: http://railscasts.com/episodes/255-undo-with-paper-trail
1417
- [4]: https://img.shields.io/travis/airblade/paper_trail/master.svg
1581
+ [4]: https://api.travis-ci.org/airblade/paper_trail.svg?branch=master
1418
1582
  [5]: https://travis-ci.org/airblade/paper_trail
1419
1583
  [6]: https://img.shields.io/gemnasium/airblade/paper_trail.svg
1420
1584
  [7]: https://gemnasium.com/airblade/paper_trail
@@ -1443,3 +1607,10 @@ Released under the MIT licence.
1443
1607
  [31]: https://github.com/rails/spring
1444
1608
  [32]: http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html#method-i-mark_for_destruction
1445
1609
  [33]: https://github.com/airblade/paper_trail/wiki/Setting-whodunnit-in-the-rails-console
1610
+ [34]: https://github.com/rails/rails/blob/591a0bb87fff7583e01156696fbbf929d48d3e54/activerecord/lib/active_record/fixtures.rb#L142
1611
+ [35]: https://dev.mysql.com/doc/refman/5.6/en/fractional-seconds.html
1612
+ [36]: http://www.postgresql.org/docs/9.4/interactive/ddl.html
1613
+ [37]: https://github.com/ankit1910/paper_trail-globalid
1614
+ [38]: https://github.com/sferik/rails_admin
1615
+ [39]: http://api.rubyonrails.org/classes/ActiveRecord/Base.html#class-ActiveRecord::Base-label-Single+table+inheritance
1616
+ [40]: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#module-ActiveRecord::Associations::ClassMethods-label-Polymorphic+Associations