paper_trail 10.2.0 → 11.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 9a180f82e731345bd4b9f46971ee6ca61753f82e
4
- data.tar.gz: c32cde31e4da02b078d13c8c2667a6b0c49c4110
2
+ SHA256:
3
+ metadata.gz: c312ab701b8ab7b26df37957b03b9f01b6ce6cbe0e5e25d4c478258f5bec1d6f
4
+ data.tar.gz: 62e94f6c2fa657d4c24f0fe6ecdf5abf3c8ae785e7b25f88611fc18e6d4324a7
5
5
  SHA512:
6
- metadata.gz: bd84d2586c73b9f08e44fabb1298a6e530ecf25953da0a05a41a925870720e6c05eb74474321816b51dd24cceb1d14b0658f685ac0c68a91de3ff6e7c2e0525b
7
- data.tar.gz: fb8e6814903909666d842d43b68b5ef97e5c207a8193d34a5d199f8405fb83565c87ba86925c58148720debde25bdd442f712ef75b9acaf8b3d86a6c71978191
6
+ metadata.gz: b59d91302dde736a2476240eadb9306938db2521594cf55de792c9678324762c4a1ffedfa5a53c3fc0fd9e37a87bc1b6ea4e74e68535b80fe70342d48a221532
7
+ data.tar.gz: b4aba2c107556fd6fbf758d9373f147c9b9bb0d1e6059bb3e2ad28b77906e980c559bd3c888fe9734665182cbe294bf470e77527a5536e202b8f32a5b7993516
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+ gemspec
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
@@ -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
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
@@ -16,6 +16,7 @@ require "active_record"
16
16
 
17
17
  require "request_store"
18
18
  require "paper_trail/cleaner"
19
+ require "paper_trail/compatibility"
19
20
  require "paper_trail/config"
20
21
  require "paper_trail/has_paper_trail"
21
22
  require "paper_trail/record_history"
@@ -145,3 +146,7 @@ if defined?(::Rails)
145
146
  else
146
147
  require "paper_trail/frameworks/active_record"
147
148
  end
149
+
150
+ if defined?(::ActiveRecord)
151
+ ::PaperTrail::Compatibility.check_activerecord(::ActiveRecord.gem_version)
152
+ end
@@ -32,50 +32,18 @@ module PaperTrail
32
32
  end
33
33
  end
34
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
-
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
49
- end
35
+ # Uses AR 5's `serialize` and `deserialize`.
36
+ class CastAttributeSerializer
37
+ def serialize(attr, val)
38
+ AttributeSerializerFactory.for(@klass, attr).serialize(val)
50
39
  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
40
 
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
41
+ def deserialize(attr, val)
42
+ if defined_enums[attr] && val.is_a?(::String)
43
+ # Because PT 4 used to save the string version of enums to `object_changes`
44
+ val
45
+ else
46
+ AttributeSerializerFactory.for(@klass, attr).deserialize(val)
79
47
  end
80
48
  end
81
49
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PaperTrail
4
+ # Rails does not follow SemVer, makes breaking changes in minor versions.
5
+ # Breaking changes are expected, and are generally good for the rails
6
+ # ecosystem. However, they often require dozens of hours to fix, even with the
7
+ # [help of experts](https://github.com/paper-trail-gem/paper_trail/pull/899).
8
+ #
9
+ # It is not safe to assume that a new version of rails will be compatible with
10
+ # PaperTrail. PT is only compatible with the versions of rails that it is
11
+ # tested against. See `.travis.yml`.
12
+ #
13
+ # However, as of
14
+ # [#1213](https://github.com/paper-trail-gem/paper_trail/pull/1213) our
15
+ # gemspec allows installation with newer, incompatible rails versions. We hope
16
+ # this will make it easier for contributors to work on compatibility with
17
+ # newer rails versions. Most PT users should avoid incompatible rails
18
+ # versions.
19
+ module Compatibility
20
+ ACTIVERECORD_GTE = ">= 5.2" # enforced in gemspec
21
+ ACTIVERECORD_LT = "< 6.2" # not enforced in gemspec
22
+
23
+ E_INCOMPATIBLE_AR = <<-EOS
24
+ PaperTrail %s is not compatible with ActiveRecord %s. We allow PT
25
+ contributors to install incompatible versions of ActiveRecord, and this
26
+ warning can be silenced with an environment variable, but this is a bad
27
+ idea for normal use. Please install a compatible version of ActiveRecord
28
+ instead (%s). Please see the discussion in paper_trail/compatibility.rb
29
+ for details.
30
+ EOS
31
+
32
+ # Normal users need a warning if they accidentally install an incompatible
33
+ # version of ActiveRecord. Contributors can silence this warning with an
34
+ # environment variable.
35
+ def self.check_activerecord(ar_version)
36
+ raise ::TypeError unless ar_version.instance_of?(::Gem::Version)
37
+ return if ::ENV["PT_SILENCE_AR_COMPAT_WARNING"].present?
38
+ req = ::Gem::Requirement.new([ACTIVERECORD_GTE, ACTIVERECORD_LT])
39
+ unless req.satisfied_by?(ar_version)
40
+ ::Kernel.warn(
41
+ format(
42
+ E_INCOMPATIBLE_AR,
43
+ ::PaperTrail.gem_version,
44
+ ar_version,
45
+ req
46
+ )
47
+ )
48
+ end
49
+ end
50
+ end
51
+ end
@@ -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
@@ -59,14 +59,29 @@ module PaperTrail
59
59
  end
60
60
 
61
61
  # @api private
62
- def attributes_before_change(is_touch)
63
- Hash[@record.attributes.map do |k, v|
64
- if @record.class.column_names.include?(k)
65
- [k, attribute_in_previous_version(k, is_touch)]
66
- else
67
- [k, v]
62
+ 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
68
70
  end
69
- end]
71
+ end
72
+ end
73
+
74
+ # Rails 5.1 changed the API of `ActiveRecord::Dirty`.
75
+ # @api private
76
+ def cache_changed_attributes(&block)
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, &block)
84
+ end
70
85
  end
