paper_trail_diff 0.3.1 → 0.4.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +61 -0
  3. data/README.md +260 -30
  4. data/lib/paper_trail_diff/activity_boundary.rb +21 -0
  5. data/lib/paper_trail_diff/activity_event_route_finder.rb +24 -44
  6. data/lib/paper_trail_diff/activity_root_steps.rb +27 -0
  7. data/lib/paper_trail_diff/activity_snapshot_sequence.rb +8 -3
  8. data/lib/paper_trail_diff/activity_timeline_builder.rb +47 -32
  9. data/lib/paper_trail_diff/analysis.rb +9 -0
  10. data/lib/paper_trail_diff/collection_comparator.rb +14 -1
  11. data/lib/paper_trail_diff/collection_transition.rb +6 -3
  12. data/lib/paper_trail_diff/endpoint.rb +27 -2
  13. data/lib/paper_trail_diff/errors.rb +3 -0
  14. data/lib/paper_trail_diff/historical_association_reifier.rb +8 -5
  15. data/lib/paper_trail_diff/historical_snapshot_store.rb +9 -2
  16. data/lib/paper_trail_diff/prepared_association_reifier.rb +8 -5
  17. data/lib/paper_trail_diff/prepared_edge_loader.rb +6 -4
  18. data/lib/paper_trail_diff/prepared_history_loader.rb +3 -2
  19. data/lib/paper_trail_diff/prepared_record_index.rb +90 -15
  20. data/lib/paper_trail_diff/snapshot.rb +26 -0
  21. data/lib/paper_trail_diff/snapshot_normalizer.rb +14 -2
  22. data/lib/paper_trail_diff/time_activity_timeline_builder.rb +52 -32
  23. data/lib/paper_trail_diff/time_version_range.rb +11 -0
  24. data/lib/paper_trail_diff/timeline_range.rb +57 -4
  25. data/lib/paper_trail_diff/version.rb +1 -1
  26. data/lib/paper_trail_diff/version_association_candidate_scope.rb +46 -19
  27. data/lib/paper_trail_diff.rb +1 -0
  28. data/sig/generated/paper_trail_diff/activity_boundary.rbs +9 -0
  29. data/sig/generated/paper_trail_diff/activity_event_route_finder.rbs +10 -17
  30. data/sig/generated/paper_trail_diff/activity_root_steps.rbs +13 -0
  31. data/sig/generated/paper_trail_diff/activity_snapshot_sequence.rbs +2 -2
  32. data/sig/generated/paper_trail_diff/activity_timeline_builder.rbs +20 -10
  33. data/sig/generated/paper_trail_diff/analysis.rbs +5 -0
  34. data/sig/generated/paper_trail_diff/collection_comparator.rbs +7 -0
  35. data/sig/generated/paper_trail_diff/collection_transition.rbs +4 -1
  36. data/sig/generated/paper_trail_diff/endpoint.rbs +12 -0
  37. data/sig/generated/paper_trail_diff/errors.rbs +4 -0
  38. data/sig/generated/paper_trail_diff/historical_association_reifier.rbs +1 -1
  39. data/sig/generated/paper_trail_diff/historical_snapshot_store.rbs +2 -2
  40. data/sig/generated/paper_trail_diff/prepared_association_reifier.rbs +1 -1
  41. data/sig/generated/paper_trail_diff/prepared_edge_loader.rbs +1 -0
  42. data/sig/generated/paper_trail_diff/prepared_history_loader.rbs +2 -2
  43. data/sig/generated/paper_trail_diff/prepared_record_index.rbs +39 -6
  44. data/sig/generated/paper_trail_diff/snapshot.rbs +20 -0
  45. data/sig/generated/paper_trail_diff/snapshot_normalizer.rbs +8 -0
  46. data/sig/generated/paper_trail_diff/time_activity_timeline_builder.rbs +21 -10
  47. data/sig/generated/paper_trail_diff/time_version_range.rbs +7 -0
  48. data/sig/generated/paper_trail_diff/timeline_range.rbs +29 -0
  49. data/sig/generated/paper_trail_diff/version_association_candidate_scope.rbs +17 -4
  50. metadata +6 -4
