paper_trail_diff 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +343 -0
  3. data/QUICKSTART.md +21 -4
  4. data/README.md +58 -55
  5. data/docs/architecture.md +32 -11
  6. data/docs/performance.md +116 -391
  7. data/docs/reference.md +345 -106
  8. data/docs/testing.md +63 -7
  9. data/lib/paper_trail_diff/activity_boundary.rb +83 -26
  10. data/lib/paper_trail_diff/activity_child_candidate_loader.rb +2 -1
  11. data/lib/paper_trail_diff/activity_event.rb +41 -10
  12. data/lib/paper_trail_diff/activity_history.rb +34 -9
  13. data/lib/paper_trail_diff/activity_range.rb +18 -3
  14. data/lib/paper_trail_diff/activity_snapshot_sequence.rb +76 -37
  15. data/lib/paper_trail_diff/activity_target_candidate_loader.rb +5 -5
  16. data/lib/paper_trail_diff/activity_timeline_builder.rb +266 -111
  17. data/lib/paper_trail_diff/activity_transaction_grouper.rb +2 -1
  18. data/lib/paper_trail_diff/activity_tree_filter.rb +114 -0
  19. data/lib/paper_trail_diff/activity_version_collector.rb +7 -5
  20. data/lib/paper_trail_diff/activity_view.rb +69 -0
  21. data/lib/paper_trail_diff/analysis.rb +13 -1
  22. data/lib/paper_trail_diff/analysis_batch.rb +24 -30
  23. data/lib/paper_trail_diff/approximate_paths.rb +48 -0
  24. data/lib/paper_trail_diff/association_activity_edge.rb +6 -6
  25. data/lib/paper_trail_diff/association_discovery.rb +9 -1
  26. data/lib/paper_trail_diff/association_key.rb +56 -0
  27. data/lib/paper_trail_diff/association_traversal.rb +13 -4
  28. data/lib/paper_trail_diff/batch_boundary_resolver.rb +3 -4
  29. data/lib/paper_trail_diff/batch_preload.rb +53 -0
  30. data/lib/paper_trail_diff/batched_root_analyzer.rb +12 -14
  31. data/lib/paper_trail_diff/batched_root_versions.rb +38 -14
  32. data/lib/paper_trail_diff/change_row.rb +133 -0
  33. data/lib/paper_trail_diff/collection_comparator.rb +5 -32
  34. data/lib/paper_trail_diff/configuration.rb +35 -12
  35. data/lib/paper_trail_diff/deleted_analysis_batch.rb +18 -18
  36. data/lib/paper_trail_diff/deleted_root_filter.rb +79 -0
  37. data/lib/paper_trail_diff/deprecation.rb +61 -0
  38. data/lib/paper_trail_diff/diagnostics.rb +84 -99
  39. data/lib/paper_trail_diff/endpoint.rb +26 -3
  40. data/lib/paper_trail_diff/errors.rb +3 -1
  41. data/lib/paper_trail_diff/fallback_paths.rb +38 -0
  42. data/lib/paper_trail_diff/historical_association_reifier.rb +2 -4
  43. data/lib/paper_trail_diff/historical_filter.rb +19 -10
  44. data/lib/paper_trail_diff/historical_snapshot_store.rb +57 -67
  45. data/lib/paper_trail_diff/live_endpoint_batch_loader.rb +1 -35
  46. data/lib/paper_trail_diff/nested_comparator.rb +19 -18
  47. data/lib/paper_trail_diff/paper_trail_adapter.rb +122 -127
  48. data/lib/paper_trail_diff/preloaded_endpoint_batch_loader.rb +13 -1
  49. data/lib/paper_trail_diff/prepared_activity_index.rb +1 -1
  50. data/lib/paper_trail_diff/prepared_edge_loader.rb +2 -1
  51. data/lib/paper_trail_diff/prepared_history.rb +9 -1
  52. data/lib/paper_trail_diff/prepared_history_loader.rb +1 -0
  53. data/lib/paper_trail_diff/prepared_record_index.rb +38 -7
  54. data/lib/paper_trail_diff/root_version_selection.rb +4 -2
  55. data/lib/paper_trail_diff/scope_condition.rb +76 -21
  56. data/lib/paper_trail_diff/scope_ranking.rb +12 -2
  57. data/lib/paper_trail_diff/scoped_analysis.rb +11 -0
  58. data/lib/paper_trail_diff/scoped_analysis_builder.rb +16 -8
  59. data/lib/paper_trail_diff/scoped_root_selection.rb +18 -12
  60. data/lib/paper_trail_diff/snapshot.rb +16 -1
  61. data/lib/paper_trail_diff/support.rb +29 -2
  62. data/lib/paper_trail_diff/time_range.rb +32 -10
  63. data/lib/paper_trail_diff/timeline_builder.rb +7 -7
  64. data/lib/paper_trail_diff/timeline_range.rb +9 -1
  65. data/lib/paper_trail_diff/timeline_snapshot_provider.rb +24 -17
  66. data/lib/paper_trail_diff/transaction_cut.rb +181 -0
  67. data/lib/paper_trail_diff/traversal_preparer.rb +134 -29
  68. data/lib/paper_trail_diff/version.rb +1 -1
  69. data/lib/paper_trail_diff/version_boundary.rb +37 -8
  70. data/lib/paper_trail_diff/version_query.rb +34 -10
  71. data/lib/paper_trail_diff/version_scope_filter.rb +9 -10
  72. data/lib/paper_trail_diff/version_sequence_diagnostics.rb +31 -16
  73. data/lib/paper_trail_diff/version_table.rb +60 -0
  74. data/lib/paper_trail_diff.rb +98 -56
  75. data/sig/generated/paper_trail_diff/activity_boundary.rbs +54 -19
  76. data/sig/generated/paper_trail_diff/activity_event.rbs +25 -4
  77. data/sig/generated/paper_trail_diff/activity_history.rbs +21 -6
  78. data/sig/generated/paper_trail_diff/activity_range.rbs +9 -2
  79. data/sig/generated/paper_trail_diff/activity_snapshot_sequence.rbs +48 -9
  80. data/sig/generated/paper_trail_diff/activity_timeline_builder.rbs +134 -30
  81. data/sig/generated/paper_trail_diff/activity_tree_filter.rbs +73 -0
  82. data/sig/generated/paper_trail_diff/activity_version_collector.rbs +2 -0
  83. data/sig/generated/paper_trail_diff/activity_view.rbs +42 -0
  84. data/sig/generated/paper_trail_diff/analysis.rbs +8 -1
  85. data/sig/generated/paper_trail_diff/analysis_batch.rbs +8 -15
  86. data/sig/generated/paper_trail_diff/approximate_paths.rbs +23 -0
  87. data/sig/generated/paper_trail_diff/association_discovery.rbs +5 -0
  88. data/sig/generated/paper_trail_diff/association_key.rbs +29 -0
  89. data/sig/generated/paper_trail_diff/association_traversal.rbs +4 -0
  90. data/sig/generated/paper_trail_diff/batch_preload.rbs +29 -0
  91. data/sig/generated/paper_trail_diff/batched_root_analyzer.rbs +9 -10
  92. data/sig/generated/paper_trail_diff/batched_root_versions.rbs +14 -4
  93. data/sig/generated/paper_trail_diff/change_row.rbs +86 -0
  94. data/sig/generated/paper_trail_diff/collection_comparator.rbs +2 -16
  95. data/sig/generated/paper_trail_diff/configuration.rbs +19 -3
  96. data/sig/generated/paper_trail_diff/deleted_analysis_batch.rbs +8 -8
  97. data/sig/generated/paper_trail_diff/deleted_root_filter.rbs +48 -0
  98. data/sig/generated/paper_trail_diff/deprecation.rbs +35 -0
  99. data/sig/generated/paper_trail_diff/diagnostics.rbs +37 -30
  100. data/sig/generated/paper_trail_diff/endpoint.rbs +11 -2
  101. data/sig/generated/paper_trail_diff/errors.rbs +3 -1
  102. data/sig/generated/paper_trail_diff/fallback_paths.rbs +22 -0
  103. data/sig/generated/paper_trail_diff/historical_filter.rbs +12 -2
  104. data/sig/generated/paper_trail_diff/historical_snapshot_store.rbs +33 -28
  105. data/sig/generated/paper_trail_diff/live_endpoint_batch_loader.rbs +0 -15
  106. data/sig/generated/paper_trail_diff/nested_comparator.rbs +10 -7
  107. data/sig/generated/paper_trail_diff/paper_trail_adapter.rbs +54 -52
  108. data/sig/generated/paper_trail_diff/preloaded_endpoint_batch_loader.rbs +9 -1
  109. data/sig/generated/paper_trail_diff/prepared_history.rbs +5 -0
  110. data/sig/generated/paper_trail_diff/prepared_record_index.rbs +19 -2
  111. data/sig/generated/paper_trail_diff/root_version_selection.rbs +2 -0
  112. data/sig/generated/paper_trail_diff/scope_condition.rbs +43 -9
  113. data/sig/generated/paper_trail_diff/scope_ranking.rbs +8 -0
  114. data/sig/generated/paper_trail_diff/scoped_analysis.rbs +6 -0
  115. data/sig/generated/paper_trail_diff/scoped_analysis_builder.rbs +6 -1
  116. data/sig/generated/paper_trail_diff/scoped_root_selection.rbs +8 -4
  117. data/sig/generated/paper_trail_diff/snapshot.rbs +8 -0
  118. data/sig/generated/paper_trail_diff/support.rbs +15 -0
  119. data/sig/generated/paper_trail_diff/time_range.rbs +20 -6
  120. data/sig/generated/paper_trail_diff/timeline_builder.rbs +2 -2
  121. data/sig/generated/paper_trail_diff/timeline_range.rbs +2 -0
  122. data/sig/generated/paper_trail_diff/timeline_snapshot_provider.rbs +17 -7
  123. data/sig/generated/paper_trail_diff/transaction_cut.rbs +94 -0
  124. data/sig/generated/paper_trail_diff/traversal_preparer.rbs +66 -8
  125. data/sig/generated/paper_trail_diff/version_boundary.rbs +20 -3
  126. data/sig/generated/paper_trail_diff/version_query.rbs +18 -2
  127. data/sig/generated/paper_trail_diff/version_scope_filter.rbs +4 -5
  128. data/sig/generated/paper_trail_diff/version_sequence_diagnostics.rbs +16 -9
  129. data/sig/generated/paper_trail_diff/version_table.rbs +30 -0
  130. data/sig/generated/paper_trail_diff.rbs +29 -26
  131. metadata +46 -10
  132. data/lib/paper_trail_diff/activity_grouping.rb +0 -38
  133. data/lib/paper_trail_diff/batch_safety.rb +0 -63
  134. data/lib/paper_trail_diff/time_activity_timeline_builder.rb +0 -223
  135. data/sig/generated/paper_trail_diff/activity_grouping.rbs +0 -22
  136. data/sig/generated/paper_trail_diff/batch_safety.rbs +0 -30
  137. data/sig/generated/paper_trail_diff/time_activity_timeline_builder.rbs +0 -92
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paper_trail_diff
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Williams
@@ -9,6 +9,26 @@ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activerecord
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '7.1'
19
+ - - "<"
20
+ - !ruby/object:Gem::Version
21
+ version: '8.2'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '7.1'
29
+ - - "<"
30
+ - !ruby/object:Gem::Version
31
+ version: '8.2'
12
32
  - !ruby/object:Gem::Dependency
