paper_trail 8.0.0 → 9.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 (39) hide show
  1. checksums.yaml +5 -5
  2. data/lib/generators/paper_trail/install_generator.rb +3 -1
  3. data/lib/paper_trail.rb +151 -84
  4. data/lib/paper_trail/attribute_serializers/attribute_serializer_factory.rb +27 -0
  5. data/lib/paper_trail/attribute_serializers/cast_attribute_serializer.rb +7 -4
  6. data/lib/paper_trail/attribute_serializers/object_attribute.rb +2 -0
  7. data/lib/paper_trail/attribute_serializers/object_changes_attribute.rb +2 -0
  8. data/lib/paper_trail/cleaner.rb +2 -0
  9. data/lib/paper_trail/config.rb +33 -8
  10. data/lib/paper_trail/frameworks/active_record.rb +2 -0
  11. data/lib/paper_trail/frameworks/active_record/models/paper_trail/version.rb +2 -0
  12. data/lib/paper_trail/frameworks/active_record/models/paper_trail/version_association.rb +2 -0
  13. data/lib/paper_trail/frameworks/cucumber.rb +5 -3
  14. data/lib/paper_trail/frameworks/rails.rb +2 -0
  15. data/lib/paper_trail/frameworks/rails/controller.rb +30 -15
  16. data/lib/paper_trail/frameworks/rails/engine.rb +2 -0
  17. data/lib/paper_trail/frameworks/rspec.rb +5 -3
  18. data/lib/paper_trail/frameworks/rspec/helpers.rb +2 -0
  19. data/lib/paper_trail/has_paper_trail.rb +2 -2
  20. data/lib/paper_trail/model_config.rb +77 -22
  21. data/lib/paper_trail/queries/versions/where_object.rb +2 -0
  22. data/lib/paper_trail/queries/versions/where_object_changes.rb +3 -1
  23. data/lib/paper_trail/record_history.rb +2 -0
  24. data/lib/paper_trail/record_trail.rb +189 -55
  25. data/lib/paper_trail/reifier.rb +4 -2
  26. data/lib/paper_trail/reifiers/belongs_to.rb +3 -1
  27. data/lib/paper_trail/reifiers/has_and_belongs_to_many.rb +3 -1
  28. data/lib/paper_trail/reifiers/has_many.rb +3 -1
  29. data/lib/paper_trail/reifiers/has_many_through.rb +3 -1
  30. data/lib/paper_trail/reifiers/has_one.rb +52 -4
  31. data/lib/paper_trail/request.rb +183 -0
  32. data/lib/paper_trail/serializers/json.rb +2 -2
  33. data/lib/paper_trail/serializers/yaml.rb +10 -5
  34. data/lib/paper_trail/type_serializers/postgres_array_serializer.rb +49 -0
  35. data/lib/paper_trail/version_association_concern.rb +1 -1
  36. data/lib/paper_trail/version_concern.rb +2 -6
  37. data/lib/paper_trail/version_number.rb +3 -1
  38. metadata +55 -81
  39. data/lib/paper_trail/attribute_serializers/legacy_active_record_shim.rb +0 -48
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 91bf6fabed9a524ca3251a32df08bf75daaf8e59
4
- data.tar.gz: 98c9208fcb6946ce58943ff3188ddee60f90a0de
2
+ SHA256:
3
+ metadata.gz: 24616bf1e1df70302e7417a24d60141dc2a0d20f1c6e8ac3690dbd4abf890a82
4
+ data.tar.gz: 8f0ea37c0b65a0c15bfbf4096db430e81d848952f6d42ecbc09d5028fa042d3e
5
5
  SHA512:
6
- metadata.gz: f75fd7f32568e15f48194cbd099152f75d0e38bd3e3e5f173c16f20919ad3766f9ccfebb69203558946abcd48d1da017a80749c1c957093af589478fa187b2cb
7
- data.tar.gz: b4916a9fae7ce82d73d98baefb6d63d41482c0e7dc090751b2ddf7543876a070782c16a233f1b89617932ec1fb92d9810995b67ab4196d7bff94ecc471e2f912
6
+ metadata.gz: 109af4466c2f6a61b42633bb94e46909203252757d7864250dd8e62cddfe82ef317419fc5ad8f784eecb453af387a02242b643b58b8eadd5778ce1e64c4b56a3
7
+ data.tar.gz: b840ca06183ff5411b0dcc493c8bdbc8abaa94060d8dfccaf1b0cbd0bbf69cd4bda4c2a98e3bd406d4760317e9949c89e306bd7b54e676237b162f3721f30497
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rails/generators"
2
4
  require "rails/generators/active_record"
3
5
 
@@ -43,7 +45,7 @@ module PaperTrail
43
45
  def create_initializer
44
46
  create_file(
45
47
  "config/initializers/paper_trail.rb",
46
- "PaperTrail.config.track_associations = #{!!options.with_associations?}"
48
+ "PaperTrail.config.track_associations = #{!!options.with_associations?}\n"
47
49
  )
48
50
  end
49
51
 
@@ -1,9 +1,26 @@
1
+ # frozen_string_literal: true
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
+ # AR is required for, eg. has_paper_trail.rb, so we could put this `require` in
12
+ # all of those files, but it seems easier to troubleshoot if we just make sure
13
+ # AR is loaded here before loading *any* of PT. See discussion of
14
+ # performance/simplicity tradeoff for activesupport above.
15
+ require "active_record"
16
+
1
17
  require "request_store"
2
18
  require "paper_trail/cleaner"
3
19
  require "paper_trail/config"
4
20
  require "paper_trail/has_paper_trail"
5
21
  require "paper_trail/record_history"
6
22
  require "paper_trail/reifier"
23
+ require "paper_trail/request"
7
24
  require "paper_trail/version_association_concern"
8
25
  require "paper_trail/version_concern"
9
26
  require "paper_trail/version_number"
@@ -13,133 +30,175 @@ require "paper_trail/serializers/yaml"
13
30
  # An ActiveRecord extension that tracks changes to your models, for auditing or
14
31
  # versioning.
15
32
  module PaperTrail
33
+ E_RAILS_NOT_LOADED = <<-EOS.squish.freeze
34
+ PaperTrail has been loaded too early, before rails is loaded. This can
35
+ happen when another gem defines the ::Rails namespace, then PT is loaded,
36
+ all before rails is loaded. You may want to reorder your Gemfile, or defer
37
+ the loading of PT by using `require: false` and a manual require elsewhere.
38
+ EOS
39
+ E_TIMESTAMP_FIELD_CONFIG = <<-EOS.squish.freeze
40
+ PaperTrail.timestamp_field= has been removed, without replacement. It is no
41
+ longer configurable. The timestamp column in the versions table must now be
42
+ named created_at.
43
+ EOS
44
+
16
45
  extend PaperTrail::Cleaner
17
46
 
18
47
  class << self
19
48
  # @api private
20
49
  def clear_transaction_id
21
- self.transaction_id = nil
50
+ ::ActiveSupport::Deprecation.warn(
51
+ "PaperTrail.clear_transaction_id is deprecated, " \
52
+ "use PaperTrail.request.clear_transaction_id",
53
+ caller(1)
54
+ )
55
+ request.clear_transaction_id
22
56
  end
23
57
 
24
- # Switches PaperTrail on or off.
58
+ # Switches PaperTrail on or off, for all threads.
25
59
  # @api public
26
60
  def enabled=(value)
27
61
  PaperTrail.config.enabled = value
28
62
  end
29
63
 
30
- # Returns `true` if PaperTrail is on, `false` otherwise.
31
- # PaperTrail is enabled by default.
64
+ # Returns `true` if PaperTrail is on, `false` otherwise. This is the
65
+ # on/off switch that affects all threads. Enabled by default.
32
66
  # @api public
33
67
  def enabled?
34
68
  !!PaperTrail.config.enabled
35
69
  end
36
70
 
