paper_trail 10.3.1 → 12.1.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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +20 -0
  3. data/lib/generators/paper_trail/install/install_generator.rb +13 -7
  4. data/lib/generators/paper_trail/install/templates/create_versions.rb.erb +1 -1
  5. data/lib/generators/paper_trail/migration_generator.rb +5 -4
  6. data/lib/generators/paper_trail/update_item_subtype/update_item_subtype_generator.rb +4 -2
  7. data/lib/paper_trail/attribute_serializers/attribute_serializer_factory.rb +24 -10
  8. data/lib/paper_trail/attribute_serializers/cast_attribute_serializer.rb +8 -46
  9. data/lib/paper_trail/compatibility.rb +2 -2
  10. data/lib/paper_trail/config.rb +0 -33
  11. data/lib/paper_trail/errors.rb +33 -0
  12. data/lib/paper_trail/events/base.rb +35 -50
  13. data/lib/paper_trail/events/destroy.rb +1 -1
  14. data/lib/paper_trail/frameworks/active_record.rb +9 -2
  15. data/lib/paper_trail/frameworks/rails/controller.rb +1 -9
  16. data/lib/paper_trail/frameworks/rails/railtie.rb +30 -0
  17. data/lib/paper_trail/frameworks/rails.rb +1 -2
  18. data/lib/paper_trail/has_paper_trail.rb +1 -1
  19. data/lib/paper_trail/model_config.rb +25 -30
  20. data/lib/paper_trail/queries/versions/where_attribute_changes.rb +50 -0
  21. data/lib/paper_trail/queries/versions/where_object.rb +1 -1
  22. data/lib/paper_trail/queries/versions/where_object_changes.rb +8 -13
  23. data/lib/paper_trail/queries/versions/where_object_changes_from.rb +57 -0
  24. data/lib/paper_trail/queries/versions/where_object_changes_to.rb +57 -0
  25. data/lib/paper_trail/record_trail.rb +3 -5
  26. data/lib/paper_trail/reifier.rb +41 -26
  27. data/lib/paper_trail/request.rb +0 -3
  28. data/lib/paper_trail/serializers/json.rb +0 -10
  29. data/lib/paper_trail/serializers/yaml.rb +0 -12
  30. data/lib/paper_trail/type_serializers/postgres_array_serializer.rb +1 -14
  31. data/lib/paper_trail/version_concern.rb +75 -40
  32. data/lib/paper_trail/version_number.rb +3 -3
  33. data/lib/paper_trail.rb +15 -40
  34. metadata +113 -40
  35. data/lib/paper_trail/frameworks/rails/engine.rb +0 -45
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a08a7c456a492933cc36762913040e14bad8f468fc08889580faf9e86ade7fea
4
- data.tar.gz: 19711dc6c6e4a438a0a97819c572df610d52db0f5c9aeba0a39eb1f44f3909c0
3
+ metadata.gz: 7e29e666977c2b4072eab98684b884cb43dba15e85367eb8221b9294ebad945c
4
+ data.tar.gz: 750c5670a52675ab4dcc17bfd5389eaf87ebe4d4a3a68ccbb7ed2da1bd43e52f
5
5
  SHA512:
6
- metadata.gz: b39e89605a2b03889976070f0871f9dcd07544b97b6e0105b78035b5fb1ec030c7d5438dc0fe9fb16cccc8e3624cd920d37bd73066f8a4f3892bf356fa088e85
7
- data.tar.gz: 56567a718ea4e605d32ffc2b71835b188f6cefab8dd7111b7abcdfc6d9e5b843e8468312ed4dfbc87f74607c1cd4a197ae2c11a9cb44ee2dd16a37a11303e6ad
6
+ metadata.gz: b44b40b4683d987d67faf3fabb87d603cd6d99dad2c0c3b6b2859c5d30fd862fe3058e2fa1a1cea982a15ec45a4b8b194a55590b8d523aa8630d3b0644374b8e
7
+ data.tar.gz: '0882176106dfa57ce49b0cf0c1267e2465e134a07719fc3436beec7cad5e10a23c6ff7d409b3332123cf8a5b7ce99e854351e4d60072894026027772f1cc14e8'
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Andy Stewart, AirBlade Software Ltd.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -25,10 +25,14 @@ module PaperTrail
25
25
  " See section 5.c. Generators in README.md for more information."
