paper_trail 1.4.0 → 17.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/lib/generators/paper_trail/install/USAGE +31 -0
  3. data/lib/generators/paper_trail/install/install_generator.rb +101 -0
  4. data/lib/generators/paper_trail/install/templates/add_object_changes_to_versions.rb.erb +12 -0
  5. data/lib/generators/paper_trail/install/templates/create_versions.rb.erb +41 -0
  6. data/lib/generators/paper_trail/migration_generator.rb +65 -0
  7. data/lib/generators/paper_trail/update_item_subtype/USAGE +4 -0
  8. data/lib/generators/paper_trail/update_item_subtype/templates/update_versions_for_item_subtype.rb.erb +86 -0
  9. data/lib/generators/paper_trail/update_item_subtype/update_item_subtype_generator.rb +40 -0
  10. data/lib/paper_trail/attribute_serializers/README.md +10 -0
  11. data/lib/paper_trail/attribute_serializers/attribute_serializer_factory.rb +41 -0
  12. data/lib/paper_trail/attribute_serializers/cast_attribute_serializer.rb +51 -0
  13. data/lib/paper_trail/attribute_serializers/object_attribute.rb +48 -0
  14. data/lib/paper_trail/attribute_serializers/object_changes_attribute.rb +51 -0
  15. data/lib/paper_trail/cleaner.rb +60 -0
  16. data/lib/paper_trail/compatibility.rb +51 -0
  17. data/lib/paper_trail/config.rb +41 -0
  18. data/lib/paper_trail/errors.rb +33 -0
  19. data/lib/paper_trail/events/base.rb +343 -0
  20. data/lib/paper_trail/events/create.rb +32 -0
  21. data/lib/paper_trail/events/destroy.rb +42 -0
  22. data/lib/paper_trail/events/update.rb +76 -0
  23. data/lib/paper_trail/frameworks/active_record/models/paper_trail/version.rb +16 -0
  24. data/lib/paper_trail/frameworks/active_record.rb +12 -0
  25. data/lib/paper_trail/frameworks/cucumber.rb +33 -0
  26. data/lib/paper_trail/frameworks/rails/controller.rb +103 -0
  27. data/lib/paper_trail/frameworks/rails/railtie.rb +34 -0
  28. data/lib/paper_trail/frameworks/rails.rb +3 -0
  29. data/lib/paper_trail/frameworks/rspec/helpers.rb +29 -0
  30. data/lib/paper_trail/frameworks/rspec.rb +42 -0
  31. data/lib/paper_trail/has_paper_trail.rb +79 -82
  32. data/lib/paper_trail/model_config.rb +257 -0
  33. data/lib/paper_trail/queries/versions/where_attribute_changes.rb +50 -0
  34. data/lib/paper_trail/queries/versions/where_object.rb +65 -0
  35. data/lib/paper_trail/queries/versions/where_object_changes.rb +70 -0
  36. data/lib/paper_trail/queries/versions/where_object_changes_from.rb +57 -0
  37. data/lib/paper_trail/queries/versions/where_object_changes_to.rb +57 -0
  38. data/lib/paper_trail/record_history.rb +51 -0
  39. data/lib/paper_trail/record_trail.rb +342 -0
  40. data/lib/paper_trail/reifier.rb +147 -0
  41. data/lib/paper_trail/request.rb +163 -0
  42. data/lib/paper_trail/serializers/json.rb +36 -0
  43. data/lib/paper_trail/serializers/yaml.rb +68 -0
  44. data/lib/paper_trail/type_serializers/postgres_array_serializer.rb +35 -0
  45. data/lib/paper_trail/version_concern.rb +406 -0
  46. data/lib/paper_trail/version_number.rb +23 -0
  47. data/lib/paper_trail.rb +128 -19
  48. metadata +444 -70
  49. data/.gitignore +0 -3
  50. data/README.md +0 -225
  51. data/Rakefile +0 -50
  52. data/VERSION +0 -1
  53. data/generators/paper_trail/USAGE +0 -2
  54. data/generators/paper_trail/paper_trail_generator.rb +0 -9
  55. data/generators/paper_trail/templates/create_versions.rb +0 -18
  56. data/init.rb +0 -1
  57. data/install.rb +0 -1
  58. data/lib/paper_trail/version.rb +0 -59
  59. data/paper_trail.gemspec +0 -67
  60. data/rails/init.rb +0 -1
  61. data/tasks/paper_trail_tasks.rake +0 -0
  62. data/test/database.yml +0 -18
  63. data/test/paper_trail_controller_test.rb +0 -70
  64. data/test/paper_trail_model_test.rb +0 -448
  65. data/test/paper_trail_schema_test.rb +0 -15
  66. data/test/schema.rb +0 -48
  67. data/test/schema_change.rb +0 -3
  68. data/test/test_helper.rb +0 -43
  69. data/uninstall.rb +0 -1
  70. /data/{MIT-LICENSE → LICENSE} +0 -0