37
- # Sets whether PaperTrail is enabled or disabled for the current request.
38
- # @api public
71
+ # @deprecated
39
72
  def enabled_for_controller=(value)
40
- paper_trail_store[:request_enabled_for_controller] = value
73
+ ::ActiveSupport::Deprecation.warn(
74
+ "PaperTrail.enabled_for_controller= is deprecated, " \
75
+ "use PaperTrail.request.enabled=",
76
+ caller(1)
77
+ )
78
+ request.enabled = value
41
79
  end
42
80
 
43
- # Returns `true` if PaperTrail is enabled for the request, `false` otherwise.
44
- #
45
- # See `PaperTrail::Rails::Controller#paper_trail_enabled_for_controller`.
46
- # @api public
81
+ # @deprecated
47
82
  def enabled_for_controller?
48
- !!paper_trail_store[:request_enabled_for_controller]
83
+ ::ActiveSupport::Deprecation.warn(
84
+ "PaperTrail.enabled_for_controller? is deprecated, " \
85
+ "use PaperTrail.request.enabled?",
86
+ caller(1)
87
+ )
88
+ request.enabled?
49
89
  end
50
90
 
51
- # Sets whether PaperTrail is enabled or disabled for this model in the
52
- # current request.
53
- # @api public
91
+ # @deprecated
54
92
  def enabled_for_model(model, value)
55
- paper_trail_store[:"enabled_for_#{model}"] = value
93
+ ::ActiveSupport::Deprecation.warn(
94
+ "PaperTrail.enabled_for_model is deprecated, " \
95
+ "use PaperTrail.request.enabled_for_model",
96
+ caller(1)
97
+ )
98
+ request.enabled_for_model(model, value)
56
99
  end
57
100
 
58
- # Returns `true` if PaperTrail is enabled for this model in the current
59
- # request, `false` otherwise.
60
- # @api public
101
+ # @deprecated
61
102
  def enabled_for_model?(model)
62
- !!paper_trail_store.fetch(:"enabled_for_#{model}", true)
103
+ ::ActiveSupport::Deprecation.warn(
104
+ "PaperTrail.enabled_for_model? is deprecated, " \
105
+ "use PaperTrail.request.enabled_for_model?",
106
+ caller(1)
107
+ )
108
+ request.enabled_for_model?(model)
63
109
  end
64
110
 
65
- # Returns a `::Gem::Version`, convenient for comparisons. This is
111
+ # Returns PaperTrail's `::Gem::Version`, convenient for comparisons. This is
66
112
  # recommended over `::PaperTrail::VERSION::STRING`.
67
113
  # @api public
68
114
  def gem_version
69
115
  ::Gem::Version.new(VERSION::STRING)
70
116
  end
71
117
 
118
+ # Set variables for the current request, eg. whodunnit.
119
+ #
120
+ # All request-level variables are now managed here, as of PT 9. Having the
121
+ # word "request" right there in your application code will remind you that
122
+ # these variables only affect the current request, not all threads.
123
+ #
124
+ # Given a block, temporarily sets the given `options` and execute the block.
125
+ #
126
+ # Without a block, this currently just returns `PaperTrail::Request`.
127
+ # However, please do not use `PaperTrail::Request` directly. Currently,
128
+ # `Request` is a `Module`, but in the future it is quite possible we may
129
+ # make it a `Class`. If we make such a choice, we will not provide any
130
+ # warning and will not treat it as a breaking change. You've been warned :)
131
+ #
132
+ # @api public
133
+ def request(options = nil, &block)
134
+ if options.nil? && !block_given?
135
+ Request
136
+ else
137
+ Request.with(options, &block)
138
+ nil
139
+ end
140
+ end
141
+
72
142
  # Set the field which records when a version was created.
73
143
  # @api public
74
144
  def timestamp_field=(_field_name)
75
- raise(
76
- "PaperTrail.timestamp_field= has been removed, without replacement. " \
77
- "It is no longer configurable. The timestamp field in the versions table " \
78
- "must now be named created_at."
79
- )
145
+ raise(E_TIMESTAMP_FIELD_CONFIG)
80
146
  end