26
26
 
27
27
  def create_migration_file
28
- add_paper_trail_migration("create_versions",
28
+ add_paper_trail_migration(
29
+ "create_versions",
29
30
  item_type_options: item_type_options,
30
- versions_table_options: versions_table_options)
31
- add_paper_trail_migration("add_object_changes_to_versions") if options.with_changes?
31
+ versions_table_options: versions_table_options
32
+ )
33
+ if options.with_changes?
34
+ add_paper_trail_migration("add_object_changes_to_versions")
35
+ end
32
36
  end
33
37
 
34
38
  private
@@ -36,9 +40,11 @@ module PaperTrail
36
40
  # MySQL 5.6 utf8mb4 limit is 191 chars for keys used in indexes.
37
41
  # See https://github.com/paper-trail-gem/paper_trail/issues/651
38
42
  def item_type_options
39
- opt = { null: false }
40
- opt[:limit] = 191 if mysql?
41
- ", #{opt}"
43
+ if mysql?
44
+ ", { null: false, limit: 191 }"
45
+ else
46
+ ", { null: false }"
47
+ end
42
48
  end
43
49
 
44
50
  def mysql?
@@ -62,7 +68,7 @@ module PaperTrail
62
68
  #
63
69
  def versions_table_options
64
70
  if mysql?
65
- ', { options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci" }'
71
+ ', options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci"'
66
72
  else
67
73
  ""
68
74
  end
@@ -11,7 +11,7 @@ class CreateVersions < ActiveRecord::Migration<%= migration_version %>
11
11
  def change
12
12
  create_table :versions<%= versions_table_options %> do |t|
13
13
  t.string :item_type<%= item_type_options %>
14
- t.integer :item_id, null: false, limit: 8
14
+ t.bigint :item_id, null: false
15
15
  t.string :event, null: false
16
16
  t.string :whodunnit
17
17
  t.text :object, limit: TEXT_BYTES
@@ -28,10 +28,11 @@ module PaperTrail
28
28
  end
29
29
 
30
30
  def migration_version
31
- major = ActiveRecord::VERSION::MAJOR
32
- if major >= 5
33
- "[#{major}.#{ActiveRecord::VERSION::MINOR}]"
34
- end
31
+ format(
32
+ "[%d.%d]",
33
+ ActiveRecord::VERSION::MAJOR,
34
+ ActiveRecord::VERSION::MINOR
35
+ )
35
36
  end
36
37
  end
37
38
  end
@@ -7,8 +7,10 @@ module PaperTrail
7
7
  class UpdateItemSubtypeGenerator < MigrationGenerator
8
8
  source_root File.expand_path("templates", __dir__)
9
9
 
10
- desc "Generates (but does not run) a migration to update item_subtype for STI entries in an "\
11
- "existing versions table."
10
+ desc(
11
+ "Generates (but does not run) a migration to update item_subtype for "\
12
+ "STI entries in an existing versions table."
13
+ )
12
14
 
13
15
  def create_migration_file
14
16
  add_paper_trail_migration("update_versions_for_item_subtype", sti_type_options: options)
@@ -8,18 +8,32 @@ module PaperTrail
8
8
  # not suited for writing JSON to a text column. This factory
9
9
  # replaces certain default Active Record serializers
10
10
  # with custom PaperTrail ones.
11
+ #
12
+ # @api private
11
13
  module AttributeSerializerFactory
12
- AR_PG_ARRAY_CLASS = "ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array"
14
+ class << self
15
+ # @api private
16
+ def for(klass, attr)
17
+ active_record_serializer = klass.type_for_attribute(attr)
18
+ if ar_pg_array?(active_record_serializer)
19
+ TypeSerializers::PostgresArraySerializer.new(
20
+ active_record_serializer.subtype,
21
+ active_record_serializer.delimiter
22
+ )
23
+ else
24
+ active_record_serializer
25
+ end
26
+ end
27
+
28
+ private
13
29
 
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
30
+ # @api private
31
+ def ar_pg_array?(obj)
32
+ if defined?(::ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array)
33
+ obj.instance_of?(::ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array)
34
+ else
35
+ false
36
+ end
23
37
  end
24
38
  end
25
39
  end
@@ -8,9 +8,6 @@ module PaperTrail
8
8
  # The `CastAttributeSerializer` (de)serializes model attribute values. For
