paper_trail 10.3.1 → 12.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (29) 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/paper_trail.rb +12 -38
  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/events/base.rb +22 -41
  12. data/lib/paper_trail/events/destroy.rb +1 -1
  13. data/lib/paper_trail/frameworks/active_record.rb +9 -2
  14. data/lib/paper_trail/frameworks/rails.rb +1 -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/has_paper_trail.rb +1 -1
  18. data/lib/paper_trail/model_config.rb +3 -7
  19. data/lib/paper_trail/queries/versions/where_object_changes.rb +1 -1
  20. data/lib/paper_trail/queries/versions/where_object_changes_from.rb +65 -0
  21. data/lib/paper_trail/record_trail.rb +2 -4
  22. data/lib/paper_trail/reifier.rb +17 -19
  23. data/lib/paper_trail/serializers/json.rb +8 -0
  24. data/lib/paper_trail/serializers/yaml.rb +8 -0
  25. data/lib/paper_trail/type_serializers/postgres_array_serializer.rb +1 -14
  26. data/lib/paper_trail/version_concern.rb +30 -26
  27. data/lib/paper_trail/version_number.rb +3 -3
  28. metadata +76 -39
  29. 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: f2ce7f019e3413c485beff4eefa957f759b084fe0d29b0d9f3451b6ba3e4befe
4
+ data.tar.gz: 3f73870b42c582f683936eee21fe288b17d915d1bfbff5327ac50dd8e9766732
5
5
  SHA512:
6
- metadata.gz: b39e89605a2b03889976070f0871f9dcd07544b97b6e0105b78035b5fb1ec030c7d5438dc0fe9fb16cccc8e3624cd920d37bd73066f8a4f3892bf356fa088e85
7
- data.tar.gz: 56567a718ea4e605d32ffc2b71835b188f6cefab8dd7111b7abcdfc6d9e5b843e8468312ed4dfbc87f74607c1cd4a197ae2c11a9cb44ee2dd16a37a11303e6ad
6
+ metadata.gz: 6134547d0c2c69f94962aea4f868aec8d456bbb0afc4588b9b07971074a8d50476f29a59acb41d3e058dea581678d0c7129d8e92a04c370fa2016dbd6bf544b7
7
+ data.tar.gz: a6ca4e41cf2f6bce774894a9927dc0b3e4abbcbbdbb39fbf4671d029398f26999ce8927143f1f14279a6ad8aa70194025f5b6e653780560a0cc4870dce9b754e
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
data/lib/paper_trail.rb CHANGED
@@ -8,34 +8,17 @@
8
8
  # can revisit this decision.
9
9
  require "active_support/all"
10
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
-
17
- require "request_store"
18
11
  require "paper_trail/cleaner"
19
12
  require "paper_trail/compatibility"
20
13
  require "paper_trail/config"
21
- require "paper_trail/has_paper_trail"
22
14
  require "paper_trail/record_history"
23
- require "paper_trail/reifier"
24
15
  require "paper_trail/request"
25
- require "paper_trail/version_concern"
26
16
  require "paper_trail/version_number"
27
17
  require "paper_trail/serializers/json"
28
- require "paper_trail/serializers/yaml"
29
18
 
30
19
  # An ActiveRecord extension that tracks changes to your models, for auditing or
31
20
  # versioning.
32
21
  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
22
  E_TIMESTAMP_FIELD_CONFIG = <<-EOS.squish.freeze
40
23
  PaperTrail.timestamp_field= has been removed, without replacement. It is no
41
24
  longer configurable. The timestamp column in the versions table must now be
@@ -126,27 +109,18 @@ module PaperTrail
126
109
  end
127
110
  end
128
111
 
129
- # We use the `on_load` "hook" instead of `ActiveRecord::Base.include` because we
130
- # don't want to cause all of AR to be autloaded yet. See
131
- # https://guides.rubyonrails.org/engines.html#what-are-on-load-hooks-questionmark
132
- # to learn more about `on_load`.
133
- ActiveSupport.on_load(:active_record) do
134
- include PaperTrail::Model
135
- end
136
-
137
- # Require frameworks
138
- if defined?(::Rails)
139
- # Rails module is sometimes defined by gems like rails-html-sanitizer
140
- # so we check for presence of Rails.application.
141
- if defined?(::Rails.application)
142
- require "paper_trail/frameworks/rails"
143
- else
144
- ::Kernel.warn(::PaperTrail::E_RAILS_NOT_LOADED)
145
- end
112
+ # PT is built on ActiveRecord, but does not require Rails. If Rails is defined,
113
+ # our Railtie makes sure not to load the AR-dependent parts of PT until AR is
114
+ # ready. A typical Rails `application.rb` has:
115
+ #
116
+ # ```
117
+ # require 'rails/all' # Defines `Rails`
118
+ # Bundler.require(*Rails.groups) # require 'paper_trail' (this file)
119
+ # ```
120
+ #
121
+ # Non-rails applications should take similar care to load AR before PT.
122
+ if defined?(Rails)
123
+ require "paper_trail/frameworks/rails"
146
124
  else
147
125
  require "paper_trail/frameworks/active_record"
148
126
  end
149
-
150
- if defined?(::ActiveRecord)
151
- ::PaperTrail::Compatibility.check_activerecord(::ActiveRecord.gem_version)
152
- end
@@ -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 = "< 6.2" # 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
@@ -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
@@ -134,7 +116,7 @@ module PaperTrail
134
116
  def changes_in_latest_version
135
117
  # Memoized to reduce memory usage
136
118
  @changes_in_latest_version ||= begin
137
- if @in_after_callback && RAILS_GTE_5_1
119
+ if @in_after_callback
138
120
  @record.saved_changes
139
121
  else
140
122
  @record.changes
@@ -148,7 +130,7 @@ module PaperTrail
148
130
  #
149
131
  # @api private
150
132
  def ignored_attr_has_changed?
151
- ignored = @record.paper_trail_options[:ignore] + @record.paper_trail_options[:skip]
133
+ ignored = calculated_ignored_array + @record.paper_trail_options[:skip]
152
134
  ignored.any? && (changed_in_latest_version & ignored).any?
153
135
  end
154
136
 
@@ -247,8 +229,7 @@ module PaperTrail
247
229
  # @api private
248
230
  def prepare_object_changes(changes)
249
231
  changes = serialize_object_changes(changes)
250
- changes = recordable_object_changes(changes)
251
- changes
232
+ recordable_object_changes(changes)
252
233
  end
253
234
 
254
235
  # Returns an object which can be assigned to the `object_changes`
@@ -260,7 +241,7 @@ module PaperTrail
260
241
  # @api private
261
242
  # @param changes HashWithIndifferentAccess
262
243
  def recordable_object_changes(changes)
263
- if PaperTrail.config.object_changes_adapter&.respond_to?(:diff)
244
+ if PaperTrail.config.object_changes_adapter.respond_to?(:diff)
264
245
  # We'd like to avoid the `to_hash` here, because it increases memory
265
246
  # usage, but that would be a breaking change because
266
247
  # `object_changes_adapter` expects a plain `Hash`, not a