13
33
  name: paper_trail
14
34
  requirement: !ruby/object:Gem::Requirement
@@ -59,7 +79,6 @@ files:
59
79
  - lib/paper_trail_diff/activity_event_record_resolver.rb
60
80
  - lib/paper_trail_diff/activity_event_route_finder.rb
61
81
  - lib/paper_trail_diff/activity_event_snapshot_refresher.rb
62
- - lib/paper_trail_diff/activity_grouping.rb
63
82
  - lib/paper_trail_diff/activity_history.rb
64
83
  - lib/paper_trail_diff/activity_range.rb
65
84
  - lib/paper_trail_diff/activity_relationship.rb
@@ -70,30 +89,38 @@ files:
70
89
  - lib/paper_trail_diff/activity_target_candidate_loader.rb
71
90
  - lib/paper_trail_diff/activity_timeline_builder.rb
72
91
  - lib/paper_trail_diff/activity_transaction_grouper.rb
92
+ - lib/paper_trail_diff/activity_tree_filter.rb
73
93
  - lib/paper_trail_diff/activity_version_collector.rb
94
+ - lib/paper_trail_diff/activity_view.rb
74
95
  - lib/paper_trail_diff/analysis.rb
75
96
  - lib/paper_trail_diff/analysis_batch.rb