71
86
 
72
87
  # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
@@ -92,7 +107,7 @@ module PaperTrail
92
107
  end
93
108
 
94
109
  # @api private
95
- def changed_and_not_ignored
110
+ def calculated_ignored_array
96
111
  ignore = @record.paper_trail_options[:ignore].dup
97
112
  # Remove Hash arguments and then evaluate whether the attributes (the
98
113
  # keys of the hash) should also get pushed into the collection.
@@ -102,13 +117,18 @@ module PaperTrail
102
117
  ignore << attr if condition.respond_to?(:call) && condition.call(@record)
103
118
  }
104
119
  end
120
+ end
121
+
122
+ # @api private
123
+ def changed_and_not_ignored
105
124
  skip = @record.paper_trail_options[:skip]
106
- (changed_in_latest_version - ignore) - skip
125
+ (changed_in_latest_version - calculated_ignored_array) - skip
107
126
  end
108
127
 
109
128
  # @api private
110
129
  def changed_in_latest_version
111
- changes_in_latest_version.keys
130
+ # Memoized to reduce memory usage
131
+ @changed_in_latest_version ||= changes_in_latest_version.keys
112
132
  end
113
133
 
114
134
  # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
@@ -116,10 +136,13 @@ module PaperTrail
116
136
  #
117
137
  # @api private
118
138
  def changes_in_latest_version
119
- if @in_after_callback && RAILS_GTE_5_1
120
- @record.saved_changes
121
- else
122
- @record.changes
139
+ # Memoized to reduce memory usage
140
+ @changes_in_latest_version ||= begin
141
+ if @in_after_callback && RAILS_GTE_5_1
142
+ @record.saved_changes
143
+ else
144
+ @record.changes
145
+ end
123
146
  end
124
147
  end
125
148
 
@@ -129,7 +152,7 @@ module PaperTrail
129
152
  #
130
153
  # @api private
131
154
  def ignored_attr_has_changed?
132
- ignored = @record.paper_trail_options[:ignore] + @record.paper_trail_options[:skip]
155
+ ignored = calculated_ignored_array + @record.paper_trail_options[:skip]
133
156
  ignored.any? && (changed_in_latest_version & ignored).any?
134
157
  end
135
158
 
@@ -200,16 +223,19 @@ module PaperTrail
200
223
 
201
224
  # @api private
202
225
  def notably_changed
203
- only = @record.paper_trail_options[:only].dup
204
- # Remove Hash arguments and then evaluate whether the attributes (the
205
- # keys of the hash) should also get pushed into the collection.
206
- only.delete_if do |obj|
207
- obj.is_a?(Hash) &&
208
- obj.each { |attr, condition|
209
- only << attr if condition.respond_to?(:call) && condition.call(@record)
210
- }
226
+ # Memoized to reduce memory usage
227
+ @notably_changed ||= begin
228
+ only = @record.paper_trail_options[:only].dup
229
+ # Remove Hash arguments and then evaluate whether the attributes (the
230
+ # keys of the hash) should also get pushed into the collection.
231
+ only.delete_if do |obj|
232
+ obj.is_a?(Hash) &&
233
+ obj.each { |attr, condition|
234
+ only << attr if condition.respond_to?(:call) && condition.call(@record)
235
+ }
236
+ end
237
+ only.empty? ? changed_and_not_ignored : (changed_and_not_ignored & only)
211
238
  end
212
- only.empty? ? changed_and_not_ignored : (changed_and_not_ignored & only)
213
239
  end
214
240
 
215
241
  # Returns hash of attributes (with appropriate attributes serialized),
@@ -217,8 +243,7 @@ module PaperTrail
217
243
  #
218
244
  # @api private
219
245
  def object_attrs_for_paper_trail(is_touch)
220
- attrs = attributes_before_change(is_touch).
221
- except(*@record.paper_trail_options[:skip])
246
+ attrs = nonskipped_attributes_before_change(is_touch)
222
247
  AttributeSerializers::ObjectAttribute.new(@record.class).serialize(attrs)
223
248
  attrs
224
249
  end
@@ -226,8 +251,7 @@ module PaperTrail
226
251
  # @api private
227
252
  def prepare_object_changes(changes)
228
253
  changes = serialize_object_changes(changes)
229
- changes = recordable_object_changes(changes)
230
- changes
254
+ recordable_object_changes(changes)
231
255
  end
232
256
 
233
257
  # Returns an object which can be assigned to the `object_changes`
@@ -237,9 +261,14 @@ module PaperTrail
237
261
  # serialization here, using `PaperTrail.serializer`.
238
262
  #
239
263
  # @api private
264
+ # @param changes HashWithIndifferentAccess
240
265
  def recordable_object_changes(changes)
241
266
  if PaperTrail.config.object_changes_adapter&.respond_to?(:diff)
242
- changes = PaperTrail.config.object_changes_adapter.diff(changes)
267
+ # We'd like to avoid the `to_hash` here, because it increases memory
268
+ # usage, but that would be a breaking change because
269
+ # `object_changes_adapter` expects a plain `Hash`, not a
270
+ # `HashWithIndifferentAccess`.
271
+ changes = PaperTrail.config.object_changes_adapter.diff(changes.to_hash)
243
272
  end
244
273
 
245
274
  if @record.class.paper_trail.version_class.object_changes_col_is_json?
@@ -284,7 +313,10 @@ module PaperTrail
284
313
  AttributeSerializers::ObjectChangesAttribute.
285
314
  new(@record.class).
286
315
  serialize(changes)
287
- changes.to_hash
316
+
317
+ # We'd like to convert this `HashWithIndifferentAccess` to a plain
318
+ # `Hash`, but we don't, to save memory.
319
+ changes
288
320
  end