9
9
  # example, the string "1.99" serializes into the integer `1` when assigned
10
10
  # to an attribute of type `ActiveRecord::Type::Integer`.
11
- #
12
- # This implementation depends on the `type_for_attribute` method, which was
13
- # introduced in rails 4.2. As of PT 8, we no longer support rails < 4.2.
14
11
  class CastAttributeSerializer
15
12
  def initialize(klass)
16
13
  @klass = klass
@@ -30,53 +27,18 @@ module PaperTrail
30
27
  def defined_enums
31
28
  @defined_enums ||= (@klass.respond_to?(:defined_enums) ? @klass.defined_enums : {})
32
29
  end
33
- end
34
-
35
- if ::ActiveRecord::VERSION::MAJOR >= 5
36
- # This implementation uses AR 5's `serialize` and `deserialize`.
37
- class CastAttributeSerializer
38
- def serialize(attr, val)
39
- AttributeSerializerFactory.for(@klass, attr).serialize(val)
40
- end
41
30
 
42
- def deserialize(attr, val)
43
- if defined_enums[attr] && val.is_a?(::String)
44
- # Because PT 4 used to save the string version of enums to `object_changes`
45
- val
46
- else
47
- AttributeSerializerFactory.for(@klass, attr).deserialize(val)
48
- end
31
+ def deserialize(attr, val)
32
+ if defined_enums[attr] && val.is_a?(::String)
33
+ # Because PT 4 used to save the string version of enums to `object_changes`
34
+ val
35
+ else
36
+ AttributeSerializerFactory.for(@klass, attr).deserialize(val)
49
37
  end
50
38
  end
51
- else
52
- # This implementation uses AR 4.2's `type_cast_for_database`. For
53
- # versions of AR < 4.2 we provide an implementation of
54
- # `type_cast_for_database` in our shim attribute type classes,
55
- # `NoOpAttribute` and `SerializedAttribute`.
56
- class CastAttributeSerializer
57
- def serialize(attr, val)
58
- castable_val = val
59
- if defined_enums[attr]
60
- # `attr` is an enum. Find the number that corresponds to `val`. If `val` is
61
- # a number already, there won't be a corresponding entry, just use `val`.
62
- castable_val = defined_enums[attr][val] || val
63
- end
64
- @klass.type_for_attribute(attr).type_cast_for_database(castable_val)
65
- end
66
39
 
67
- def deserialize(attr, val)
68
- if defined_enums[attr] && val.is_a?(::String)
69
- # Because PT 4 used to save the string version of enums to `object_changes`
70
- val
71
- else
72
- val = @klass.type_for_attribute(attr).type_cast_from_database(val)
73
- if defined_enums[attr]
74
- defined_enums[attr].key(val)
75
- else
76
- val
77
- end
78
- end
79
- end
40
+ def serialize(attr, val)
41
+ AttributeSerializerFactory.for(@klass, attr).serialize(val)
80
42
  end
81
43
  end
82
44
  end
@@ -17,8 +17,8 @@ module PaperTrail
17
17
  # newer rails versions. Most PT users should avoid incompatible rails
18
18
  # versions.
19
19
  module Compatibility
20
- ACTIVERECORD_GTE = ">= 4.2"
21
- ACTIVERECORD_LT = "< 6.1"
20
+ ACTIVERECORD_GTE = ">= 5.2" # enforced in gemspec
21
+ ACTIVERECORD_LT = "< 7.0" # not enforced in gemspec
22
22
 
23
23
  E_INCOMPATIBLE_AR = <<-EOS
24
24
  PaperTrail %s is not compatible with ActiveRecord %s. We allow PT
@@ -9,14 +9,6 @@ module PaperTrail
9
9
  class Config
10
10
  include Singleton
11
11
 