97
+ - lib/paper_trail_diff/approximate_paths.rb
76
98
  - lib/paper_trail_diff/association_activity_edge.rb
77
99
  - lib/paper_trail_diff/association_activity_selection.rb
78
100
  - lib/paper_trail_diff/association_diff_traversal.rb
79
101
  - lib/paper_trail_diff/association_discovery.rb
102
+ - lib/paper_trail_diff/association_key.rb
80
103
  - lib/paper_trail_diff/association_traversal.rb
81
104
  - lib/paper_trail_diff/batch_boundary_resolver.rb
82
- - lib/paper_trail_diff/batch_safety.rb
105
+ - lib/paper_trail_diff/batch_preload.rb
83
106
  - lib/paper_trail_diff/batched_root_analyzer.rb
84
107
  - lib/paper_trail_diff/batched_root_versions.rb
85
108
  - lib/paper_trail_diff/branch_snapshot_refresher.rb
109
+ - lib/paper_trail_diff/change_row.rb
86
110
  - lib/paper_trail_diff/collection_comparator.rb
87
111
  - lib/paper_trail_diff/collection_identity_index.rb
88
112
  - lib/paper_trail_diff/collection_transition.rb
89
113
  - lib/paper_trail_diff/comparison_batch.rb
90
114
  - lib/paper_trail_diff/configuration.rb
91
115
  - lib/paper_trail_diff/deleted_analysis_batch.rb
116
+ - lib/paper_trail_diff/deleted_root_filter.rb
117
+ - lib/paper_trail_diff/deprecation.rb
92
118
  - lib/paper_trail_diff/diagnostics.rb
93
119
  - lib/paper_trail_diff/direct_record_state_loader.rb
94
120
  - lib/paper_trail_diff/endpoint.rb
95
121
  - lib/paper_trail_diff/engine.rb
96
122
  - lib/paper_trail_diff/errors.rb
123
+ - lib/paper_trail_diff/fallback_paths.rb
97
124
  - lib/paper_trail_diff/habtm_transaction_owners.rb
98
125
  - lib/paper_trail_diff/historical_association_reifier.rb
99
126
  - lib/paper_trail_diff/historical_filter.rb
@@ -129,12 +156,12 @@ files:
129
156
  - lib/paper_trail_diff/step.rb
130
157
  - lib/paper_trail_diff/stored_time_precision.rb
131
158
  - lib/paper_trail_diff/support.rb
132
- - lib/paper_trail_diff/time_activity_timeline_builder.rb
133
159
  - lib/paper_trail_diff/time_range.rb
134
160
  - lib/paper_trail_diff/time_version_range.rb
135
161
  - lib/paper_trail_diff/timeline_builder.rb
136
162
  - lib/paper_trail_diff/timeline_range.rb
137
163
  - lib/paper_trail_diff/timeline_snapshot_provider.rb
164
+ - lib/paper_trail_diff/transaction_cut.rb
138
165
  - lib/paper_trail_diff/traversal.rb
139
166
  - lib/paper_trail_diff/traversal_emitter.rb
140
167
  - lib/paper_trail_diff/traversal_entry.rb
@@ -147,6 +174,7 @@ files:
147
174
  - lib/paper_trail_diff/version_range.rb
148
175
  - lib/paper_trail_diff/version_scope_filter.rb