289
321
  end
290
322
  end
@@ -13,6 +13,7 @@ module PaperTrail
13
13
  # @api private
14
14
  def data
15
15
  data = {
16
+ item: @record,
16
17
  event: @record.paper_trail_event || "create",
17
18
  whodunnit: PaperTrail.request.whodunnit
18
19
  }
@@ -25,6 +25,7 @@ module PaperTrail
25
25
  # @api private
26
26
  def data
27
27
  data = {
28
+ item: @record,
28
29
  event: @record.paper_trail_event || "update",
29
30
  whodunnit: PaperTrail.request.whodunnit
30
31
  }
@@ -25,9 +25,7 @@ module PaperTrail
25
25
  # @api public
26
26
  def user_for_paper_trail
27
27
  return unless defined?(current_user)
28
- ActiveSupport::VERSION::MAJOR >= 4 ? current_user.try!(:id) : current_user.try(:id)
29
- rescue NoMethodError
30
- current_user
28
+ current_user.try(:id) || current_user
31
29
  end
32
30
 
33
31
  # Returns any information about the controller or request that you
@@ -12,7 +12,7 @@ module PaperTrail
12
12
  # `.paper_trail` and `#paper_trail`.
13
13
  module Model
14
14
  def self.included(base)
15
- base.send :extend, ClassMethods
15
+ base.extend ClassMethods
16
16
  end
17
17
 
18
18
  # :nodoc:
@@ -18,6 +18,11 @@ module PaperTrail
18
18
  `abstract_class`. This is fine, but all application models must be
19
19
  configured to use concrete (not abstract) version models.
20
20
  STR
21
+ E_MODEL_LIMIT_REQUIRES_ITEM_SUBTYPE = <<~STR.squish.freeze
22
+ To use PaperTrail's per-model limit in your %s model, you must have an
23
+ item_subtype column in your versions table. See documentation sections
24
+ 2.e.1 Per-model limit, and 4.b.1 The optional item_subtype column.
25
+ STR
21
26
  DPR_PASSING_ASSOC_NAME_DIRECTLY_TO_VERSIONS_OPTION = <<~STR.squish
22
27
  Passing versions association name as `has_paper_trail versions: %{versions_name}`
23
28
  is deprecated. Use `has_paper_trail versions: {name: %{versions_name}}` instead.
@@ -112,6 +117,7 @@ module PaperTrail
112
117
  @model_class.send :include, ::PaperTrail::Model::InstanceMethods
113
118
  setup_options(options)
114
119
  setup_associations(options)
120
+ check_presence_of_item_subtype_column(options)
115
121
  @model_class.after_rollback { paper_trail.clear_rolled_back_versions }
116
122
  setup_callbacks_from_options options[:on]
117
123
  end
@@ -122,10 +128,6 @@ module PaperTrail
122
128
 
123
129
  private
124
130
 
125
- def active_record_gem_version
126
- Gem::Version.new(ActiveRecord::VERSION::STRING)
127
- end
128
-
129
131
  # Raises an error if the provided class is an `abstract_class`.
130
132
  # @api private
131
133
  def assert_concrete_activerecord_class(class_name)
@@ -135,8 +137,17 @@ module PaperTrail
135
137
  end
136
138
 
137
139
  def cannot_record_after_destroy?
138
- Gem::Version.new(ActiveRecord::VERSION::STRING).release >= Gem::Version.new("5") &&
139
- ::ActiveRecord::Base.belongs_to_required_by_default
140
+ ::ActiveRecord::Base.belongs_to_required_by_default
141
+ end
142
+
143
+ # Some options require the presence of the `item_subtype` column. Currently
144
+ # only `limit`, but in the future there may be others.
145
+ #
146
+ # @api private
147
+ def check_presence_of_item_subtype_column(options)
148
+ return unless options.key?(:limit)
149
+ return if version_class.item_subtype_column_present?
150
+ raise format(E_MODEL_LIMIT_REQUIRES_ITEM_SUBTYPE, @model_class.name)
140
151
  end
141
152
 
142
153
  def check_version_class_name(options)
@@ -67,14 +67,14 @@ module PaperTrail
67
67
 
68
68
  def record_create
69
69
  return unless enabled?
70
- event = Events::Create.new(@record, true)
71
70
 
72
- # Merge data from `Event` with data from PT-AT. We no longer use
73
- # `data_for_create` but PT-AT still does.
74
- data = event.data.merge(data_for_create)
75
-
76
- versions_assoc = @record.send(@record.class.versions_association_name)
77
- versions_assoc.create!(data)
71
+ build_version_on_create(in_after_callback: true).tap do |version|
72
+ version.save!
73
+ # Because the version object was created using version_class.new instead
74
+ # of versions_assoc.build?, the association cache is unaware. So, we
75
+ # invalidate the `versions` association cache with `reset`.
76
+ versions.reset
77
+ end
78
78
  end
79
79
 
80
80
  # PT-AT extends this method to add its transaction id.
@@ -119,19 +119,22 @@ module PaperTrail
119
119
  # paper_trail-association_tracking
120
120
  def record_update(force:, in_after_callback:, is_touch:)
121
121
  return unless enabled?
122
- event = Events::Update.new(@record, in_after_callback, is_touch, nil)
123
- return unless force || event.changed_notably?
124
122
 
125
- # Merge data from `Event` with data from PT-AT. We no longer use
126
- # `data_for_update` but PT-AT still does.
127
- data = event.data.merge(data_for_update)
123
+ version = build_version_on_update(
124
+ force: force,
125
+ in_after_callback: in_after_callback,
126
+ is_touch: is_touch
127
+ )
128
+ return unless version
128
129
 