81
147
 
82
- # Sets who is responsible for any changes that occur. You would normally use
83
- # this in a migration or on the console, when working with models directly.
84
- # In a controller it is set automatically to the `current_user`.
85
- # @api public
148
+ # @deprecated
86
149
  def whodunnit=(value)
87
- paper_trail_store[:whodunnit] = value
150
+ ::ActiveSupport::Deprecation.warn(
151
+ "PaperTrail.whodunnit= is deprecated, use PaperTrail.request.whodunnit=",
152
+ caller(1)
153
+ )
154
+ request.whodunnit = value
88
155
  end
89
156
 
90
- # If nothing passed, returns who is reponsible for any changes that occur.
91
- #
92
- # PaperTrail.whodunnit = "someone"
93
- # PaperTrail.whodunnit # => "someone"
94
- #
95
- # If value and block passed, set this value as whodunnit for the duration of the block
96
- #
97
- # PaperTrail.whodunnit("me") do
98
- # puts PaperTrail.whodunnit # => "me"
99
- # end
100
- #
101
- # @api public
102
- def whodunnit(value = nil)
103
- if value
104
- raise ArgumentError, "no block given" unless block_given?
105
-
106
- previous_whodunnit = paper_trail_store[:whodunnit]
107
- paper_trail_store[:whodunnit] = value
108
-
109
- begin
110
- yield
111
- ensure
112
- paper_trail_store[:whodunnit] = previous_whodunnit
113
- end
114
- elsif paper_trail_store[:whodunnit].respond_to?(:call)
115
- paper_trail_store[:whodunnit].call
157
+ # @deprecated
158
+ def whodunnit(value = nil, &block)
159
+ if value.nil?
160
+ ::ActiveSupport::Deprecation.warn(
161
+ "PaperTrail.whodunnit is deprecated, use PaperTrail.request.whodunnit",
162
+ caller(1)
163
+ )
164
+ request.whodunnit
165
+ elsif block_given?
166
+ ::ActiveSupport::Deprecation.warn(
167
+ "Passing a block to PaperTrail.whodunnit is deprecated, " \
168
+ 'use PaperTrail.request(whodunnit: "John") do .. end',
169
+ caller(1)
170
+ )
171
+ request(whodunnit: value, &block)
116
172
  else
117
- paper_trail_store[:whodunnit]
173
+ raise ArgumentError, "Invalid arguments"
118
174
  end
119
175
  end
120
176
 
121
- # Sets any information from the controller that you want PaperTrail to
122
- # store. By default this is set automatically by a before filter.
123
- # @api public
177
+ # @deprecated
124
178
  def controller_info=(value)
125
- paper_trail_store[:controller_info] = value
179
+ ::ActiveSupport::Deprecation.warn(
180
+ "PaperTrail.controller_info= is deprecated, use PaperTrail.request.controller_info=",
181
+ caller(1)
182
+ )
183
+ request.controller_info = value
126
184
  end
127
185
 
128
- # Returns any information from the controller that you want
129
- # PaperTrail to store.
130
- #
131
- # See `PaperTrail::Rails::Controller#info_for_paper_trail`.
132
- # @api public
186
+ # @deprecated
133
187
  def controller_info
134
- paper_trail_store[:controller_info]
188
+ ::ActiveSupport::Deprecation.warn(
189
+ "PaperTrail.controller_info is deprecated, use PaperTrail.request.controller_info",
190
+ caller(1)
191
+ )
192
+ request.controller_info
135
193
  end
136
194
 
137
- # Getter and Setter for PaperTrail Serializer
195
+ # Set the PaperTrail serializer. This setting affects all threads.
138
196
  # @api public
139
197
  def serializer=(value)
140
198
  PaperTrail.config.serializer = value
141
199
  end
142
200
 
201
+ # Get the PaperTrail serializer used by all threads.
143
202
  # @api public
144
203
  def serializer
145
204
  PaperTrail.config.serializer