@@ -13,6 +13,9 @@ module PaperTrailDiff
13
13
  # : (untyped, Hash[untyped, untyped]) -> PreparedRecordState
14
14
  def self.from_attributes: (untyped, Hash[untyped, untyped]) -> PreparedRecordState
15
15
 
16
+ # Mirrors PaperTrail's reifier, which writes attributes directly. Mass
17
+ # assignment would route reconstructed state through application-defined
18
+ # attribute writers and reify a different record than the version stored.
16
19
  # : () -> untyped
17
20
  def instantiate: () -> untyped
18
21
 
@@ -73,16 +76,25 @@ module PaperTrailDiff
73
76
 
74
77
  @states: Hash[untyped, PreparedRecordState?]
75
78
 
79
+ @transaction_positions: Hash[untyped, Integer]
80
+
76
81
  @version_positions: Hash[String, Integer]
77
82
 
78
83
  # : (Integer) -> PreparedRecordState?
79
84
  def state_after: (Integer) -> PreparedRecordState?
80
85
 
81
- # : (untyped, untyped) -> bool
82
- def eligible?: (untyped, untyped) -> bool
86
+ # A version is eligible when it is at or after the boundary, or shares the
87
+ # boundary's transaction. The first is monotonic over the chronological
88
+ # list and the second is indexed, so neither rescans a long history once
89
+ # per boundary.
90
+ # : (untyped) -> Integer?
91
+ def boundary_index: (untyped) -> Integer?
83
92
 
84
- # : (untyped, untyped) -> bool
85
- def same_transaction?: (untyped, untyped) -> bool
93
+ # : (untyped) -> Integer?
94
+ def transaction_index: (untyped) -> Integer?
95
+
96
+ # : () -> Hash[untyped, Integer]
97
+ def build_transaction_positions: () -> Hash[untyped, Integer]
86
98
 
87
99
  # : (untyped) -> PreparedRecordState?
88
100
  def state_for: (untyped) -> PreparedRecordState?
@@ -90,8 +102,8 @@ module PaperTrailDiff
90
102
 
91
103
  # Request-scoped scalar history loaded once per model identity.
92
104
  class PreparedRecordIndex
93
- # : (untyped, ?live_records: Array[untyped]) -> void
94
- def initialize: (untyped, ?live_records: Array[untyped]) -> void
105
+ # : (untyped, ?end_at: untyped, ?live_records: Array[untyped]) -> void
106
+ def initialize: (untyped, ?end_at: untyped, ?live_records: Array[untyped]) -> void
95
107
 
96
108
  # : (untyped, Array[untyped]) -> void
97
109
  def load: (untyped, Array[untyped]) -> void
@@ -110,6 +122,8 @@ module PaperTrailDiff
110
122
 
111
123
  private
112
124
 
125
+ @end_time: untyped
126
+
113
127
  @seeded_live_records: Hash[Array[String], untyped]
114
128
 
115
129
  @series: Hash[Array[String], PreparedRecordSeries]
@@ -118,6 +132,10 @@ module PaperTrailDiff
118
132
 
119
133
  @state_loader: PreparedVersionStateLoader
120
134
 
135
+ # Callers bound the range with either a version or a bare timestamp.
136
+ # : (untyped) -> untyped
137
+ def boundary_time: (untyped) -> untyped
138
+
121
139
  # : (untyped, untyped) -> Array[String]
122
140
  def identity: (untyped, untyped) -> Array[String]
123
141
 
@@ -127,9 +145,24 @@ module PaperTrailDiff
127
145
  # : (untyped, untyped, Hash[String, Array[untyped]], Hash[String, untyped]) -> void
128
146
  def add_series: (untyped, untyped, Hash[String, Array[untyped]], Hash[String, untyped]) -> void
129
147
 
148
+ # A PaperTrail version is a pre-change snapshot, so the state at the last
149
+ # selected boundary can only live in the next version after it. One
150
+ # trailing version per identity is retained, rather than every version
151
+ # recorded between the requested range and the present.
130
152
  # : (untyped, Array[untyped]) -> Array[untyped]
131
153
  def versions_for: (untyped, Array[untyped]) -> Array[untyped]
132
154
 