129
- versions_assoc = @record.send(@record.class.versions_association_name)
130
- version = versions_assoc.create(data)
131
- if version.errors.any?
132
- log_version_errors(version, :update)
133
- else
130
+ if version.save
131
+ # Because the version object was created using version_class.new instead
132
+ # of versions_assoc.build?, the association cache is unaware. So, we
133
+ # invalidate the `versions` association cache with `reset`.
134
+ versions.reset
134
135
  version
136
+ else
137
+ log_version_errors(version, :update)
135
138
  end
136
139
  end
137
140
 
@@ -250,6 +253,35 @@ module PaperTrail
250
253
  @record.send(@record.class.versions_association_name).reset
251
254
  end
252
255
 
256
+ # @api private
257
+ def build_version_on_create(in_after_callback:)
258
+ event = Events::Create.new(@record, in_after_callback)
259
+
260
+ # Merge data from `Event` with data from PT-AT. We no longer use
261
+ # `data_for_create` but PT-AT still does.
262
+ data = event.data.merge!(data_for_create)
263
+
264
+ # Pure `version_class.new` reduces memory usage compared to `versions_assoc.build`
265
+ @record.class.paper_trail.version_class.new(data)
266
+ end
267
+
268
+ # @api private
269
+ def build_version_on_update(force:, in_after_callback:, is_touch:)
270
+ event = Events::Update.new(@record, in_after_callback, is_touch, nil)
271
+ return unless force || event.changed_notably?
272
+
273
+ # Merge data from `Event` with data from PT-AT. We no longer use
274
+ # `data_for_update` but PT-AT still does. To save memory, we use `merge!`
275
+ # instead of `merge`.
276
+ data = event.data.merge!(data_for_update)
277
+
278
+ # Using `version_class.new` reduces memory usage compared to
279
+ # `versions_assoc.build`. It's a trade-off though. We have to clear
280
+ # the association cache (see `versions.reset`) and that could cause an
281
+ # additional query in certain applications.
282
+ @record.class.paper_trail.version_class.new(data)
283
+ end
284
+
253
285
  def log_version_errors(version, action)