149
176
  - lib/paper_trail_diff/version_sequence_diagnostics.rb
177
+ - lib/paper_trail_diff/version_table.rb
150
178
  - sig/generated/paper_trail_diff.rbs
151
179
  - sig/generated/paper_trail_diff/activity_belongs_to_event_applier.rbs
152
180
  - sig/generated/paper_trail_diff/activity_boundary.rbs
@@ -160,7 +188,6 @@ files:
160
188
  - sig/generated/paper_trail_diff/activity_event_record_resolver.rbs
161
189
  - sig/generated/paper_trail_diff/activity_event_route_finder.rbs
162
190
  - sig/generated/paper_trail_diff/activity_event_snapshot_refresher.rbs
163
- - sig/generated/paper_trail_diff/activity_grouping.rbs
164
191
  - sig/generated/paper_trail_diff/activity_history.rbs
165
192
  - sig/generated/paper_trail_diff/activity_range.rbs
166
193
  - sig/generated/paper_trail_diff/activity_relationship.rbs
@@ -171,30 +198,38 @@ files:
171
198
  - sig/generated/paper_trail_diff/activity_target_candidate_loader.rbs
172
199
  - sig/generated/paper_trail_diff/activity_timeline_builder.rbs
173
200
  - sig/generated/paper_trail_diff/activity_transaction_grouper.rbs
201
+ - sig/generated/paper_trail_diff/activity_tree_filter.rbs
174
202
  - sig/generated/paper_trail_diff/activity_version_collector.rbs
203
+ - sig/generated/paper_trail_diff/activity_view.rbs
175
204
  - sig/generated/paper_trail_diff/analysis.rbs
176
205
  - sig/generated/paper_trail_diff/analysis_batch.rbs
206
+ - sig/generated/paper_trail_diff/approximate_paths.rbs
177
207
  - sig/generated/paper_trail_diff/association_activity_edge.rbs
178
208
  - sig/generated/paper_trail_diff/association_activity_selection.rbs
179
209
  - sig/generated/paper_trail_diff/association_diff_traversal.rbs
180
210
  - sig/generated/paper_trail_diff/association_discovery.rbs
211
+ - sig/generated/paper_trail_diff/association_key.rbs
181
212
  - sig/generated/paper_trail_diff/association_traversal.rbs
182
213
  - sig/generated/paper_trail_diff/batch_boundary_resolver.rbs
183
- - sig/generated/paper_trail_diff/batch_safety.rbs
214
+ - sig/generated/paper_trail_diff/batch_preload.rbs
184
215
  - sig/generated/paper_trail_diff/batched_root_analyzer.rbs
185
216
  - sig/generated/paper_trail_diff/batched_root_versions.rbs
186
217
  - sig/generated/paper_trail_diff/branch_snapshot_refresher.rbs
218
+ - sig/generated/paper_trail_diff/change_row.rbs
187
219
  - sig/generated/paper_trail_diff/collection_comparator.rbs
188
220
  - sig/generated/paper_trail_diff/collection_identity_index.rbs
189
221
  - sig/generated/paper_trail_diff/collection_transition.rbs
190
222
  - sig/generated/paper_trail_diff/comparison_batch.rbs
191
223
  - sig/generated/paper_trail_diff/configuration.rbs
192
224
  - sig/generated/paper_trail_diff/deleted_analysis_batch.rbs
225
+ - sig/generated/paper_trail_diff/deleted_root_filter.rbs
226
+ - sig/generated/paper_trail_diff/deprecation.rbs
193
227
  - sig/generated/paper_trail_diff/diagnostics.rbs
194
228
  - sig/generated/paper_trail_diff/direct_record_state_loader.rbs
195
229
  - sig/generated/paper_trail_diff/endpoint.rbs
196
230
  - sig/generated/paper_trail_diff/engine.rbs
197
231
  - sig/generated/paper_trail_diff/errors.rbs
232
+ - sig/generated/paper_trail_diff/fallback_paths.rbs
198
233
  - sig/generated/paper_trail_diff/habtm_transaction_owners.rbs
199
234
  - sig/generated/paper_trail_diff/historical_association_reifier.rbs
200
235
  - sig/generated/paper_trail_diff/historical_filter.rbs
@@ -230,12 +265,12 @@ files:
230
265
  - sig/generated/paper_trail_diff/step.rbs
231
266
  - sig/generated/paper_trail_diff/stored_time_precision.rbs
232
267
  - sig/generated/paper_trail_diff/support.rbs
233
- - sig/generated/paper_trail_diff/time_activity_timeline_builder.rbs
234
268
  - sig/generated/paper_trail_diff/time_range.rbs
235
269
  - sig/generated/paper_trail_diff/time_version_range.rbs
236
270
  - sig/generated/paper_trail_diff/timeline_builder.rbs
237
271
  - sig/generated/paper_trail_diff/timeline_range.rbs
238
272
  - sig/generated/paper_trail_diff/timeline_snapshot_provider.rbs
273
+ - sig/generated/paper_trail_diff/transaction_cut.rbs
239
274
  - sig/generated/paper_trail_diff/traversal.rbs
240
275
  - sig/generated/paper_trail_diff/traversal_emitter.rbs
241
276
  - sig/generated/paper_trail_diff/traversal_entry.rbs
@@ -248,17 +283,18 @@ files:
248
283
  - sig/generated/paper_trail_diff/version_range.rbs
249
284
  - sig/generated/paper_trail_diff/version_scope_filter.rbs