155
+ # In range, or the earliest version after it, resolved in the same query
156
+ # rather than with a window function or one query per identity.
157
+ # : (untyped) -> untyped
158
+ def window_condition: (untyped) -> untyped
159
+
160
+ # : (untyped) -> untyped
161
+ def first_after_range: (untyped) -> untyped
162
+
163
+ # : (untyped, untyped) -> untyped
164
+ def preceding_trailing_version: (untyped, untyped) -> untyped
165
+
133
166
  # : (untyped, Array[untyped]) -> Array[untyped]
134
167
  def available_live_records: (untyped, Array[untyped]) -> Array[untyped]
135
168
 
@@ -36,6 +36,24 @@ module PaperTrailDiff
36
36
  def index: (Array[RecordSnapshot]) -> CollectionIdentityIndex
37
37
  end
38
38
 
39
+ # Identifies one snapshot for internal carry-forward. A transition cannot
40
+ # hold its origin without retaining every earlier snapshot at that path,
41
+ # and `object_id` is only guaranteed unique among live objects, so Ruby may
42
+ # hand a collected snapshot's id to a later one.
43
+ class SerialSequence
44
+ # : () -> void
45
+ def initialize: () -> void
46
+
47
+ # : () -> Integer
48
+ def next_serial: () -> Integer
49
+
50
+ @counter: Integer
51
+
52
+ @mutex: Thread::Mutex
53
+ end
54
+
55
+ SERIALS: untyped
56
+
39
57
  # : (Symbol) -> bool
40
58
  def self.collection_kind?: (Symbol) -> bool
41
59
 
@@ -43,6 +61,8 @@ module PaperTrailDiff
43
61
 
44
62
  attr_reader records: Array[RecordSnapshot]
45
63
 
64
+ attr_reader serial: Integer
65
+
46
66
  # : (kind: Symbol, records: Array[RecordSnapshot], ?identity_index_cache: untyped, ?transition: CollectionTransition?) -> void
47
67
  def initialize: (kind: Symbol, records: Array[RecordSnapshot], ?identity_index_cache: untyped, ?transition: CollectionTransition?) -> void
48
68
 
@@ -39,6 +39,8 @@ module PaperTrailDiff
39
39
 
40
40
  private
41
41
 
42
+ @excluded_attributes: Hash[Array[untyped], Array[String]]
43
+
42
44
  @ignore_policy: IgnorePolicy
43
45
 
44
46
  @pool: SnapshotPool
@@ -55,9 +57,15 @@ module PaperTrailDiff
55
57
  # : (untyped, Array[untyped], String) -> snapshot_attributes
56
58
  def normalized_attributes: (untyped, Array[untyped], String) -> snapshot_attributes
57
59
 
60
+ # Every input is fixed for one model class at one selected path, and the
61
+ # path's structural columns are recorded before its records normalize, so
62
+ # this is resolved once instead of per record per boundary.
58
63
  # : (untyped, Array[untyped], String) -> Array[String]
59
64
  def excluded_attributes: (untyped, Array[untyped], String) -> Array[String]
60
65
 
66
+ # : (untyped, Array[untyped], String) -> Array[String]
67
+ def build_excluded_attributes: (untyped, Array[untyped], String) -> Array[String]
68
+
61
69
  # : (Array[untyped]) -> Array[String]
62
70
  def relationship_columns: (Array[untyped]) -> Array[String]
63
71
 
@@ -22,8 +22,25 @@ module PaperTrailDiff
22
22
 
23
23
  @tree: AssociationTree
24
24
 
25
- # : () -> [ActivityHistory, Array[untyped]]
26
- def history_and_versions: () -> [ ActivityHistory, Array[untyped] ]
25
+ # : () -> [ActivityHistory, Array[untyped], ActivityStep?]
26
+ def history_and_versions: () -> [ ActivityHistory, Array[untyped], ActivityStep? ]
27
+
28
+ # : (Array[untyped], Array[ActivityEvent]) -> ActivityHistory
29
+ def build_history: (Array[untyped], Array[ActivityEvent]) -> ActivityHistory
30
+
31
+ # : (ActivityHistory, ActivityStep?) -> Array[ActivityStep]
32
+ def activity_steps: (ActivityHistory, ActivityStep?) -> Array[ActivityStep]
33
+
34
+ # : (Array[ActivityEvent]) -> Array[ActivityEvent]
35
+ def selected_events: (Array[ActivityEvent]) -> Array[ActivityEvent]
36
+
37
+ # The window's last selected mutation is the root's own destruction, so the
38
+ # timeline closes on the absence it leaves rather than on a later boundary.
39
+ # : (ActivityHistory, ActivityEvent?) -> ActivityStep?
40
+ def closing_step: (ActivityHistory, ActivityEvent?) -> ActivityStep?
41
+
42
+ # : (ActivityEvent?) -> bool
43
+ def terminal_destroy?: (ActivityEvent?) -> bool
27
44
 