@@ -147,27 +206,29 @@ module PaperTrail
147
206
 
148
207
  # @api public
149
208
  def transaction?
150
- ::ActiveRecord::Base.connection.open_transactions > 0
209
+ ::ActiveRecord::Base.connection.open_transactions.positive?
151
210
  end
152
211
 
153
- # @api public
212
+ # @deprecated
154
213
  def transaction_id
155
- paper_trail_store[:transaction_id]
214
+ ::ActiveSupport::Deprecation.warn(
215
+ "PaperTrail.transaction_id is deprecated without replacement.",
216
+ caller(1)
217
+ )
218
+ request.transaction_id
156
219
  end
157
220
 
158
- # @api public
221
+ # @deprecated
159
222
  def transaction_id=(id)
160
- paper_trail_store[:transaction_id] = id
161
- end
162
-
163
- # Thread-safe hash to hold PaperTrail's data. Initializing with needed
164
- # default values.
165
- # @api private
166
- def paper_trail_store
167
- RequestStore.store[:paper_trail] ||= { request_enabled_for_controller: true }
223
+ ::ActiveSupport::Deprecation.warn(
224
+ "PaperTrail.transaction_id= is deprecated without replacement.",
225
+ caller(1)
226
+ )
227
+ request.transaction_id = id
168
228
  end
169
229
 
170
- # Returns PaperTrail's configuration object.
230
+ # Returns PaperTrail's global configuration object, a singleton. These
231
+ # settings affect all threads.
171
232
  # @api private
172
233
  def config
173
234
  @config ||= PaperTrail::Config.instance
@@ -187,8 +248,14 @@ ActiveSupport.on_load(:active_record) do
187
248
  end
188
249
 
189
250
  # Require frameworks
190
- if defined?(::Rails) && ActiveRecord::VERSION::STRING >= "3.2"
191
- require "paper_trail/frameworks/rails"
251
+ if defined?(::Rails)
252
+ # Rails module is sometimes defined by gems like rails-html-sanitizer
253
+ # so we check for presence of Rails.application.
254
+ if defined?(::Rails.application)
255
+ require "paper_trail/frameworks/rails"
256
+ else
257
+ ::Kernel.warn(::PaperTrail::E_RAILS_NOT_LOADED)
258
+ end
192
259
  else
193
260
  require "paper_trail/frameworks/active_record"
194
261
  end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "paper_trail/type_serializers/postgres_array_serializer"
4
+
5
+ module PaperTrail
6
+ module AttributeSerializers
7
+ # Values returned by some Active Record serializers are
8
+ # not suited for writing JSON to a text column. This factory
9
+ # replaces certain default Active Record serializers
10
+ # with custom PaperTrail ones.
11
+ module AttributeSerializerFactory
12
+ AR_PG_ARRAY_CLASS = "ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array"
13
+
14
+ def self.for(klass, attr)
15
+ active_record_serializer = klass.type_for_attribute(attr)
16
+ if active_record_serializer.class.name == AR_PG_ARRAY_CLASS
17
+ TypeSerializers::PostgresArraySerializer.new(
18
+ active_record_serializer.subtype,
19
+ active_record_serializer.delimiter
20
+ )
21
+ else
22
+ active_record_serializer
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "paper_trail/attribute_serializers/attribute_serializer_factory"
4
+
1
5
  module PaperTrail
2
6
  # :nodoc:
3
7
  module AttributeSerializers
@@ -6,8 +10,7 @@ module PaperTrail
6
10
  # to an attribute of type `ActiveRecord::Type::Integer`.
7
11
  #
8
12
  # This implementation depends on the `type_for_attribute` method, which was
9
- # introduced in rails 4.2. In older versions of rails, we shim this method
10
- # with `LegacyActiveRecordShim`.
13
+ # introduced in rails 4.2. As of PT 8, we no longer support rails < 4.2.
11
14
  class CastAttributeSerializer
12
15
  def initialize(klass)
13
16
  @klass = klass