@@ -0,0 +1,406 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "paper_trail/attribute_serializers/object_changes_attribute"
4
+ require "paper_trail/queries/versions/where_attribute_changes"
5
+ require "paper_trail/queries/versions/where_object"
6
+ require "paper_trail/queries/versions/where_object_changes"
7
+ require "paper_trail/queries/versions/where_object_changes_from"
8
+ require "paper_trail/queries/versions/where_object_changes_to"
9
+
10
+ module PaperTrail
11
+ # Originally, PaperTrail did not provide this module, and all of this
12
+ # functionality was in `PaperTrail::Version`. That model still exists (and is
13
+ # used by most apps) but by moving the functionality to this module, people
14
+ # can include this concern instead of sub-classing the `Version` model.
15
+ module VersionConcern
16
+ extend ::ActiveSupport::Concern
17
+
18
+ E_YAML_PERMITTED_CLASSES = <<-EOS.squish.freeze
19
+ PaperTrail encountered a Psych::DisallowedClass error during
20
+ deserialization of YAML column, indicating that
21
+ yaml_column_permitted_classes has not been configured correctly. %s
22
+ EOS
23
+
24
+ included do
25
+ belongs_to :item, polymorphic: true, optional: true, inverse_of: false
26
+ validates_presence_of :event
27
+ after_create :enforce_version_limit!
28
+ end
29
+
30
+ # :nodoc:
31
+ module ClassMethods
32
+ def with_item_keys(item_type, item_id)
33
+ where item_type: item_type, item_id: item_id
34
+ end
35
+
36
+ def creates
37
+ where event: "create"
38
+ end
39
+
40
+ def updates
41
+ where event: "update"
42
+ end
43
+
44
+ def destroys
45
+ where event: "destroy"
46
+ end
47
+
48
+ def not_creates
49
+ where.not(event: "create")
50
+ end
51
+
52
+ def between(start_time, end_time)
53
+ where(
54
+ arel_table[:created_at].gt(start_time).
55
+ and(arel_table[:created_at].lt(end_time))
56
+ ).order(timestamp_sort_order)
57
+ end
58
+
59
+ # Defaults to using the primary key as the secondary sort order if
60
+ # possible.
61
+ def timestamp_sort_order(direction = "asc")
62
+ [arel_table[:created_at].send(direction.downcase)].tap do |array|
63
+ array << arel_table[primary_key].send(direction.downcase) if primary_key_is_int?
64
+ end
65
+ end
66
+
67
+ # Given an attribute like `"name"`, query the `versions.object_changes`
68
+ # column for any changes that modified the provided attribute.
69
+ #
70
+ # @api public
71
+ def where_attribute_changes(attribute)
72
+ unless attribute.is_a?(String) || attribute.is_a?(Symbol)
73
+ raise ArgumentError, "expected to receive a String or Symbol"
74
+ end
75
+
76
+ Queries::Versions::WhereAttributeChanges.new(self, attribute).execute
77
+ end
78
+
79
+ # Given a hash of attributes like `name: 'Joan'`, query the
80
+ # `versions.objects` column.
81
+ #
82
+ # ```
83
+ # SELECT "versions".*
84
+ # FROM "versions"
85
+ # WHERE ("versions"."object" LIKE '%
86
+ # name: Joan
87
+ # %')
88
+ # ```
89
+ #
90
+ # This is useful for finding versions where a given attribute had a given
91
+ # value. Imagine, in the example above, that Joan had changed her name
92
+ # and we wanted to find the versions before that change.
93
+ #
94
+ # Based on the data type of the `object` column, the appropriate SQL
95
+ # operator is used. For example, a text column will use `like`, and a
96
+ # jsonb column will use `@>`.
97
+ #
98
+ # @api public
99
+ def where_object(args = {})
100
+ raise ArgumentError, "expected to receive a Hash" unless args.is_a?(Hash)
101
+ Queries::Versions::WhereObject.new(self, args).execute
102
+ end
103
+
104
+ # Given a hash of attributes like `name: 'Joan'`, query the
105
+ # `versions.objects_changes` column.
106
+ #
107
+ # ```
108
+ # SELECT "versions".*
109
+ # FROM "versions"
110
+ # WHERE .. ("versions"."object_changes" LIKE '%
111
+ # name:
112
+ # - Joan
113
+ # %' OR "versions"."object_changes" LIKE '%
114
+ # name:
115
+ # -%
116
+ # - Joan
117
+ # %')
118
+ # ```
119
+ #
120
+ # This is useful for finding versions immediately before and after a given
121
+ # attribute had a given value. Imagine, in the example above, that someone
122
+ # changed their name to Joan and we wanted to find the versions
123
+ # immediately before and after that change.
124
+ #
125
+ # Based on the data type of the `object` column, the appropriate SQL
126
+ # operator is used. For example, a text column will use `like`, and a
127
+ # jsonb column will use `@>`.
128
+ #
129
+ # @api public
130
+ def where_object_changes(args = {})
131
+ raise ArgumentError, "expected to receive a Hash" unless args.is_a?(Hash)
132
+ Queries::Versions::WhereObjectChanges.new(self, args).execute
133
+ end
134
+
135
+ # Given a hash of attributes like `name: 'Joan'`, query the
136
+ # `versions.objects_changes` column for changes where the version changed
137
+ # from the hash of attributes to other values.
138
+ #
139
+ # This is useful for finding versions where the attribute started with a
140
+ # known value and changed to something else. This is in comparison to
141
+ # `where_object_changes` which will find both the changes before and
142
+ # after.
143
+ #
144
+ # @api public
145
+ def where_object_changes_from(args = {})
146
+ raise ArgumentError, "expected to receive a Hash" unless args.is_a?(Hash)
147
+ Queries::Versions::WhereObjectChangesFrom.new(self, args).execute
148
+ end
149
+
150
+ # Given a hash of attributes like `name: 'Joan'`, query the
151
+ # `versions.objects_changes` column for changes where the version changed
152
+ # to the hash of attributes from other values.
153
+ #
154
+ # This is useful for finding versions where the attribute started with an
155
+ # unknown value and changed to a known value. This is in comparison to
156
+ # `where_object_changes` which will find both the changes before and
157
+ # after.
158
+ #
159
+ # @api public
160
+ def where_object_changes_to(args = {})
161
+ raise ArgumentError, "expected to receive a Hash" unless args.is_a?(Hash)
162
+ Queries::Versions::WhereObjectChangesTo.new(self, args).execute
163
+ end
164
+
165
+ def primary_key_is_int?
166
+ @primary_key_is_int ||= columns_hash[primary_key].type == :integer
167
+ rescue StandardError # TODO: Rescue something more specific
168
+ true
169
+ end
170
+
171
+ # Returns whether the `object` column is using the `json` type supported
172
+ # by PostgreSQL.
173
+ def object_col_is_json?
174
+ %i[json jsonb].include?(columns_hash["object"].type)
175
+ end
176
+
177
+ # Returns whether the `object_changes` column is using the `json` type
178
+ # supported by PostgreSQL.
179
+ def object_changes_col_is_json?
180
+ %i[json jsonb].include?(columns_hash["object_changes"].try(:type))
181
+ end
182
+
183
+ # Returns versions before `obj`.
184
+ #
185
+ # @param obj - a `Version` or a timestamp
186
+ # @param timestamp_arg - boolean - When true, `obj` is a timestamp.
187
+ # Default: false.
188
+ # @return `ActiveRecord::Relation`
189
+ # @api public
190
+ # rubocop:disable Style/OptionalBooleanParameter
191
+ def preceding(obj, timestamp_arg = false)
192
+ if timestamp_arg != true && primary_key_is_int?
193
+ preceding_by_id(obj)
194
+ else
195
+ preceding_by_timestamp(obj)
196
+ end
197
+ end
198
+ # rubocop:enable Style/OptionalBooleanParameter
199
+
200
+ # Returns versions after `obj`.
201
+ #
202
+ # @param obj - a `Version` or a timestamp
203
+ # @param timestamp_arg - boolean - When true, `obj` is a timestamp.
204
+ # Default: false.
205
+ # @return `ActiveRecord::Relation`
206
+ # @api public
207
+ # rubocop:disable Style/OptionalBooleanParameter
208
+ def subsequent(obj, timestamp_arg = false)
209
+ if timestamp_arg != true && primary_key_is_int?
210
+ subsequent_by_id(obj)
211
+ else
212
+ subsequent_by_timestamp(obj)
213
+ end
214
+ end
215
+ # rubocop:enable Style/OptionalBooleanParameter
216
+
217
+ private
218
+
219
+ # @api private
220
+ def preceding_by_id(obj)
221
+ where(arel_table[primary_key].lt(obj.id)).order(arel_table[primary_key].desc)
222
+ end
223
+
224
+ # @api private
225
+ def preceding_by_timestamp(obj)
226
+ obj = obj.send(:created_at) if obj.is_a?(self)
227
+ where(arel_table[:created_at].lt(obj)).
228
+ order(timestamp_sort_order("desc"))
229
+ end
230
+
231
+ # @api private
232
+ def subsequent_by_id(version)
233
+ where(arel_table[primary_key].gt(version.id)).order(arel_table[primary_key].asc)
234
+ end
235
+
236
+ # @api private
237
+ def subsequent_by_timestamp(obj)
238
+ obj = obj.send(:created_at) if obj.is_a?(self)
239
+ where(arel_table[:created_at].gt(obj)).order(timestamp_sort_order)
240
+ end
241
+ end
242
+
243
+ # @api private
244
+ def object_deserialized
245
+ if self.class.object_col_is_json?
246
+ object
247
+ else
248
+ PaperTrail.serializer.load(object)
249
+ end
250
+ end
251
+
252
+ # Restore the item from this version.
253
+ #
254
+ # Options:
255
+ #
256
+ # - :mark_for_destruction
257
+ # - `true` - Mark the has_one/has_many associations that did not exist in
258
+ # the reified version for destruction, instead of removing them.
259
+ # - `false` - Default. Useful for persisting the reified version.
260
+ # - :dup
261
+ # - `false` - Default.
262
+ # - `true` - Always create a new object instance. Useful for
263
+ # comparing two versions of the same object.
264
+ # - :unversioned_attributes
265
+ # - `:nil` - Default. Attributes undefined in version record are set to
266
+ # nil in reified record.
267
+ # - `:preserve` - Attributes undefined in version record are not modified.
268
+ #
269
+ def reify(options = {})
270
+ unless self.class.column_names.include? "object"
271
+ raise Error, "reify requires an object column"
272
+ end
273
+ return nil if object.nil?
274
+ ::PaperTrail::Reifier.reify(self, options)
275
+ end
276
+
277
+ # Returns what changed in this version of the item.
278
+ # `ActiveModel::Dirty#changes`. returns `nil` if your `versions` table does
279
+ # not have an `object_changes` text column.
280
+ def changeset
281
+ return nil unless self.class.column_names.include? "object_changes"
282
+ @changeset ||= load_changeset
283
+ end
284
+
285
+ # Returns who put the item into the state stored in this version.
286
+ def paper_trail_originator
287
+ @paper_trail_originator ||= previous.try(:whodunnit)
288
+ end
289
+
290
+ # Returns who changed the item from the state it had in this version. This
291
+ # is an alias for `whodunnit`.
292
+ def terminator
293
+ @terminator ||= whodunnit
294
+ end
295
+ alias version_author terminator
296
+
297
+ def next
298
+ @next ||= sibling_versions.subsequent(self).first
299
+ end
300
+
301
+ def previous
302
+ @previous ||= sibling_versions.preceding(self).first
303
+ end
304
+
305
+ # Returns an integer representing the chronological position of the
306
+ # version among its siblings. The "create" event, for example, has an index
307
+ # of 0.
308
+ #
309
+ # @api public
310
+ def index
311
+ @index ||= RecordHistory.new(sibling_versions, self.class).index(self)
312
+ end
313
+
314
+ private
315
+
316
+ # @api private
317
+ def load_changeset
318
+ if PaperTrail.config.object_changes_adapter.respond_to?(:load_changeset)
319
+ return PaperTrail.config.object_changes_adapter.load_changeset(self)
320
+ end
321
+
322
+ # First, deserialize the `object_changes` column.
323
+ changes = ActiveSupport::HashWithIndifferentAccess.new(object_changes_deserialized)
324
+
325
+ # The next step is, perhaps unfortunately, called "de-serialization",
326
+ # and appears to be responsible for custom attribute serializers. For an
327
+ # example of a custom attribute serializer, see
328
+ # `Person::TimeZoneSerializer` in the test suite.
329
+ #
330
+ # Is `item.class` good enough? Does it handle `inheritance_column`
331
+ # as well as `Reifier#version_reification_class`? We were using
332
+ # `item_type.constantize`, but that is problematic when the STI parent
333
+ # is not versioned. (See `Vehicle` and `Car` in the test suite).
334
+ #
335
+ # Note: `item` returns nil if `event` is "destroy".
336
+ unless item.nil?
337
+ AttributeSerializers::ObjectChangesAttribute.
338
+ new(item.class).
339
+ deserialize(changes)
340
+ end
341
+
342
+ # Finally, return a Hash mapping each attribute name to
343
+ # a two-element array representing before and after.
344
+ changes
345
+ end
346
+
347
+ # If the `object_changes` column is a Postgres JSON column, then
348
+ # ActiveRecord will deserialize it for us. Otherwise, it's a string column
349
+ # and we must deserialize it ourselves.
350
+ # @api private
351
+ def object_changes_deserialized
352
+ if self.class.object_changes_col_is_json?
353
+ object_changes
354
+ else
355
+ begin
356
+ PaperTrail.serializer.load(object_changes)
357
+ rescue StandardError => e
358
+ if defined?(::Psych::Exception) && e.instance_of?(::Psych::Exception)
359
+ ::Kernel.warn format(E_YAML_PERMITTED_CLASSES, e)
360
+ end
361
+ {}
362
+ end
363
+ end
364
+ end
365
+
366
+ # Enforces the `version_limit`, if set. Default: no limit.
367
+ # @api private
368
+ def enforce_version_limit!
369
+ limit = version_limit
370
+ return unless limit.is_a? Numeric
371
+ previous_versions = sibling_versions.not_creates.
372
+ order(self.class.timestamp_sort_order("asc"))
373
+ return unless previous_versions.size > limit
374
+ excess_versions = previous_versions - previous_versions.last(limit)
375
+ excess_versions.map(&:destroy)
376
+ end
377
+
378
+ # @api private
379
+ def sibling_versions
380
+ @sibling_versions ||= self.class.with_item_keys(item_type, item_id)
381
+ end
382
+
383
+ # See docs section 2.e. Limiting the Number of Versions Created.
384
+ # The version limit can be global or per-model.
385
+ #
386
+ # @api private
387
+ def version_limit
388
+ klass = item.class
389
+ if limit_option?(klass)
390
+ klass.paper_trail_options[:limit]
391
+ elsif base_class_limit_option?(klass)
392
+ klass.base_class.paper_trail_options[:limit]
393
+ else
394
+ PaperTrail.config.version_limit
395
+ end
396
+ end
397
+
398
+ def limit_option?(klass)
399
+ klass.respond_to?(:paper_trail_options) && klass.paper_trail_options.key?(:limit)
400
+ end
401
+
402
+ def base_class_limit_option?(klass)
403
+ klass.respond_to?(:base_class) && limit_option?(klass.base_class)
404
+ end
405
+ end
406
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaperTrail
4
+ # The version number of the paper_trail gem. Not to be confused with
5
+ # `PaperTrail::Version`. Ruby constants are case-sensitive, apparently,
6
+ # and they are two different modules! It would be nice to remove `VERSION`,
7
+ # because of this confusion, but it's not worth the breaking change.
8
+ # People are encouraged to use `PaperTrail.gem_version` instead.
9
+ module VERSION
10
+ MAJOR = 17
11
+ MINOR = 0
12
+ TINY = 0
13
+
14
+ # Set PRE to nil unless it's a pre-release (beta, rc, etc.)
15
+ PRE = nil
16
+
17
+ STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".").freeze
18
+
19
+ def self.to_s
20
+ STRING
21
+ end
22
+ end
23
+ end
data/lib/paper_trail.rb CHANGED
@@ -1,29 +1,138 @@
1
- require 'yaml'
2
- require 'paper_trail/has_paper_trail'
3
- require 'paper_trail/version'
1
+ # frozen_string_literal: true
4
2
 