12
- E_PT_AT_REMOVED = <<-EOS.squish
13
- Association Tracking for PaperTrail has been extracted to a separate gem.
14
- To use it, please add `paper_trail-association_tracking` to your Gemfile.
15
- If you don't use it (most people don't, that's the default) and you set
16
- `track_associations = false` somewhere (probably a rails initializer) you
17
- can remove that line now.
18
- EOS
19
-
20
12
  attr_accessor(
21
13
  :association_reify_error_behaviour,
22
14
  :object_changes_adapter,
@@ -43,30 +35,5 @@ module PaperTrail
43
35
  def enabled=(enable)
44
36
  @mutex.synchronize { @enabled = enable }
45
37
  end
46
-
47
- # In PT 10, the paper_trail-association_tracking gem was changed from a
48
- # runtime dependency to a development dependency. We raise an error about
49
- # this for the people who don't read changelogs.
50
- #
51
- # We raise a generic RuntimeError instead of a specific PT error class
52
- # because there is no known use case where someone would want to rescue
53
- # this. If we think of such a use case in the future we can revisit this
54
- # decision.
55
- #
56
- # @override If PT-AT is `require`d, it will replace this method with its
57
- # own implementation.
58
- def track_associations=(value)
59
- if value
60
- raise E_PT_AT_REMOVED
61
- else
62
- ::Kernel.warn(E_PT_AT_REMOVED)
63
- end
64
- end
65
-
66
- # @override If PT-AT is `require`d, it will replace this method with its
67
- # own implementation.
68
- def track_associations?
69
- raise E_PT_AT_REMOVED
70
- end
71
38
  end
72
39
  end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaperTrail
4
+ # Generic PaperTrail exception.
5
+ # @api public
6
+ class Error < StandardError
7
+ end
8
+
9
+ # An unexpected option, perhaps a typo, was passed to a public API method.
10
+ # @api public
11
+ class InvalidOption < Error
12
+ end
13
+
14
+ # The application's database schema is not supported.
15
+ # @api public
16
+ class UnsupportedSchema < Error
17
+ end
18
+
19
+ # The application's database column type is not supported.
20
+ # @api public
21
+ class UnsupportedColumnType < UnsupportedSchema
22
+ def initialize(method:, expected:, actual:)
23
+ super(
24
+ format(
25
+ "%s expected %s column, got %s",
26
+ method,
27
+ expected,
28
+ actual
29
+ )
30
+ )
31
+ end
32
+ end
33
+ end
@@ -22,8 +22,6 @@ module PaperTrail
22
22
  #
23
23
  # @api private
24
24
  class Base
25
- RAILS_GTE_5_1 = ::ActiveRecord.gem_version >= ::Gem::Version.new("5.1.0.beta1")
26
-
27
25
  # @api private
28
26
  def initialize(record, in_after_callback)
29
27
  @record = record
@@ -51,7 +49,7 @@ module PaperTrail
51
49
  #
52
50
  # @api private
53
51
  def attribute_changed_in_latest_version?(attr_name)
54
- if @in_after_callback && RAILS_GTE_5_1
52
+ if @in_after_callback
55
53
  @record.saved_change_to_attribute?(attr_name.to_s)
56
54
  else
57
55
  @record.attribute_changed?(attr_name.to_s)
@@ -60,30 +58,14 @@ module PaperTrail
60
58
 
61
59
  # @api private
62
60
  def nonskipped_attributes_before_change(is_touch)
63
- cache_changed_attributes do
64
- record_attributes = @record.attributes.except(*@record.paper_trail_options[:skip])
65
-
66
- record_attributes.each_key do |k|
67
- if @record.class.column_names.include?(k)
68
- record_attributes[k] = attribute_in_previous_version(k, is_touch)
69
- end
61
+ record_attributes = @record.attributes.except(*@record.paper_trail_options[:skip])
62
+ record_attributes.each_key do |k|
63
+ if @record.class.column_names.include?(k)
64
+ record_attributes[k] = attribute_in_previous_version(k, is_touch)
70
65
  end
71
66
  end
72
67
  end
73
68
 
74
- # Rails 5.1 changed the API of `ActiveRecord::Dirty`.
75
- # @api private
76
- def cache_changed_attributes
77
- if RAILS_GTE_5_1
78
- # Everything works fine as it is
79
- yield
80
- else
81
- # Any particular call to `changed_attributes` produces the huge memory allocation.
82
- # Lets use the generic AR workaround for that.
83
- @record.send(:cache_changed_attributes) { yield }
84
- end
85
- end
86
-
87
69
  # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
88
70
  # https://github.com/paper-trail-gem/paper_trail/pull/899
89
71
  #
@@ -91,23 +73,19 @@ module PaperTrail
91
73
  #
92
74
  # @api private
93
75
  def attribute_in_previous_version(attr_name, is_touch)
94
- if RAILS_GTE_5_1
95
- if @in_after_callback && !is_touch
96
- # For most events, we want the original value of the attribute, before
97
- # the last save.
98
- @record.attribute_before_last_save(attr_name.to_s)
99
- else
100
- # We are either performing a `record_destroy` or a
101
- # `record_update(is_touch: true)`.
102
- @record.attribute_in_database(attr_name.to_s)
103
- end
76
+ if @in_after_callback && !is_touch
77
+ # For most events, we want the original value of the attribute, before
78
+ # the last save.
79
+ @record.attribute_before_last_save(attr_name.to_s)
104
80
  else
105
- @record.attribute_was(attr_name.to_s)
81
+ # We are either performing a `record_destroy` or a
82
+ # `record_update(is_touch: true)`.
83
+ @record.attribute_in_database(attr_name.to_s)
106
84
  end
107
85
  end
108
86
 
109
87
  # @api private
110
- def changed_and_not_ignored
88
+ def calculated_ignored_array
111
89
  ignore = @record.paper_trail_options[:ignore].dup
112
90
  # Remove Hash arguments and then evaluate whether the attributes (the
113
91
  # keys of the hash) should also get pushed into the collection.
@@ -117,8 +95,12 @@ module PaperTrail
117
95
  ignore << attr if condition.respond_to?(:call) && condition.call(@record)
118
96
  }
