paper_trail 8.0.0 → 9.0.0

Sign up to get free protection for your applications and to get access to all the features.
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