3
+ # AR does not require all of AS, but PT does. PT uses core_ext like
4
+ # `String#squish`, so we require `active_support/all`. Instead of eagerly
5
+ # loading all of AS here, we could put specific `require`s in only the various
6
+ # PT files that need them, but this seems easier to troubleshoot, though it may
7
+ # add a few milliseconds to rails boot time. If that becomes a pain point, we
8
+ # can revisit this decision.
9
+ require "active_support/all"
10
+
11
+ # We used to `require "active_record"` here, but that was [replaced with a
12
+ # Railtie](https://github.com/paper-trail-gem/paper_trail/pull/1281) in PT 12.
13
+ # As a result, we cannot reference `ActiveRecord` in this file (ie. until our
14
+ # Railtie has loaded). If we did, it would cause [problems with non-Rails
15
+ # projects](https://github.com/paper-trail-gem/paper_trail/pull/1401).
16
+
17
+ require "paper_trail/errors"
18
+ require "paper_trail/cleaner"
19
+ require "paper_trail/compatibility"
20
+ require "paper_trail/config"
21
+ require "paper_trail/record_history"
22
+ require "paper_trail/request"
23
+ require "paper_trail/version_number"
24
+ require "paper_trail/serializers/json"
25
+
26
+ # An ActiveRecord extension that tracks changes to your models, for auditing or
27
+ # versioning.
5
28
  module PaperTrail