@@ -33,7 +36,7 @@ module PaperTrail
33
36
  # This implementation uses AR 5's `serialize` and `deserialize`.
34
37
  class CastAttributeSerializer
35
38
  def serialize(attr, val)
36
- @klass.type_for_attribute(attr).serialize(val)
39
+ AttributeSerializerFactory.for(@klass, attr).serialize(val)
37
40
  end
38
41
 
39
42
  def deserialize(attr, val)
@@ -41,7 +44,7 @@ module PaperTrail
41
44
  # Because PT 4 used to save the string version of enums to `object_changes`
42
45
  val
43
46
  else
44
- @klass.type_for_attribute(attr).deserialize(val)
47
+ AttributeSerializerFactory.for(@klass, attr).deserialize(val)
45
48
  end
46
49
  end
47
50
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "paper_trail/attribute_serializers/cast_attribute_serializer"
2
4
 
3
5
  module PaperTrail
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "paper_trail/attribute_serializers/cast_attribute_serializer"
2
4
 
3
5
  module PaperTrail
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module PaperTrail
2
4
  # Utilities for deleting version records.
3
5
  module Cleaner
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "singleton"
2
4
  require "paper_trail/serializers/yaml"
3
5
 
@@ -5,9 +7,26 @@ module PaperTrail
5
7
  # Global configuration affecting all threads. Some thread-specific
6
8
  # configuration can be found in `paper_trail.rb`, others in `controller.rb`.
7
9
  class Config
10
+ DPR_TRACK_ASSOC = <<~STR
11
+ Association tracking is an endangered feature. For the past three or four
12
+ years it has been an experimental feature, not recommended for production.
13
+ It has a long list of known issues
14
+ (https://github.com/airblade/paper_trail#4b1-known-issues) and has no
15
+ regular volunteers caring for it.
16
+
17
+ If you don't use this feature, I strongly recommend disabling it.
18
+
19
+ If you do use this feature, please head over to
20
+ https://github.com/airblade/paper_trail/issues/1070 and volunteer to work
21
+ on the known issues.
22
+
23
+ If we can't make a serious dent in the list of known issues over the next
24
+ few years, then I'm inclined to delete it, though that would make me sad
25
+ because I've put dozens of hours into it, and I know others have too.
26
+ STR
27
+
8
28
  include Singleton
9
29
  attr_accessor :serializer, :version_limit
10
- attr_writer :track_associations
11
30
 
12
31
  def initialize
13
32
  # Variables which affect all threads, whose access is synchronized.
@@ -18,18 +37,24 @@ module PaperTrail
18
37
  @serializer = PaperTrail::Serializers::YAML
19
38
  end
20
39
 
21
- # Previously, we checked `PaperTrail::VersionAssociation.table_exists?`
40
+ def track_associations=(value)
41
+ @track_associations = !!value
42
+ if @track_associations
43
+ ::ActiveSupport::Deprecation.warn(DPR_TRACK_ASSOC, caller(1))
44
+ end
45
+ end
46
+
47
+ # As of PaperTrail 5, `track_associations?` defaults to false. Tracking
48
+ # associations is an experimental feature so we recommend setting
49
+ # PaperTrail.config.track_associations = false in your
50
+ # config/initializers/paper_trail.rb
51
+ #
52
+ # In PT 4, we checked `PaperTrail::VersionAssociation.table_exists?`
22
53
  # here, but that proved to be problematic in situations when the database
23
54
  # connection had not been established, or when the database does not exist
24
55
  # yet (as with `rake db:create`).
25
56
  def track_associations?
26
57
  if @track_associations.nil?
27
- ActiveSupport::Deprecation.warn <<-EOS.strip_heredoc.gsub(/\s+/, " ")
28
- PaperTrail.config.track_associations has not been set. As of PaperTrail 5, it
29
- defaults to false. Tracking associations is an experimental feature so
30
- we recommend setting PaperTrail.config.track_associations = false in
31
- your config/initializers/paper_trail.rb
32
- EOS
33
58
  false
34
59
  else
35
60
  @track_associations