28
45
  # : (Array[untyped]) -> void
29
46
  def prepare_history: (Array[untyped]) -> void
@@ -31,16 +48,10 @@ module PaperTrailDiff
31
48
  # : (Array[untyped]) -> Array[ActivityEvent]
32
49
  def collect_events: (Array[untyped]) -> Array[ActivityEvent]
33
50
 
34
- # : (Array[ActivityEvent]) -> bool
35
- def time_events?: (Array[ActivityEvent]) -> bool
51
+ # : (Array[ActivityEvent], Array[ActivityEvent]) -> bool
52
+ def time_events?: (Array[ActivityEvent], Array[ActivityEvent]) -> bool
36
53
 
37
54
  # : (ActivityEvent, Array[ActivityEvent]) -> bool
38
55
  def later_event?: (ActivityEvent, Array[ActivityEvent]) -> bool
39
-
40
- # : (Array[untyped], Hash[Array[untyped], RecordSnapshot?]) -> Array[Step]
41
- def root_steps: (Array[untyped], Hash[Array[untyped], RecordSnapshot?]) -> Array[Step]
42
-
43
- # : (untyped) -> Array[untyped]
44
- def version_key: (untyped) -> Array[untyped]
45
56
  end
46
57
  end
@@ -18,6 +18,13 @@ module PaperTrailDiff
18
18
  # : () -> untyped
19
19
  def versions_relation: () -> untyped
20
20
 
21
+ # A window closing on the record's own destruction needs no later version:
22
+ # the destroy reveals the preceding mutation and nothing can follow it, so
23
+ # demanding a checkpoint that can never be written would reject the range
24
+ # permanently.
25
+ # : (Array[untyped]) -> bool
26
+ def terminal_destroy?: (Array[untyped]) -> bool
27
+
21
28
  # : (untyped) -> untyped
22
29
  def trailing_version: (untyped) -> untyped
23
30
 
@@ -3,6 +3,8 @@
3
3
  module PaperTrailDiff
4
4
  # Chooses explicit-version or wall-clock selection for one timeline request.
5
5
  class TimelineRange
6
+ BOUNDARY_SYMBOLS: untyped
7
+
6
8
  attr_reader from: untyped
7
9
 
8
10
  attr_reader to: untyped
@@ -12,6 +14,11 @@ module PaperTrailDiff
12
14
  # : (untyped, from: untyped, to: untyped, within: untyped) -> void
13
15
  def initialize: (untyped, from: untyped, to: untyped, within: untyped) -> void
14
16
 
17
+ # A record with no versions has no first or last boundary to resolve, which
18
+ # is an empty history rather than a bad request.
19
+ # : () -> bool
20
+ def unresolved?: () -> bool
21
+
15
22
  # : (?context_required: bool) -> Array[untyped]
16
23
  def select: (?context_required: bool) -> Array[untyped]
17
24
 
@@ -30,8 +37,30 @@ module PaperTrailDiff
30
37
 
31
38
  @record: untyped
32
39
 
40
+ @requested_from: untyped
41
+
42
+ @requested_to: untyped
43
+
33
44
  @to: untyped
34
45
 
46
+ # : () -> Array[untyped]
47
+ def empty_versions: () -> Array[untyped]
48
+
49
+ # : (untyped) -> bool
50
+ def symbolic?: (untyped) -> bool
51
+
52
+ # Resolving `:first` and `:last` here keeps callers from depending on the
53
+ # order PaperTrail happens to give its versions association, which a caller
54
+ # is also free to reorder.
55
+ # : (untyped) -> untyped
56
+ def resolve: (untyped) -> untyped
57
+
58
+ # : () -> untyped
59
+ def ordered_versions: () -> untyped
60
+
61
+ # : () -> untyped
62
+ def versions_relation: () -> untyped
63
+
35
64
  # : (untyped) -> TimeRange?