6
- @@whodunnit = nil
29
+ E_TIMESTAMP_FIELD_CONFIG = <<-EOS.squish.freeze
30
+ PaperTrail.timestamp_field= has been removed, without replacement. It is no
31
+ longer configurable. The timestamp column in the versions table must now be
32
+ named created_at.
33
+ EOS
7
34
 
8
- def self.included(base)
9
- base.before_filter :set_whodunnit
10
- end
35
+ extend PaperTrail::Cleaner
11
36
 
12
- def self.whodunnit
13
- @@whodunnit.respond_to?(:call) ? @@whodunnit.call : @@whodunnit
14
- end
37
+ class << self
38
+ # Switches PaperTrail on or off, for all threads.
39
+ # @api public
40
+ def enabled=(value)
41
+ PaperTrail.config.enabled = value
42
+ end
15
43
 
16
- def self.whodunnit=(value)
17
- @@whodunnit = value
18
- end
44
+ # Returns `true` if PaperTrail is on, `false` otherwise. This is the
45
+ # on/off switch that affects all threads. Enabled by default.
46
+ # @api public
47
+ def enabled?
48
+ !!PaperTrail.config.enabled
49
+ end
50
+
51
+ # Returns PaperTrail's `::Gem::Version`, convenient for comparisons. This is
52
+ # recommended over `::PaperTrail::VERSION::STRING`.
53
+ #
54
+ # Added in 7.0.0
55
+ #
56
+ # @api public
57
+ def gem_version
58
+ ::Gem::Version.new(VERSION::STRING)
59
+ end
19
60
 
