paper_trail 10.3.1 → 12.1.0

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