36
65
  def build_time_range: (untyped) -> TimeRange?
37
66
 
@@ -17,14 +17,27 @@ module PaperTrailDiff
17
17
 
18
18
  @version_class: untyped
19
19
 
20
+ # Only quoting helpers are needed, so the connection is borrowed for the
21
+ # duration of building the condition rather than checked out permanently,
22
+ # which Active Record 7.2 and newer can deprecate. Active Record 7.1 has no
23
+ # `with_connection` and reaches the same connection through the accessor.
24
+ # : () { (untyped) -> String } -> String
25
+ def with_connection: () { (untyped) -> String } -> String
26
+
20
27
  # : () -> String
21
28
  def candidate_condition: () -> String
22
29
 
23
- # : (String, String) -> String
24
- def activity_condition: (String, String) -> String
30
+ # : (untyped) -> String
31
+ def condition_sql: (untyped) -> String
32
+
33
+ # : (untyped, String, String) -> Hash[Symbol, String]
34
+ def quoted_columns: (untyped, String, String) -> Hash[Symbol, String]
35
+
36
+ # : (Hash[Symbol, String], String, untyped) -> String
37
+ def activity_condition: (Hash[Symbol, String], String, untyped) -> String
25
38
 
26
- # : (String, String) -> String
27
- def column: (String, String) -> String
39
+ # : (untyped, String, String) -> String
40
+ def column: (untyped, String, String) -> String
28
41
 
29
42
  # : (String) -> String
30
43
  def compact_sql: (String) -> String
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.3.1
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Williams
@@ -59,6 +59,7 @@ files:
59
59
  - lib/paper_trail_diff/activity_range.rb
60
60
  - lib/paper_trail_diff/activity_relationship.rb
61
61
  - lib/paper_trail_diff/activity_root_snapshot_refresher.rb
62
+ - lib/paper_trail_diff/activity_root_steps.rb
62
63
  - lib/paper_trail_diff/activity_snapshot_delta.rb
63
64
  - lib/paper_trail_diff/activity_snapshot_sequence.rb
64
65
  - lib/paper_trail_diff/activity_timeline_builder.rb
@@ -126,6 +127,7 @@ files:
126
127
  - sig/generated/paper_trail_diff/activity_range.rbs
127
128
  - sig/generated/paper_trail_diff/activity_relationship.rbs
128
129
  - sig/generated/paper_trail_diff/activity_root_snapshot_refresher.rbs
130
+ - sig/generated/paper_trail_diff/activity_root_steps.rbs
129
131
  - sig/generated/paper_trail_diff/activity_snapshot_delta.rbs
130
132
  - sig/generated/paper_trail_diff/activity_snapshot_sequence.rbs
131
133
  - sig/generated/paper_trail_diff/activity_timeline_builder.rbs
@@ -182,11 +184,11 @@ licenses:
182
184
  metadata:
183
185
  allowed_push_host: https://rubygems.org
184
186
  bug_tracker_uri: https://github.com/aheathwilliams/paper_trail_diff/issues
185
- changelog_uri: https://github.com/aheathwilliams/paper_trail_diff/blob/v0.3.1/CHANGELOG.md
186
- documentation_uri: https://github.com/aheathwilliams/paper_trail_diff/blob/v0.3.1/README.md
187
+ changelog_uri: https://github.com/aheathwilliams/paper_trail_diff/blob/v0.4.0/CHANGELOG.md
188
+ documentation_uri: https://github.com/aheathwilliams/paper_trail_diff/blob/v0.4.0/README.md
187
189
  homepage_uri: https://github.com/aheathwilliams/paper_trail_diff
188
190
  rubygems_mfa_required: 'true'
189
- source_code_uri: https://github.com/aheathwilliams/paper_trail_diff/tree/v0.3.1
191
+ source_code_uri: https://github.com/aheathwilliams/paper_trail_diff/tree/v0.4.0
190
192
  rdoc_options: []
191
193
  require_paths:
192
194
  - lib