20
- private
61
+ # Set variables for the current request, eg. whodunnit.
62
+ #
63
+ # All request-level variables are now managed here, as of PT 9. Having the
64
+ # word "request" right there in your application code will remind you that
65
+ # these variables only affect the current request, not all threads.
66
+ #
67
+ # Given a block, temporarily sets the given `options`, executes the block,
68
+ # and returns the value of the block.
69
+ #
70
+ # Without a block, this currently just returns `PaperTrail::Request`.
71
+ # However, please do not use `PaperTrail::Request` directly. Currently,
72
+ # `Request` is a `Module`, but in the future it is quite possible we may
73
+ # make it a `Class`. If we make such a choice, we will not provide any
74
+ # warning and will not treat it as a breaking change. You've been warned :)
75
+ #
76
+ # @api public
77
+ def request(options = nil, &block)
78
+ if options.nil? && !block
79
+ Request
80
+ else
81
+ Request.with(options, &block)
82
+ end
83
+ end
21
84
 
22
- def set_whodunnit
23
- @@whodunnit = lambda {
24
- self.send :current_user rescue nil
25
- }
85
+ # Set the field which records when a version was created.
86
+ # @api public
87
+ def timestamp_field=(_field_name)
88
+ raise Error, E_TIMESTAMP_FIELD_CONFIG
89
+ end
90
+
91
+ # Set the PaperTrail serializer. This setting affects all threads.
92
+ # @api public
93
+ def serializer=(value)
94
+ PaperTrail.config.serializer = value
95
+ end
96
+
97
+ # Get the PaperTrail serializer used by all threads.
98
+ # @api public
99
+ def serializer
100
+ PaperTrail.config.serializer
101
+ end
102
+
103
+ # Returns PaperTrail's global configuration object, a singleton. These
104
+ # settings affect all threads.
105
+ # @api public
106
+ def config
107
+ @config ||= PaperTrail::Config.instance
108
+ yield @config if block_given?
109
+ @config
110
+ end
111
+ alias configure config
112
+
113
+ # @api public
114
+ def version
115
+ VERSION::STRING
116
+ end
117
+
118
+ def deprecator
119
+ @deprecator ||= ActiveSupport::Deprecation.new("16.0", "PaperTrail")
120
+ end
26
121
  end
27
122
  end
28
123
 
29
- ActionController::Base.send :include, PaperTrail
124
+ # PT is built on ActiveRecord, but does not require Rails. If Rails is defined,
125
+ # our Railtie makes sure not to load the AR-dependent parts of PT until AR is
126
+ # ready. A typical Rails `application.rb` has:
127
+ #
128
+ # ```
129
+ # require 'rails/all' # Defines `Rails`
130
+ # Bundler.require(*Rails.groups) # require 'paper_trail' (this file)
131
+ # ```
132
+ #
133
+ # Non-rails applications should take similar care to load AR before PT.
134
+ if defined?(Rails)
135
+ require "paper_trail/frameworks/rails"
136
+ else
137
+ require "paper_trail/frameworks/active_record"
138
+ end