250
285
  - sig/generated/paper_trail_diff/version_sequence_diagnostics.rbs
286
+ - sig/generated/paper_trail_diff/version_table.rbs
251
287
  homepage: https://github.com/aheathwilliams/paper_trail_diff
252
288
  licenses:
253
289
  - MIT
254
290
  metadata:
255
291
  allowed_push_host: https://rubygems.org
256
292
  bug_tracker_uri: https://github.com/aheathwilliams/paper_trail_diff/issues
257
- changelog_uri: https://github.com/aheathwilliams/paper_trail_diff/blob/v0.14.0/CHANGELOG.md
258
- documentation_uri: https://github.com/aheathwilliams/paper_trail_diff/blob/v0.14.0/README.md
293
+ changelog_uri: https://github.com/aheathwilliams/paper_trail_diff/blob/v0.15.0/CHANGELOG.md
294
+ documentation_uri: https://github.com/aheathwilliams/paper_trail_diff/blob/v0.15.0/README.md
259
295
  homepage_uri: https://github.com/aheathwilliams/paper_trail_diff
260
296
  rubygems_mfa_required: 'true'
261
- source_code_uri: https://github.com/aheathwilliams/paper_trail_diff/tree/v0.14.0
297
+ source_code_uri: https://github.com/aheathwilliams/paper_trail_diff/tree/v0.15.0
262
298
  rdoc_options: []
263
299
  require_paths:
264
300
  - lib
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
- # rbs_inline: enabled
3
-
4
- module PaperTrailDiff
5
- # Shared by the two activity builders. They differ in how they choose
6
- # boundaries -- one from an explicit version range, one from a wall-clock
7
- # window -- but a transaction has to collapse the same way in both, or the
8
- # same history would read differently depending on how it was asked for.
9
- module ActivityGrouping
10
- #: (Symbol?, ?activity: bool, ?snapshots: bool) -> void
11
- def self.validate!(group, activity: true, snapshots: false)
12
- unless group.nil? || group == :transaction
13
- raise ConfigurationError, 'group: must be :transaction or nil'
14
- end
15
- raise ConfigurationError, 'snapshots: requires activity: true' if snapshots && !activity
16
-
17
- return if group.nil? || activity
18
-
19
- raise ConfigurationError, 'group: requires activity: true'
20
- end
21
-
22
- private
23
-
24
- #: () -> bool
25
- def grouping?
26
- @group == :transaction
27
- end
28
-
29
- # Applied last, to finished steps, so grouping sees the same timeline the
30
- # caller would otherwise have received.
31
- #: (Array[ActivityStep]) -> Array[ActivityStep]
32
- def group_steps(steps)
33
- return steps unless grouping?
34
-
35
- ActivityTransactionGrouper.new(steps, retain: @snapshots).call
36
- end
37
- end
38
- end
@@ -1,63 +0,0 @@
1
- # frozen_string_literal: true
2
- # rbs_inline: enabled
3
-
4
- module PaperTrailDiff
5
- # Refuses known per-record association loading before a batch reconstructs it.
6
- class BatchSafety
7
- #: (AssociationTraversal, allow_expensive: bool) -> void
8
- def initialize(traversal, allow_expensive:)
9
- unless [true, false].include?(allow_expensive)
10
- raise ConfigurationError, 'allow_expensive: must be true or false'
11
- end
12
-
13
- @traversal = traversal
14
- @allow_expensive = allow_expensive
15
- end
16
-
17
- #: (untyped) -> void
18
- def call(model)
19
- return if @allow_expensive
20
-
21
- @traversal.selected_reflections(model).each do |path, reflection|
22
- check(reflection, path)
23
- end
24
- end
25
-
26
- private
27
-
28
- # @rbs @traversal: AssociationTraversal
29
- # @rbs @allow_expensive: bool
30
-
31
- # A has_many through reads its parts' conditions only, at any depth. A
32
- # has_one through over a ranked part is refused whole, so its parts, too,
33
- # are checked for conditions alone.
34
- #: (untyped, String, ?ranked: bool) -> void
35
- def check(reflection, path, ranked: true)
36
- reason = reason_for(reflection, ranked)
37
- if reason
38
- raise UnsupportedBatchPathError,
39
- "association #{path} requires per-record reconstruction (#{reason}); " \
40
- 'remove this path or explicitly pass allow_expensive: true'
41
- end
42
- return unless reflection.through_reflection?
43
-
44
- [reflection.through_reflection, reflection.source_reflection].each do |part|
45
- check(part, path, ranked: false)
46
- end
47
- end
48
-
49
- # A scope prepared history can read as conditions on stored attributes
50
- # is batched like any other path. A polymorphic target's class, and so
51
- # its scope, is known only per record.
52
- #: (untyped, bool) -> String?
53
- def reason_for(reflection, ranked)
54
- return ('association scope' if reflection.scope) if reflection.polymorphic?
55
- if ranked && ScopeCondition.ranked_through?(reflection)
56
- return 'limit, offset or has_one order inside a has_one through'
57
- end
58
- return if ScopeCondition.build(reflection, ranked: ranked)
59
-
60
- reflection.scope ? 'association scope' : 'default-scoped target'
61
- end
62
- end
63
- end
@@ -1,223 +0,0 @@
1
- # frozen_string_literal: true
2
- # rbs_inline: enabled
3
-
4
- module PaperTrailDiff
5
- # Builds activity views for mutations selected by a wall-clock range.
6
- class TimeActivityTimelineBuilder
7
- include ActivityGrouping
8
-
9
- #: (untyped, range: TimelineRange, tree: AssociationTree, snapshotter: untyped, ?snapshots: bool, ?group: Symbol?) -> void
10
- def initialize(record, range:, tree:, snapshotter:, snapshots: false, group: nil) # rubocop:disable Metrics/ParameterLists
11
- @snapshots = snapshots
12
- @group = group
13
- # Merging a group compares its outer states, so the snapshots must survive
14
- # the build even when the caller did not ask to keep them.
15
- @retain = snapshots || grouping?
16
- @record = record
17
- @range = range
18
- @tree = tree
19
- @snapshotter = snapshotter
20
- end
21
-
22
- #: () -> Array[ActivityStep]
23
- def build
24
- history, _plan, closing, = history_and_versions
25
- activity_steps(history, closing)
26
- end
27
-
28
- #: () -> Analysis
29
- def analyze
30
- history, plan, closing, closing_snapshot, captured_at = history_and_versions
31
- final = plan.closes_on_destroy ? nil : closing_snapshot || history.last_snapshot
32
- Analysis.new(
33
- diff: Engine.compare(history.first_snapshot, final),
34
- timeline: ActivityRootSteps.call(
35
- plan, history.root_snapshots,
36
- closing_snapshot: closing_snapshot, captured_at: captured_at
37
- ),
38
- activity_timeline: activity_steps(history, closing),
39
- from_snapshot: history.first_snapshot,
40
- to_snapshot: final
41
- )
42
- end
43
-
44
- private
45
-
46
- # @rbs @record: untyped
47
- # @rbs @range: TimelineRange
48
- # @rbs @tree: AssociationTree
49
- # @rbs @snapshotter: untyped
50
- # @rbs @snapshots: bool
51
-
52
- #: () -> [ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped]
53
- def history_and_versions
54
- plan = @range.select_plan(context_required: !@tree.empty?)
55
- reject_live_habtm! if plan.closing_record
56
- if plan.reconstruction_versions.empty? && !plan.closing_record
57
- return [ActivityHistory.empty, plan, nil, nil, nil]
58
- end
59
-
60
- # A window closing on current state runs to the instant it is captured,
61
- # not to the last root version, or descendants that moved after that
62
- # version would be missing from a report that claims to reach now.
63
- history_for(plan, plan.closing_record ? Time.now.utc : nil)
64
- end
65
-
66
- #: () -> void
67
- def reject_live_habtm!
68
- AssociationTraversal.new(@tree).reject_live_habtm_activity!(@record.class)
69
- end
70
-
71
- #: (RootVersionPlan, untyped) -> [ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped]
72
- def history_for(plan, captured_at)
73
- root_versions = plan.reconstruction_versions
74
- events = events_through(root_versions, captured_at)
75
- selected = selected_events(events, plan)
76
- unless time_events?(selected, events, plan)
77
- return [ActivityHistory.empty, plan, nil, nil, nil]
78
- end
79
-
80
- history = build_history(root_versions, events, current: plan.closing_record)
81
- snapshot = current_snapshot(plan)
82
- closing = closing_step(history, selected.last, plan, captured_at, snapshot)
83
- [history, plan, closing, snapshot, captured_at]
84
- end
85
-
86
- # One live read serves both the closing activity step and the endpoint the
87
- # analysis reports, which would otherwise reconstruct the graph twice.
88
- #: (RootVersionPlan) -> RecordSnapshot?
89
- def current_snapshot(plan)
90
- record = plan.closing_record
91
- return unless record
92
-
93
- @snapshotter.call(record, record)
94
- end
95
-
96
- #: (Array[untyped], untyped) -> Array[ActivityEvent]
97
- def events_through(root_versions, captured_at)
98
- prepare_history(root_versions, end_at: captured_at)
99
- collect_events(root_versions, range_end: captured_at)
100
- end
101
-
102
- # Events after the last root version read the root from the live record
103
- # when the window closes on it, as an explicit `to: record` range does.
104
- #: (Array[untyped], Array[ActivityEvent], ?current: untyped) -> ActivityHistory
105
- def build_history(root_versions, events, current: nil)
106
- ActivityHistoryBuilder.new(
107
- root_versions,
108
- events,
109
- @snapshotter,
110
- current: current,
111
- include_step: ->(event) { @range.include?(event.version) },
112
- snapshots: @retain
113
- ).call
114
- end
115
-
116
- #: (ActivityHistory, ActivityStep?) -> Array[ActivityStep]
117
- def activity_steps(history, closing)
118
- steps = closing ? history.steps + [closing] : history.steps
119
- group_steps(steps).freeze
120
- end
121
-
122
- # Root versions inside the window that the plan does not report are context
123
- # rather than mutations: the version appended to reveal the last selected
124
- # change, and anything a filter excluded but the replay still walks. Neither
125
- # may be counted as a selected mutation. Descendant events are not filtered,
126
- # so window membership is the whole test for them.
127
- #: (Array[ActivityEvent], RootVersionPlan) -> Array[ActivityEvent]
128
- def selected_events(events, plan)
129
- events.select do |event|
130
- next false unless @range.include?(event.version)
131
-
132
- !event.root? || plan.mutation?(event.version)
133
- end
134
- end
135
-
136
- # A window either closes on the absence its final destruction leaves, or on
137
- # current state when it reaches past everything recorded. Both are boundaries
138
- # no version can supply.
139
- #: (ActivityHistory, ActivityEvent?, RootVersionPlan, untyped, RecordSnapshot?) -> ActivityStep?
140
- def closing_step(history, event, plan, captured_at, snapshot)
141
- return unless event
142
- return destroyed_step(history, event) if terminal_destroy?(event)
143
- return unless plan.closing_record
144
-
145
- current_step(history, plan.closing_record, captured_at, snapshot)
146
- end
147
-
148
- #: (ActivityHistory, ActivityEvent) -> ActivityStep
149
- def destroyed_step(history, event)
150
- version = event.version
151
- ActivityStep.between(
152
- from_boundary: ActivityBoundary.from_version(version),
153
- to_boundary: ActivityBoundary.destroyed(version),
154
- from_snapshot: history.root_snapshots[ActivityRootSteps.version_key(version)],
155
- to_snapshot: nil, retain: @retain
156
- )
157
- end
158
-
159
- # The last boundary reached is where current state is compared from, which
160
- # is the final event rather than the final root version once descendants
161
- # have moved since it.
162
- #: (ActivityHistory, untyped, untyped, RecordSnapshot?) -> ActivityStep?
163
- def current_step(history, record, captured_at, snapshot)
164
- previous = history.last_boundary
165
- return unless previous
166
-
167
- ActivityStep.between(
168
- from_boundary: previous,
169
- to_boundary: ActivityBoundary.current(record, captured_at: captured_at),
170
- from_snapshot: history.last_event_snapshot, to_snapshot: snapshot, retain: @retain
171
- )
172
- end
173
-
174
- #: (ActivityEvent?) -> bool
175
- def terminal_destroy?(event)
176
- return false unless event
177
-
178
- event.root? && event.version.event.to_s == 'destroy'
179
- end
180
-
181
- #: (Array[untyped], ?end_at: untyped) -> void
182
- def prepare_history(root_versions, end_at: nil)
183
- return unless @snapshotter.respond_to?(:prepare)
184
-
185
- start_at = @range.begin_time
186
- return @snapshotter.prepare(@record, root_versions, start_at: start_at) unless end_at
187
-
188
- @snapshotter.prepare(@record, root_versions, start_at: start_at, end_at: end_at)
189
- end
190
-
191
- #: (Array[untyped], ?range_end: untyped) -> Array[ActivityEvent]
192
- def collect_events(root_versions, range_end: nil)
193
- ActivityVersionCollector.new(
194
- @record,
195
- root_versions: root_versions,
196
- tree: @tree,
197
- prepared: (@snapshotter.activity_index if @snapshotter.respond_to?(:activity_index)),
198
- range_start: @range.begin_time,
199
- range_end: range_end || root_versions.last
200
- ).call
201
- end
202
-
203
- #: (Array[ActivityEvent], Array[ActivityEvent], RootVersionPlan) -> bool
204
- def time_events?(selected, events, plan)
205
- return false if selected.empty?
206
- return true if later_event?(selected.last, events)
207
- return true if terminal_destroy?(selected.last)
208
- return true if plan.closing_record
209
-
210
- message = 'time range requires a later activity boundary to reconstruct its ' \
211
- 'final change: pass close_on: :current to end at current state, ' \
212
- 'or narrow the window to end before the last recorded boundary'
213
- raise IncompleteTimeRangeError, message
214
- end
215
-
216
- #: (ActivityEvent, Array[ActivityEvent]) -> bool
217
- def later_event?(selected, events)
218
- events.any? do |event|
219
- Support.compare_versions(selected.version, event.version).negative?
220
- end
221
- end
222
- end
223
- end
@@ -1,22 +0,0 @@
1
- # Generated from lib/paper_trail_diff/activity_grouping.rb with RBS::Inline
2
-
3
- module PaperTrailDiff
4
- # Shared by the two activity builders. They differ in how they choose
5
- # boundaries -- one from an explicit version range, one from a wall-clock
6
- # window -- but a transaction has to collapse the same way in both, or the
7
- # same history would read differently depending on how it was asked for.
8
- module ActivityGrouping
9
- # : (Symbol?, ?activity: bool, ?snapshots: bool) -> void
10
- def self.validate!: (Symbol?, ?activity: bool, ?snapshots: bool) -> void
11
-
12
- private
13
-
14
- # : () -> bool
15
- def grouping?: () -> bool
16
-
17
- # Applied last, to finished steps, so grouping sees the same timeline the
18
- # caller would otherwise have received.
19
- # : (Array[ActivityStep]) -> Array[ActivityStep]
20
- def group_steps: (Array[ActivityStep]) -> Array[ActivityStep]
21
- end
22
- end
@@ -1,30 +0,0 @@
1
- # Generated from lib/paper_trail_diff/batch_safety.rb with RBS::Inline
2
-
3
- module PaperTrailDiff
4
- # Refuses known per-record association loading before a batch reconstructs it.
5
- class BatchSafety
6
- # : (AssociationTraversal, allow_expensive: bool) -> void
7
- def initialize: (AssociationTraversal, allow_expensive: bool) -> void
8
-
9
- # : (untyped) -> void
10
- def call: (untyped) -> void
11
-
12
- private
13
-
14
- @allow_expensive: bool
15
-
16
- @traversal: AssociationTraversal
17
-
18
- # A has_many through reads its parts' conditions only, at any depth. A
19
- # has_one through over a ranked part is refused whole, so its parts, too,
20
- # are checked for conditions alone.
21
- # : (untyped, String, ?ranked: bool) -> void
22
- def check: (untyped, String, ?ranked: bool) -> void
23
-
24
- # A scope prepared history can read as conditions on stored attributes
25
- # is batched like any other path. A polymorphic target's class, and so
26
- # its scope, is known only per record.
27
- # : (untyped, bool) -> String?
28
- def reason_for: (untyped, bool) -> String?
29
- end
30
- end
@@ -1,92 +0,0 @@
1
- # Generated from lib/paper_trail_diff/time_activity_timeline_builder.rb with RBS::Inline
2
-
3
- module PaperTrailDiff
4
- # Builds activity views for mutations selected by a wall-clock range.
5
- class TimeActivityTimelineBuilder
6
- include ActivityGrouping
7
-
8
- # : (untyped, range: TimelineRange, tree: AssociationTree, snapshotter: untyped, ?snapshots: bool, ?group: Symbol?) -> void
9
- def initialize: (untyped, range: TimelineRange, tree: AssociationTree, snapshotter: untyped, ?snapshots: bool, ?group: Symbol?) -> void
10
-
11
- # : () -> Array[ActivityStep]
12
- def build: () -> Array[ActivityStep]
13
-
14
- # : () -> Analysis
15
- def analyze: () -> Analysis
16
-
17
- private
18
-
19
- @range: TimelineRange
20
-
21
- @record: untyped
22
-
23
- @snapshots: bool
24
-
25
- @snapshotter: untyped
26
-
27
- @tree: AssociationTree
28
-
29
- # : () -> [ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped]
30
- def history_and_versions: () -> [ ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped ]
31
-
32
- # : () -> void
33
- def reject_live_habtm!: () -> void
34
-
35
- # : (RootVersionPlan, untyped) -> [ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped]
36
- def history_for: (RootVersionPlan, untyped) -> [ ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped ]
37
-
38
- # One live read serves both the closing activity step and the endpoint the
39
- # analysis reports, which would otherwise reconstruct the graph twice.
40
- # : (RootVersionPlan) -> RecordSnapshot?
41
- def current_snapshot: (RootVersionPlan) -> RecordSnapshot?
42
-
43
- # : (Array[untyped], untyped) -> Array[ActivityEvent]
44
- def events_through: (Array[untyped], untyped) -> Array[ActivityEvent]
45
-
46
- # Events after the last root version read the root from the live record
47
- # when the window closes on it, as an explicit `to: record` range does.
48
- # : (Array[untyped], Array[ActivityEvent], ?current: untyped) -> ActivityHistory
49
- def build_history: (Array[untyped], Array[ActivityEvent], ?current: untyped) -> ActivityHistory
50
-
51
- # : (ActivityHistory, ActivityStep?) -> Array[ActivityStep]
52
- def activity_steps: (ActivityHistory, ActivityStep?) -> Array[ActivityStep]
53
-
54
- # Root versions inside the window that the plan does not report are context
55
- # rather than mutations: the version appended to reveal the last selected
56
- # change, and anything a filter excluded but the replay still walks. Neither
57
- # may be counted as a selected mutation. Descendant events are not filtered,
58
- # so window membership is the whole test for them.
59
- # : (Array[ActivityEvent], RootVersionPlan) -> Array[ActivityEvent]
60
- def selected_events: (Array[ActivityEvent], RootVersionPlan) -> Array[ActivityEvent]
61
-
62
- # A window either closes on the absence its final destruction leaves, or on
63
- # current state when it reaches past everything recorded. Both are boundaries
64
- # no version can supply.
65
- # : (ActivityHistory, ActivityEvent?, RootVersionPlan, untyped, RecordSnapshot?) -> ActivityStep?
66
- def closing_step: (ActivityHistory, ActivityEvent?, RootVersionPlan, untyped, RecordSnapshot?) -> ActivityStep?
67
-
68
- # : (ActivityHistory, ActivityEvent) -> ActivityStep
69
- def destroyed_step: (ActivityHistory, ActivityEvent) -> ActivityStep
70
-
71
- # The last boundary reached is where current state is compared from, which
72
- # is the final event rather than the final root version once descendants
73
- # have moved since it.
74
- # : (ActivityHistory, untyped, untyped, RecordSnapshot?) -> ActivityStep?
75
- def current_step: (ActivityHistory, untyped, untyped, RecordSnapshot?) -> ActivityStep?
76
-
77
- # : (ActivityEvent?) -> bool
78
- def terminal_destroy?: (ActivityEvent?) -> bool
79
-
80
- # : (Array[untyped], ?end_at: untyped) -> void
81
- def prepare_history: (Array[untyped], ?end_at: untyped) -> void
82
-
83
- # : (Array[untyped], ?range_end: untyped) -> Array[ActivityEvent]
84
- def collect_events: (Array[untyped], ?range_end: untyped) -> Array[ActivityEvent]
85
-
86
- # : (Array[ActivityEvent], Array[ActivityEvent], RootVersionPlan) -> bool
87
- def time_events?: (Array[ActivityEvent], Array[ActivityEvent], RootVersionPlan) -> bool
88
-
89
- # : (ActivityEvent, Array[ActivityEvent]) -> bool
90
- def later_event?: (ActivityEvent, Array[ActivityEvent]) -> bool
91
- end
92
- end