254
286
  version.logger&.warn(
255
287
  "Unable to create version for #{action} of #{@record.class.name}" \
@@ -52,23 +52,23 @@ module PaperTrail
52
52
  # not the actual subclass. If `type` is present but empty, the class is
53
53
  # the base class.
54
54
  def init_model(attrs, options, version)
55
- if options[:dup] != true && version.item
56
- model = version.item
57
- if options[:unversioned_attributes] == :nil
58
- init_unversioned_attrs(attrs, model)
59
- end
60
- else
61
- klass = version_reification_class(version, attrs)
62
- # The `dup` option always returns a new object, otherwise we should
63
- # attempt to look for the item outside of default scope(s).
64
- find_cond = { klass.primary_key => version.item_id }
65
- if options[:dup] || (item_found = klass.unscoped.where(find_cond).first).nil?
66
- model = klass.new
67
- elsif options[:unversioned_attributes] == :nil
68
- model = item_found
69
- init_unversioned_attrs(attrs, model)
70
- end
55
+ klass = version_reification_class(version, attrs)
56
+
57
+ # The `dup` option and destroyed version always returns a new object,
58
+ # otherwise we should attempt to load item or to look for the item
59
+ # outside of default scope(s).
60
+ model = if options[:dup] == true || version.event == "destroy"
61
+ klass.new
62
+ else
63
+ find_cond = { klass.primary_key => version.item_id }
64
+
65
+ version.item || klass.unscoped.where(find_cond).first || klass.new
66
+ end
67
+
68
+ if options[:unversioned_attributes] == :nil && !model.new_record?
69
+ init_unversioned_attrs(attrs, model)
71
70
  end
71
+
72
72
  model
73
73
  end
74
74
 
@@ -88,9 +88,7 @@ module PaperTrail
88
88
  #
89
89
  # @api private
90
90
  def reify_attribute(k, v, model, version)
91
- enums = model.class.respond_to?(:defined_enums) ? model.class.defined_enums : {}
92
- is_enum_without_type_caster = ::ActiveRecord::VERSION::MAJOR < 5 && enums.key?(k)
93
- if model.has_attribute?(k) && !is_enum_without_type_caster
91
+ if model.has_attribute?(k)
94
92
  model[k.to_sym] = v
95
93
  elsif model.respond_to?("#{k}=")
96
94
  model.send("#{k}=", v)
@@ -120,6 +118,7 @@ module PaperTrail
120
118
  # this method returns the constant `Animal`. You can see this particular
121
119
  # example in action in `spec/models/animal_spec.rb`.
122
120
  #
121
+ # TODO: Duplication: similar `constantize` in VersionConcern#version_limit
123
122
  def version_reification_class(version, attrs)
124
123
  inheritance_column_name = version.item_type.constantize.inheritance_column
125
124
  inher_col_value = attrs[inheritance_column_name]
@@ -12,7 +12,12 @@ module PaperTrail
12
12
  ::YAML.load string
13
13
  end
14
14
 
15
+ # @param object (Hash | HashWithIndifferentAccess) - Coming from
16
+ # `recordable_object` `object` will be a plain `Hash`. However, due to
17
+ # recent [memory optimizations](https://git.io/fjeYv), when coming from
18
+ # `recordable_object_changes`, it will be a `HashWithIndifferentAccess`.
15
19
  def dump(object)
20
+ object = object.to_hash if object.is_a?(HashWithIndifferentAccess)
16
21
  ::YAML.dump object
17
22
  end
18
23
 
@@ -25,6 +25,10 @@ module PaperTrail
25
25
 
26
26
  # :nodoc:
27
27
  module ClassMethods
28
+ def item_subtype_column_present?
29
+ column_names.include?("item_subtype")
30
+ end
31
+
28
32
  def with_item_keys(item_type, item_id)
29
33
  where item_type: item_type, item_id: item_id
30
34
  end
@@ -141,6 +145,7 @@ module PaperTrail
141
145
  # Default: false.
142
146
  # @return `ActiveRecord::Relation`
143
147
  # @api public
148
+ # rubocop:disable Style/OptionalBooleanParameter
144
149
  def preceding(obj, timestamp_arg = false)
145
150
  if timestamp_arg != true && primary_key_is_int?
146
151
  preceding_by_id(obj)
@@ -148,6 +153,7 @@ module PaperTrail
148
153
  preceding_by_timestamp(obj)
149
154
  end
150
155
  end
156
+ # rubocop:enable Style/OptionalBooleanParameter
151
157
 
152
158
  # Returns versions after `obj`.
153
159
  #
@@ -156,6 +162,7 @@ module PaperTrail
156
162
  # Default: false.
157
163
  # @return `ActiveRecord::Relation`
158
164
  # @api public
165
+ # rubocop:disable Style/OptionalBooleanParameter
159
166
  def subsequent(obj, timestamp_arg = false)
160
167
  if timestamp_arg != true && primary_key_is_int?
161
168
  subsequent_by_id(obj)
@@ -163,6 +170,7 @@ module PaperTrail
163
170
  subsequent_by_timestamp(obj)
164
171
  end
165
172
  end
173
+ # rubocop:enable Style/OptionalBooleanParameter
166
174
 
167
175
  private
168
176
 
@@ -201,18 +209,8 @@ module PaperTrail
201
209
 
202
210
  # Restore the item from this version.
203
211
  #
204
- # Optionally this can also restore all :has_one and :has_many (including
205
- # has_many :through) associations as they were "at the time", if they are
206
- # also being versioned by PaperTrail.
207
- #
208
212
  # Options:
209
213
  #
210
- # - :has_one
211
- # - `true` - Also reify has_one associations.
212
- # - `false - Default.
213
- # - :has_many
214
- # - `true` - Also reify has_many and has_many :through associations.
215
- # - `false` - Default.
216
214
  # - :mark_for_destruction
217
215
  # - `true` - Mark the has_one/has_many associations that did not exist in
218
216
  # the reified version for destruction, instead of removing them.
@@ -254,13 +252,6 @@ module PaperTrail
254
252
  end
255
253
  alias version_author terminator
256
254
 
257
- def sibling_versions(reload = false)
258
- if reload || !defined?(@sibling_versions) || @sibling_versions.nil?
259
- @sibling_versions = self.class.with_item_keys(item_type, item_id)
260
- end
261
- @sibling_versions
262
- end
263
-
264
255
  def next
265
256
  @next ||= sibling_versions.subsequent(self).first
266
257
  end
@@ -270,8 +261,9 @@ module PaperTrail
270
261
  end
271
262
 
272
263
  # Returns an integer representing the chronological position of the
273
- # version among its siblings (see `sibling_versions`). The "create" event,
274
- # for example, has an index of 0.
264
+ # version among its siblings. The "create" event, for example, has an index
265
+ # of 0.
266
+ #
275
267
  # @api public
276
268
  def index
277
269
  @index ||= RecordHistory.new(sibling_versions, self.class).index(self)
@@ -329,7 +321,7 @@ module PaperTrail
329
321
  # Enforces the `version_limit`, if set. Default: no limit.
330
322
  # @api private
331
323
  def enforce_version_limit!
332
- limit = PaperTrail.config.version_limit
324
+ limit = version_limit
333
325
  return unless limit.is_a? Numeric
334
326
  previous_versions = sibling_versions.not_creates.
335
327
  order(self.class.timestamp_sort_order("asc"))
@@ -337,5 +329,26 @@ module PaperTrail
337
329
  excess_versions = previous_versions - previous_versions.last(limit)
338
330
  excess_versions.map(&:destroy)
339
331
  end
332
+
333
+ # @api private
334
+ def sibling_versions
335
+ @sibling_versions ||= self.class.with_item_keys(item_type, item_id)
336
+ end
337
+
338
+ # See docs section 2.e. Limiting the Number of Versions Created.
339
+ # The version limit can be global or per-model.
340
+ #
341
+ # @api private
342
+ #
343
+ # TODO: Duplication: similar `constantize` in Reifier#version_reification_class
344
+ def version_limit
345
+ if self.class.item_subtype_column_present?
346
+ klass = (item_subtype || item_type).constantize
347
+ if klass&.paper_trail_options&.key?(:limit)
348
+ return klass.paper_trail_options[:limit]
349
+ end
350
+ end
351
+ PaperTrail.config.version_limit
352
+ end
340
353
  end
341
354
  end
@@ -7,8 +7,8 @@ module PaperTrail
7
7
  # because of this confusion, but it's not worth the breaking change.
8
8
  # People are encouraged to use `PaperTrail.gem_version` instead.
9
9
  module VERSION
10
- MAJOR = 10
11
- MINOR = 2
10
+ MAJOR = 11
11
+ MINOR = 1
12
12
  TINY = 0
13
13
 
14
14
  # Set PRE to nil unless it's a pre-release (beta, rc, etc.)
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ $LOAD_PATH.unshift File.expand_path("lib", __dir__)
4
+ require "paper_trail/compatibility"
5
+ require "paper_trail/version_number"
6
+
7
+ Gem::Specification.new do |s|
8
+ s.name = "paper_trail"
9
+ s.version = PaperTrail::VERSION::STRING
10
+ s.platform = Gem::Platform::RUBY
11
+ s.summary = "Track changes to your models."
12
+ s.description = <<-EOS
13
+ Track changes to your models, for auditing or versioning. See how a model looked
14
+ at any stage in its lifecycle, revert it to any version, or restore it after it
15
+ has been destroyed.
16
+ EOS
17
+ s.homepage = "https://github.com/paper-trail-gem/paper_trail"
18
+ s.authors = ["Andy Stewart", "Ben Atkins", "Jared Beck"]
19
+ s.email = "jared@jaredbeck.com"
20
+ s.license = "MIT"
21
+
22
+ s.files = `git ls-files -z`.split("\x0").select { |f|
23
+ f.match(%r{^(Gemfile|LICENSE|lib/|paper_trail.gemspec)})
24
+ }
25
+ s.executables = []
26
+ s.require_paths = ["lib"]
27
+
28
+ s.required_rubygems_version = ">= 1.3.6"
29
+
30
+ # Ruby 2.4 reaches EoL at the end of March of 2020
31
+ # https://www.ruby-lang.org/en/news/2019/10/02/ruby-2-4-9-released/
32
+ s.required_ruby_version = ">= 2.4.0"
33
+
34
+ # We no longer specify a maximum activerecord version.
35
+ # See discussion in paper_trail/compatibility.rb
36
+ s.add_dependency "activerecord", ::PaperTrail::Compatibility::ACTIVERECORD_GTE
37
+ s.add_dependency "request_store", "~> 1.1"
38
+
39
+ s.add_development_dependency "appraisal", "~> 2.2"
40
+ s.add_development_dependency "byebug", "~> 11.0"
41
+ s.add_development_dependency "ffaker", "~> 2.11"
42
+ s.add_development_dependency "generator_spec", "~> 0.9.4"
43
+ s.add_development_dependency "memory_profiler", "~> 0.9.14"
44
+
45
+ # For `spec/dummy_app`. Technically, we only need `actionpack` (as of 2020).
46
+ # However, that might change in the future, and the advantages of specifying a
47
+ # subset (e.g. actionpack only) are unclear.
48
+ s.add_development_dependency "rails", ::PaperTrail::Compatibility::ACTIVERECORD_GTE
49
+
50
+ s.add_development_dependency "rake", "~> 13.0"
51
+ s.add_development_dependency "rspec-rails", "~> 4.0"
52
+ s.add_development_dependency "rubocop", "~> 0.89.1"
53
+ s.add_development_dependency "rubocop-performance", "~> 1.7.1"
54
+ s.add_development_dependency "rubocop-rspec", "~> 1.42.0"
55
+
56
+ # ## Database Adapters
57
+ #
58
+ # The dependencies here must match the `gem` call at the top of their
59
+ # adapters, eg. `active_record/connection_adapters/mysql2_adapter.rb`,
60
+ # assuming said call is consistent for all versions of rails we test against
61
+ # (see `Appraisals`).
62
+ #
63
+ # Currently, all versions of rails we test against are consistent. In the past,
64
+ # when we tested against rails 4.2, we had to specify database adapters in
65
+ # `Appraisals`.
66
+ s.add_development_dependency "mysql2", "~> 0.5"
67
+ s.add_development_dependency "pg", ">= 0.18", "< 2.0"
68
+ s.add_development_dependency "sqlite3", "~> 1.4"
69
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paper_trail
3
3
  version: !ruby/object:Gem::Version
4
- version: 10.2.0
4
+ version: 11.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Stewart
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2019-01-31 00:00:00.000000000 Z
13
+ date: 2020-12-16 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -18,20 +18,14 @@ dependencies:
18
18
  requirements:
19
19
  - - ">="
20
20
  - !ruby/object:Gem::Version
21
- version: '4.2'
22
- - - "<"
23
- - !ruby/object:Gem::Version
24
- version: '6.1'
21
+ version: '5.2'
25
22
  type: :runtime
26
23
  prerelease: false
27
24
  version_requirements: !ruby/object:Gem::Requirement
28
25
  requirements:
29
26
  - - ">="
30
27
  - !ruby/object:Gem::Version
31
- version: '4.2'
32
- - - "<"
33
- - !ruby/object:Gem::Version
34
- version: '6.1'
28
+ version: '5.2'
35
29
  - !ruby/object:Gem::Dependency
36
30
  name: request_store
37
31
  requirement: !ruby/object:Gem::Requirement
@@ -66,28 +60,28 @@ dependencies:
66
60
  requirements:
67
61
  - - "~>"
68
62
  - !ruby/object:Gem::Version
69
- version: '10.0'
63
+ version: '11.0'
70
64
  type: :development
71
65
  prerelease: false
72
66
  version_requirements: !ruby/object:Gem::Requirement
73
67
  requirements:
74
68
  - - "~>"
75
69
  - !ruby/object:Gem::Version
76
- version: '10.0'
70
+ version: '11.0'
77
71
  - !ruby/object:Gem::Dependency
78
72
  name: ffaker
79
73
  requirement: !ruby/object:Gem::Requirement
80
74
  requirements:
81
75
  - - "~>"
82
76
  - !ruby/object:Gem::Version
83
- version: '2.8'
77
+ version: '2.11'
84
78
  type: :development
85
79
  prerelease: false
86
80
  version_requirements: !ruby/object:Gem::Requirement
87
81
  requirements:
88
82
  - - "~>"
89
83
  - !ruby/object:Gem::Version
90
- version: '2.8'
84
+ version: '2.11'
91
85
  - !ruby/object:Gem::Dependency
92
86
  name: generator_spec
93
87
  requirement: !ruby/object:Gem::Requirement
@@ -103,117 +97,151 @@ dependencies:
103
97
  - !ruby/object:Gem::Version
104
98
  version: 0.9.4
105
99
  - !ruby/object:Gem::Dependency
106
- name: mysql2
100
+ name: memory_profiler
107
101
  requirement: !ruby/object:Gem::Requirement
108
102
  requirements:
109
103
  - - "~>"
110
104
  - !ruby/object:Gem::Version
111
- version: 0.5.2
105
+ version: 0.9.14
112
106
  type: :development
113
107
  prerelease: false
114
108
  version_requirements: !ruby/object:Gem::Requirement
115
109
  requirements:
116
110
  - - "~>"
117
111
  - !ruby/object:Gem::Version
118
- version: 0.5.2
112
+ version: 0.9.14
119
113
  - !ruby/object:Gem::Dependency
120
- name: paper_trail-association_tracking
114
+ name: rails
115
+ requirement: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '5.2'
120
+ type: :development
121
+ prerelease: false
122
+ version_requirements: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '5.2'
127
+ - !ruby/object:Gem::Dependency
128
+ name: rake
121
129
  requirement: !ruby/object:Gem::Requirement
122
130
  requirements:
123
131
  - - "~>"
124
132
  - !ruby/object:Gem::Version
125
- version: 2.0.0
133
+ version: '13.0'
126
134
  type: :development
127
135
  prerelease: false
128
136
  version_requirements: !ruby/object:Gem::Requirement
129
137
  requirements:
130
138
  - - "~>"
131
139
  - !ruby/object:Gem::Version
132
- version: 2.0.0
140
+ version: '13.0'
133
141
  - !ruby/object:Gem::Dependency
134
- name: pg
142
+ name: rspec-rails
135
143
  requirement: !ruby/object:Gem::Requirement
136
144
  requirements:
137
145
  - - "~>"
138
146
  - !ruby/object:Gem::Version
139
- version: '1.0'
147
+ version: '4.0'
140
148
  type: :development
141
149
  prerelease: false
142
150
  version_requirements: !ruby/object:Gem::Requirement
143
151
  requirements:
144
152
  - - "~>"
145
153
  - !ruby/object:Gem::Version
146
- version: '1.0'
154
+ version: '4.0'
147
155
  - !ruby/object:Gem::Dependency
148
- name: rake
156
+ name: rubocop
149
157
  requirement: !ruby/object:Gem::Requirement
150
158
  requirements:
151
159
  - - "~>"
152
160
  - !ruby/object:Gem::Version
153
- version: '12.3'
161
+ version: 0.89.1
154
162
  type: :development
155
163
  prerelease: false
156
164
  version_requirements: !ruby/object:Gem::Requirement
157
165
  requirements:
158
166
  - - "~>"
159
167
  - !ruby/object:Gem::Version
160
- version: '12.3'
168
+ version: 0.89.1
161
169
  - !ruby/object:Gem::Dependency
162
- name: rspec-rails
170
+ name: rubocop-performance
163
171
  requirement: !ruby/object:Gem::Requirement
164
172
  requirements:
165
173
  - - "~>"
166
174
  - !ruby/object:Gem::Version
167
- version: '3.8'
175
+ version: 1.7.1
168
176
  type: :development
169
177
  prerelease: false
170
178
  version_requirements: !ruby/object:Gem::Requirement
171
179
  requirements:
172
180
  - - "~>"
173
181
  - !ruby/object:Gem::Version
174
- version: '3.8'
182
+ version: 1.7.1
175
183
  - !ruby/object:Gem::Dependency
176
- name: rubocop
184
+ name: rubocop-rspec
177
185
  requirement: !ruby/object:Gem::Requirement
178
186
  requirements:
179
187
  - - "~>"
180
188
  - !ruby/object:Gem::Version
181
- version: 0.62.0
189
+ version: 1.42.0
182
190
  type: :development
183
191
  prerelease: false
184
192
  version_requirements: !ruby/object:Gem::Requirement
185
193
  requirements:
186
194
  - - "~>"
187
195
  - !ruby/object:Gem::Version
188
- version: 0.62.0
196
+ version: 1.42.0
189
197
  - !ruby/object:Gem::Dependency
190
- name: rubocop-rspec
198
+ name: mysql2
191
199
  requirement: !ruby/object:Gem::Requirement
192
200
  requirements:
193
201
  - - "~>"
194
202
  - !ruby/object:Gem::Version
195
- version: 1.28.0
203
+ version: '0.5'
196
204
  type: :development
197
205
  prerelease: false
198
206
  version_requirements: !ruby/object:Gem::Requirement
199
207
  requirements:
200
208
  - - "~>"
201
209
  - !ruby/object:Gem::Version
202
- version: 1.28.0
210
+ version: '0.5'
211
+ - !ruby/object:Gem::Dependency
212
+ name: pg
213
+ requirement: !ruby/object:Gem::Requirement
214
+ requirements:
215
+ - - ">="
216
+ - !ruby/object:Gem::Version
217
+ version: '0.18'
218
+ - - "<"
219
+ - !ruby/object:Gem::Version
220
+ version: '2.0'
221
+ type: :development
222
+ prerelease: false
223
+ version_requirements: !ruby/object:Gem::Requirement
224
+ requirements:
225
+ - - ">="
226
+ - !ruby/object:Gem::Version
227
+ version: '0.18'
228
+ - - "<"
229
+ - !ruby/object:Gem::Version
230
+ version: '2.0'
203
231
  - !ruby/object:Gem::Dependency
204
232
  name: sqlite3
205
233
  requirement: !ruby/object:Gem::Requirement
206
234
  requirements:
207
235
  - - "~>"
208
236
  - !ruby/object:Gem::Version
209
- version: '1.3'
237
+ version: '1.4'
210
238
  type: :development
211
239
  prerelease: false
212
240
  version_requirements: !ruby/object:Gem::Requirement
213
241
  requirements:
214
242
  - - "~>"
215
243
  - !ruby/object:Gem::Version
216
- version: '1.3'
244
+ version: '1.4'
217
245
  description: |
218
246
  Track changes to your models, for auditing or versioning. See how a model looked
219
247
  at any stage in its lifecycle, revert it to any version, or restore it after it
@@ -223,11 +251,12 @@ executables: []
223
251
  extensions: []
224
252
  extra_rdoc_files: []
225
253
  files:
254
+ - Gemfile
255
+ - LICENSE
226
256
  - lib/generators/paper_trail/install/USAGE
227
257
  - lib/generators/paper_trail/install/install_generator.rb
228
258
  - lib/generators/paper_trail/install/templates/add_object_changes_to_versions.rb.erb
229
259
  - lib/generators/paper_trail/install/templates/create_versions.rb.erb
230
- - lib/generators/paper_trail/install_generator.rb
231
260
  - lib/generators/paper_trail/migration_generator.rb
232
261
  - lib/generators/paper_trail/update_item_subtype/USAGE
233
262
  - lib/generators/paper_trail/update_item_subtype/templates/update_versions_for_item_subtype.rb.erb
@@ -239,6 +268,7 @@ files:
239
268
  - lib/paper_trail/attribute_serializers/object_attribute.rb
240
269
  - lib/paper_trail/attribute_serializers/object_changes_attribute.rb
241
270
  - lib/paper_trail/cleaner.rb
271
+ - lib/paper_trail/compatibility.rb
242
272
  - lib/paper_trail/config.rb
243
273
  - lib/paper_trail/events/base.rb
244
274
  - lib/paper_trail/events/create.rb
@@ -265,6 +295,7 @@ files:
265
295
  - lib/paper_trail/type_serializers/postgres_array_serializer.rb
266
296
  - lib/paper_trail/version_concern.rb
267
297
  - lib/paper_trail/version_number.rb
298
+ - paper_trail.gemspec
268
299
  homepage: https://github.com/paper-trail-gem/paper_trail
269
300
  licenses:
270
301
  - MIT
@@ -277,15 +308,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
277
308
  requirements:
278
309
  - - ">="
279
310
  - !ruby/object:Gem::Version
280
- version: 2.3.0
311
+ version: 2.4.0
281
312
  required_rubygems_version: !ruby/object:Gem::Requirement
282
313
  requirements:
283
314
  - - ">="
284
315
  - !ruby/object:Gem::Version
285
316
  version: 1.3.6
286
317
  requirements: []
287
- rubyforge_project:
288
- rubygems_version: 2.5.2.3
318
+ rubygems_version: 3.0.3
289
319
  signing_key:
290
320
  specification_version: 4
291
321
  summary: Track changes to your models.
@@ -1,99 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "rails/generators"
4
- require "rails/generators/active_record"
5
-
6
- module PaperTrail
7
- # Installs PaperTrail in a rails app.
8
- class InstallGenerator < ::Rails::Generators::Base
9
- include ::Rails::Generators::Migration
10
-
11
- # Class names of MySQL adapters.
12
- # - `MysqlAdapter` - Used by gems: `mysql`, `activerecord-jdbcmysql-adapter`.
13
- # - `Mysql2Adapter` - Used by `mysql2` gem.
14
- MYSQL_ADAPTERS = [
15
- "ActiveRecord::ConnectionAdapters::MysqlAdapter",
16
- "ActiveRecord::ConnectionAdapters::Mysql2Adapter"
17
- ].freeze
18
-
19
- source_root File.expand_path("templates", __dir__)
20
- class_option(
21
- :with_changes,
22
- type: :boolean,
23
- default: false,
24
- desc: "Store changeset (diff) with each version"
25
- )
26
-
27
- desc "Generates (but does not run) a migration to add a versions table."
28
-
29
- def create_migration_file
30
- add_paper_trail_migration("create_versions")
31
- add_paper_trail_migration("add_object_changes_to_versions") if options.with_changes?
32
- end
33
-
34
- def self.next_migration_number(dirname)
35
- ::ActiveRecord::Generators::Base.next_migration_number(dirname)
36
- end
37
-
38
- protected
39
-
40
- def add_paper_trail_migration(template)
41
- migration_dir = File.expand_path("db/migrate")
42
- if self.class.migration_exists?(migration_dir, template)
43
- ::Kernel.warn "Migration already exists: #{template}"
44
- else
45
- migration_template(
46
- "#{template}.rb.erb",
47
- "db/migrate/#{template}.rb",
48
- item_type_options: item_type_options,
49
- migration_version: migration_version,
50
- versions_table_options: versions_table_options
51
- )
52
- end
53
- end
54
-
55
- private
56
-
57
- # MySQL 5.6 utf8mb4 limit is 191 chars for keys used in indexes.
58
- # See https://github.com/paper-trail-gem/paper_trail/issues/651
59
- def item_type_options
60
- opt = { null: false }
61
- opt[:limit] = 191 if mysql?
62
- ", #{opt}"
63
- end
64
-
65
- def migration_version
66
- major = ActiveRecord::VERSION::MAJOR
67
- if major >= 5
68
- "[#{major}.#{ActiveRecord::VERSION::MINOR}]"
69
- end
70
- end
71
-
72
- def mysql?
73
- MYSQL_ADAPTERS.include?(ActiveRecord::Base.connection.class.name)
74
- end
75
-
76
- # Even modern versions of MySQL still use `latin1` as the default character
77
- # encoding. Many users are not aware of this, and run into trouble when they
78
- # try to use PaperTrail in apps that otherwise tend to use UTF-8. Postgres, by
79
- # comparison, uses UTF-8 except in the unusual case where the OS is configured
80
- # with a custom locale.
81
- #
82
- # - https://dev.mysql.com/doc/refman/5.7/en/charset-applications.html
83
- # - http://www.postgresql.org/docs/9.4/static/multibyte.html
84
- #
85
- # Furthermore, MySQL's original implementation of UTF-8 was flawed, and had
86
- # to be fixed later by introducing a new charset, `utf8mb4`.
87
- #
88
- # - https://mathiasbynens.be/notes/mysql-utf8mb4
89
- # - https://dev.mysql.com/doc/refman/5.5/en/charset-unicode-utf8mb4.html
90
- #
91
- def versions_table_options
92
- if mysql?
93
- ', { options: "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci" }'
94
- else
95
- ""
96
- end
97
- end
98
- end
99
- end