119
97
  end
98
+ end
99
+
100
+ # @api private
101
+ def changed_and_not_ignored
120
102
  skip = @record.paper_trail_options[:skip]
121
- (changed_in_latest_version - ignore) - skip
103
+ (changed_in_latest_version - calculated_ignored_array) - skip
122
104
  end
123
105
 
124
106
  # @api private
@@ -127,19 +109,11 @@ module PaperTrail
127
109
  @changed_in_latest_version ||= changes_in_latest_version.keys
128
110
  end
129
111
 
130
- # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
131
- # https://github.com/paper-trail-gem/paper_trail/pull/899
112
+ # Memoized to reduce memory usage
132
113
  #
133
114
  # @api private
134
115
  def changes_in_latest_version
135
- # Memoized to reduce memory usage
136
- @changes_in_latest_version ||= begin
137
- if @in_after_callback && RAILS_GTE_5_1
138
- @record.saved_changes
139
- else
140
- @record.changes
141
- end
142
- end
116
+ @changes_in_latest_version ||= load_changes_in_latest_version
143
117
  end
144
118
 
145
119
  # An attributed is "ignored" if it is listed in the `:ignore` option
@@ -148,10 +122,22 @@ module PaperTrail
148
122
  #
149
123
  # @api private
150
124
  def ignored_attr_has_changed?
151
- ignored = @record.paper_trail_options[:ignore] + @record.paper_trail_options[:skip]
125
+ ignored = calculated_ignored_array + @record.paper_trail_options[:skip]
152
126
  ignored.any? && (changed_in_latest_version & ignored).any?
153
127
  end
154
128
 
129
+ # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
130
+ # https://github.com/paper-trail-gem/paper_trail/pull/899
131
+ #
132
+ # @api private
133
+ def load_changes_in_latest_version
134
+ if @in_after_callback
135
+ @record.saved_changes
136
+ else
137
+ @record.changes
138
+ end
139
+ end
140
+
155
141
  # PT 10 has a new optional column, `item_subtype`
156
142
  #
157
143
  # @api private
@@ -247,8 +233,7 @@ module PaperTrail
247
233
  # @api private
248
234
  def prepare_object_changes(changes)
249
235
  changes = serialize_object_changes(changes)
250
- changes = recordable_object_changes(changes)
251
- changes
236
+ recordable_object_changes(changes)
252
237
  end
253
238
 
254
239
  # Returns an object which can be assigned to the `object_changes`
@@ -260,7 +245,7 @@ module PaperTrail
260
245
  # @api private
261
246
  # @param changes HashWithIndifferentAccess
262
247
  def recordable_object_changes(changes)
263
- if PaperTrail.config.object_changes_adapter&.respond_to?(:diff)
248
+ if PaperTrail.config.object_changes_adapter.respond_to?(:diff)
264
249
  # We'd like to avoid the `to_hash` here, because it increases memory
265
250
  # usage, but that would be a breaking change because
266
251
  # `object_changes_adapter` expects a plain `Hash`, not a