paper_trail 10.2.0 → 10.2.1

Sign up to get free protection for your applications and to get access to all the features.
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: 35e835ccf1aa917cfbe1202f639d73f7e70f4f35e4845706f6edb8ba713004b3
4
+ data.tar.gz: eafb8a0ed8ddfc6daeafbd7fb8197c225a2a962a1e32f42664802b72f386d510
5
5
  SHA512:
6
- metadata.gz: bd84d2586c73b9f08e44fabb1298a6e530ecf25953da0a05a41a925870720e6c05eb74474321816b51dd24cceb1d14b0658f685ac0c68a91de3ff6e7c2e0525b
7
- data.tar.gz: fb8e6814903909666d842d43b68b5ef97e5c207a8193d34a5d199f8405fb83565c87ba86925c58148720debde25bdd442f712ef75b9acaf8b3d86a6c71978191
6
+ metadata.gz: 2c22225eb860afba59822dac469c24bd6aa3fd9712a521a93187ce0ce497a2c66a022c8cb6615d1cfc13fbfc8ac9df386cf4f038ac1ec7fd78cea9611d86f26e
7
+ data.tar.gz: efb345a2262034ee6ac5366bc1825bb710b79ed3b80e99567bfe873cc1aca7815e8f5136e066358d542ad52aa777274059c3334d5b6fb56c4fff24ecc4521b65
@@ -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
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
70
85
  end
71
86
 
72
87
  # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
@@ -108,7 +123,8 @@ module PaperTrail
108
123
 
109
124
  # @api private
110
125
  def changed_in_latest_version
111
- changes_in_latest_version.keys
126
+ # Memoized to reduce memory usage
127
+ @changed_in_latest_version ||= changes_in_latest_version.keys
112
128
  end
113
129
 
114
130
  # Rails 5.1 changed the API of `ActiveRecord::Dirty`. See
@@ -116,10 +132,13 @@ module PaperTrail
116
132
  #
117
133
  # @api private
118
134
  def changes_in_latest_version
119
- if @in_after_callback && RAILS_GTE_5_1
120
- @record.saved_changes
121
- else
122
- @record.changes
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
123
142
  end
124
143
  end
125
144
 
@@ -200,16 +219,19 @@ module PaperTrail
200
219
 
201
220
  # @api private
202
221
  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
- }
222
+ # Memoized to reduce memory usage
223
+ @notably_changed ||= begin
224
+ only = @record.paper_trail_options[:only].dup
225
+ # Remove Hash arguments and then evaluate whether the attributes (the
226
+ # keys of the hash) should also get pushed into the collection.
227
+ only.delete_if do |obj|
228
+ obj.is_a?(Hash) &&
229
+ obj.each { |attr, condition|
230
+ only << attr if condition.respond_to?(:call) && condition.call(@record)
231
+ }
232
+ end
233
+ only.empty? ? changed_and_not_ignored : (changed_and_not_ignored & only)
211
234
  end
212
- only.empty? ? changed_and_not_ignored : (changed_and_not_ignored & only)
213
235
  end
214
236
 
215
237
  # Returns hash of attributes (with appropriate attributes serialized),
@@ -217,8 +239,7 @@ module PaperTrail
217
239
  #
218
240
  # @api private
219
241
  def object_attrs_for_paper_trail(is_touch)
220
- attrs = attributes_before_change(is_touch).
221
- except(*@record.paper_trail_options[:skip])
242
+ attrs = nonskipped_attributes_before_change(is_touch)
222
243
  AttributeSerializers::ObjectAttribute.new(@record.class).serialize(attrs)
223
244
  attrs
224
245
  end
@@ -237,9 +258,14 @@ module PaperTrail
237
258
  # serialization here, using `PaperTrail.serializer`.
238
259
  #
239
260
  # @api private
261
+ # @param changes HashWithIndifferentAccess
240
262
  def recordable_object_changes(changes)
241
263
  if PaperTrail.config.object_changes_adapter&.respond_to?(:diff)
242
- changes = PaperTrail.config.object_changes_adapter.diff(changes)
264
+ # We'd like to avoid the `to_hash` here, because it increases memory
265
+ # usage, but that would be a breaking change because
266
+ # `object_changes_adapter` expects a plain `Hash`, not a
267
+ # `HashWithIndifferentAccess`.
268
+ changes = PaperTrail.config.object_changes_adapter.diff(changes.to_hash)
243
269
  end
244
270
 
245
271
  if @record.class.paper_trail.version_class.object_changes_col_is_json?
@@ -284,7 +310,10 @@ module PaperTrail
284
310
  AttributeSerializers::ObjectChangesAttribute.
285
311
  new(@record.class).
286
312
  serialize(changes)
287
- changes.to_hash
313
+
314
+ # We'd like to convert this `HashWithIndifferentAccess` to a plain
315
+ # `Hash`, but we don't, to save memory.
316
+ changes
288
317
  end
289
318
  end
290
319
  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
  }
@@ -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}" \
@@ -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
 
@@ -9,7 +9,7 @@ module PaperTrail
9
9
  module VERSION
10
10
  MAJOR = 10
11
11
  MINOR = 2
12
- TINY = 0
12
+ TINY = 1
13
13
 
14
14
  # Set PRE to nil unless it's a pre-release (beta, rc, etc.)
15
15
  PRE = nil
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: 10.2.1
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: 2019-03-14 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -102,6 +102,20 @@ dependencies:
102
102
  - - "~>"
103
103
  - !ruby/object:Gem::Version
104
104
  version: 0.9.4
105
+ - !ruby/object:Gem::Dependency
106
+ name: memory_profiler
107
+ requirement: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: 0.9.12
112
+ type: :development
113
+ prerelease: false
114
+ version_requirements: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - "~>"
117
+ - !ruby/object:Gem::Version
118
+ version: 0.9.12
105
119
  - !ruby/object:Gem::Dependency
106
120
  name: mysql2
107
121
  requirement: !ruby/object:Gem::Requirement
@@ -206,14 +220,14 @@ dependencies:
206
220
  requirements:
207
221
  - - "~>"
208
222
  - !ruby/object:Gem::Version
209
- version: '1.3'
223
+ version: 1.3.13
210
224
  type: :development
211
225
  prerelease: false
212
226
  version_requirements: !ruby/object:Gem::Requirement
213
227
  requirements:
214
228
  - - "~>"
215
229
  - !ruby/object:Gem::Version
216
- version: '1.3'
230
+ version: 1.3.13
217
231
  description: |
218
232
  Track changes to your models, for auditing or versioning. See how a model looked
219
233
  at any stage in its lifecycle, revert it to any version, or restore it after it
@@ -284,8 +298,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
284
298
  - !ruby/object:Gem::Version
285
299
  version: 1.3.6
286
300
  requirements: []
287
- rubyforge_project:
288
- rubygems_version: 2.5.2.3
301
+ rubygems_version: 3.0.3
289
302
  signing_key:
290
303
  specification_version: 4
291
304
  summary: Track changes to your models.