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
@@ -1,13 +1,16 @@
1
1
  # Generated from lib/paper_trail_diff/historical_snapshot_store.rb with RBS::Inline
2
2
 
3
3
  module PaperTrailDiff
4
- # Reconstructs and caches immutable historical snapshots for one adapter configuration.
4
+ # Reconstructs and caches immutable historical snapshots for one adapter
5
+ # configuration. It keeps the histories batches prepare, by root version;
6
+ # one prepared for a single root goes back to the caller, who passes it to
7
+ # every read for that root.
5
8
  class HistoricalSnapshotStore
6
9
  # : (tree: AssociationTree, traversal: AssociationTraversal, normalizer: SnapshotNormalizer, preparer: untyped) -> void
7
10
  def initialize: (tree: AssociationTree, traversal: AssociationTraversal, normalizer: SnapshotNormalizer, preparer: untyped) -> void
8
11
 
9
- # : (untyped, Array[untyped], ?start_at: untyped, ?end_at: untyped) -> void
10
- def prepare: (untyped, Array[untyped], ?start_at: untyped, ?end_at: untyped) -> void
12
+ # : (untyped, Array[untyped], ?start_at: untyped, ?end_at: untyped) -> PreparedHistory?
13
+ def prepare: (untyped, Array[untyped], ?start_at: untyped, ?end_at: untyped) -> PreparedHistory?
11
14
 
12
15
  # : (Array[untyped], Array[untyped], ?start_at: untyped, ?end_at: untyped) -> void
13
16
  def prepare_batch: (Array[untyped], Array[untyped], ?start_at: untyped, ?end_at: untyped) -> void
@@ -18,37 +21,29 @@ module PaperTrailDiff
18
21
  # : (Array[untyped], Array[untyped], ?start_at: untyped, ?end_at: untyped) -> void
19
22
  def prepare_deleted_batch: (Array[untyped], Array[untyped], ?start_at: untyped, ?end_at: untyped) -> void
20
23
 
21
- # : (untyped, untyped) -> RecordSnapshot?
22
- def call: (untyped, untyped) -> RecordSnapshot?
24
+ # `history` is the one `prepare` returned for this root, if any.
25
+ # : (untyped, untyped, ?history: PreparedHistory?) -> RecordSnapshot?
26
+ def call: (untyped, untyped, ?history: PreparedHistory?) -> RecordSnapshot?
23
27
 
24
- # : (untyped, untyped) -> RecordSnapshot?
25
- def uncached: (untyped, untyped) -> RecordSnapshot?
28
+ # : (untyped, untyped, ?history: PreparedHistory?) -> RecordSnapshot?
29
+ def uncached: (untyped, untyped, ?history: PreparedHistory?) -> RecordSnapshot?
26
30
 
27
- # : (untyped, untyped, tree: AssociationTree, normalizer: SnapshotNormalizer) -> RecordSnapshot?
28
- def custom: (untyped, untyped, tree: AssociationTree, normalizer: SnapshotNormalizer) -> RecordSnapshot?
31
+ # : (untyped, untyped, tree: AssociationTree, normalizer: SnapshotNormalizer, ?history: PreparedHistory?) -> RecordSnapshot?
32
+ def custom: (untyped, untyped, tree: AssociationTree, normalizer: SnapshotNormalizer, ?history: PreparedHistory?) -> RecordSnapshot?
29
33
 
30
- # : (untyped, habtm_version: untyped) -> untyped
31
- def association_reader: (untyped, habtm_version: untyped) -> untyped
32
-
33
- # : () -> PreparedActivityIndex?
34
- def activity_index: () -> PreparedActivityIndex?
35
-
36
- # : (untyped) -> [Hash[untyped, untyped], Hash[untyped, untyped]]?
37
- def record_transition: (untyped) -> [ Hash[untyped, untyped], Hash[untyped, untyped] ]?
38
-
39
- # : (untyped, ?direct: bool) -> [bool, untyped]
40
- def record_after: (untyped, ?direct: bool) -> [ bool, untyped ]
34
+ # : (untyped, habtm_version: untyped, ?history: PreparedHistory?) -> untyped
35
+ def association_reader: (untyped, habtm_version: untyped, ?history: PreparedHistory?) -> untyped
41
36
 
42
37
  private
43
38
 
44
39
  @live_graph_collector: LiveGraphCollector
45
40
 
41
+ @live_roots: Hash[identity, untyped]
42
+
46
43
  @normalizer: SnapshotNormalizer
47
44
 
48
45
  @prepared_histories: Hash[Array[untyped], PreparedHistory]
49
46
 
50
- @prepared_history: PreparedHistory?
51
-
52
47
  @prepared_snapshotter: PreparedSnapshotBuilder
53
48
 
54
49
  @preparer: untyped
@@ -59,6 +54,10 @@ module PaperTrailDiff
59
54
 
60
55
  @tree: AssociationTree
61
56
 
57
+ # A batch's history for the version wins over the one a caller passes.
58
+ # : (untyped, PreparedHistory?) -> PreparedHistory?
59
+ def history_for: (untyped, PreparedHistory?) -> PreparedHistory?
60
+
62
61
  # : (untyped) -> Array[untyped]
63
62
  def context_key: (untyped) -> Array[untyped]
64
63
 
@@ -68,14 +67,20 @@ module PaperTrailDiff
68
67
  # : (Array[untyped], Array[untyped], ?live_records: Array[untyped], ?start_at: untyped, ?end_at: untyped) -> void
69
68
  def register_batch: (Array[untyped], Array[untyped], ?live_records: Array[untyped], ?start_at: untyped, ?end_at: untyped) -> void
70
69
 
71
- # : (untyped, untyped, tree: AssociationTree, normalizer: SnapshotNormalizer) -> RecordSnapshot?
72
- def snapshot_from_version: (untyped, untyped, tree: AssociationTree, normalizer: SnapshotNormalizer) -> RecordSnapshot?
70
+ # : (untyped, untyped, tree: AssociationTree, normalizer: SnapshotNormalizer, history: PreparedHistory?) -> RecordSnapshot?
71
+ def snapshot_from_version: (untyped, untyped, tree: AssociationTree, normalizer: SnapshotNormalizer, history: PreparedHistory?) -> RecordSnapshot?
72
+
73
+ # : (untyped, untyped, tree: AssociationTree, normalizer: SnapshotNormalizer, history: PreparedHistory?) -> RecordSnapshot?
74
+ def snapshot_from_live_root: (untyped, untyped, tree: AssociationTree, normalizer: SnapshotNormalizer, history: PreparedHistory?) -> RecordSnapshot?
73
75
 
74
- # : (untyped, untyped, tree: AssociationTree, normalizer: SnapshotNormalizer) -> RecordSnapshot?
75
- def snapshot_from_live_root: (untyped, untyped, tree: AssociationTree, normalizer: SnapshotNormalizer) -> RecordSnapshot?
76
+ # Rebuilding a version's associations writes them onto the record, so each
77
+ # snapshot gets its own copy of a batch's loaded row, built from the row's
78
+ # stored values without a query.
79
+ # : (untyped) -> untyped
80
+ def live_root: (untyped) -> untyped
76
81
 
77
- # : (untyped, untyped, tree: AssociationTree, normalizer: SnapshotNormalizer, habtm_version: untyped) -> RecordSnapshot?
78
- def normalize: (untyped, untyped, tree: AssociationTree, normalizer: SnapshotNormalizer, habtm_version: untyped) -> RecordSnapshot?
82
+ # : (untyped, untyped, tree: AssociationTree, normalizer: SnapshotNormalizer, habtm_version: untyped, history: PreparedHistory?) -> RecordSnapshot?
83
+ def normalize: (untyped, untyped, tree: AssociationTree, normalizer: SnapshotNormalizer, habtm_version: untyped, history: PreparedHistory?) -> RecordSnapshot?
79
84
 
80
85
  # : (untyped, untyped) -> Array[untyped]
81
86
  def snapshot_key: (untyped, untyped) -> Array[untyped]
@@ -39,21 +39,6 @@ module PaperTrailDiff
39
39
  # : () -> untyped
40
40
  def active_record_preloader: () -> untyped
41
41
 
42
- # The preloader applies a limit or offset to all owners' records at once,
43
- # not to each owner's, so such an association is loaded per record. A
44
- # has_one is no exception. Through a part with a limit, an offset or a
45
- # has_one cut, the preloader keeps what the part alone would load, while
46
- # the through's own query keeps the part's conditions only.
47
- # : (untyped, untyped) -> bool
48
- def batch_safe?: (untyped, untyped) -> bool
49
-
50
- # : (untyped) -> bool
51
- def cut_part?: (untyped) -> bool
52
-
53
- # Every part a through passes, at any depth.
54
- # : (untyped) -> Array[untyped]
55
- def through_parts: (untyped) -> Array[untyped]
56
-
57
42
  # : (Array[untyped], untyped) -> Array[untyped]
58
43
  def load_individually: (Array[untyped], untyped) -> Array[untyped]
59
44
 
@@ -43,6 +43,7 @@ module PaperTrailDiff
43
43
  # Returns the changed paths, or an empty hash when the pair is not two
44
44
  # structures this can look inside. An empty result therefore means "nothing
45
45
  # to report at this depth", and the caller still has the whole-value change.
46
+ # Two arrays at the top are one membership change, at the empty path.
46
47
  # : () -> nested_changes
47
48
  def call: () -> nested_changes
48
49
 
@@ -52,14 +53,16 @@ module PaperTrailDiff
52
53
 
53
54
  @to_value: untyped
54
55
 
55
- # Both sides have to be readable as a Hash for a nested answer to mean
56
- # anything. A column that held text on one side and JSON on the other
57
- # changed wholesale, and saying so is the accurate report.
58
- # : () -> [Hash[untyped, untyped]?, Hash[untyped, untyped]?]
59
- def structures: () -> [ Hash[untyped, untyped]?, Hash[untyped, untyped]? ]
56
+ # Both sides have to be readable as the same kind of structure for a
57
+ # nested answer to mean anything. A column that held text on one side and
58
+ # JSON on the other changed wholesale, and saying so is the accurate report.
59
+ # : () -> [untyped, untyped]
60
+ def structures: () -> [ untyped, untyped ]
60
61
 
61
- # : (untyped) -> Hash[untyped, untyped]?
62
- def structure: (untyped) -> Hash[untyped, untyped]?
62
+ # A string is read as JSON; anything else, or JSON that is neither an
63
+ # object nor an array, is left for #call to decline.
64
+ # : (untyped) -> untyped
65
+ def structure: (untyped) -> untyped
63
66
 
64
67
  # : (Hash[untyped, untyped], Hash[untyped, untyped], Array[String], nested_changes) -> void
65
68
  def walk: (Hash[untyped, untyped], Hash[untyped, untyped], Array[String], nested_changes) -> void
@@ -6,11 +6,10 @@ module PaperTrailDiff
6
6
  # the only place allowed to know about both PaperTrail and the pure engine.
7
7
  # Reconstruction logic lives in the collaborators it wires together.
8
8
  class PaperTrailAdapter
9
- # The only thing a wall-clock window can close on besides a later version.
10
- CLOSE_ON_CURRENT: ::Symbol
11
-
12
- # : (associations: Array[String | Symbol], ignore: ignore_option, ?reload_live_endpoints: bool, ?allow_expensive: bool) -> void
13
- def initialize: (associations: Array[String | Symbol], ignore: ignore_option, ?reload_live_endpoints: bool, ?allow_expensive: bool) -> void
9
+ # `recorded_changes:` reads what each activity boundary's version recorded.
10
+ # `allow_approximate:` accepts association paths read with PT-AT.
11
+ # : (associations: association_option, ignore: ignore_option, ?reload_live_endpoints: bool, ?recorded_changes: bool, ?allow_approximate: bool) -> void
12
+ def initialize: (associations: association_option, ignore: ignore_option, ?reload_live_endpoints: bool, ?recorded_changes: bool, ?allow_approximate: bool) -> void
14
13
 
15
14
  # : (untyped, untyped) -> Diff
16
15
  def compare: (untyped, untyped) -> Diff
@@ -19,44 +18,49 @@ module PaperTrailDiff
19
18
  # : (Array[comparison_input]) -> comparison_results
20
19
  def compare_many: (Array[comparison_input]) -> comparison_results
21
20
 
22
- # : (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?close_on: Symbol?) -> Array[Step]
23
- def timeline: (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?close_on: Symbol?) -> Array[Step]
21
+ # : (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped) -> Array[Step]
22
+ def timeline: (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped) -> Array[Step]
24
23
 
25
- # : (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?) -> Array[ActivityStep]
26
- def activity_timeline: (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?) -> Array[ActivityStep]
24
+ # : (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?snapshots: bool, ?group: Symbol?) -> Array[ActivityStep]
25
+ def activity_timeline: (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?snapshots: bool, ?group: Symbol?) -> Array[ActivityStep]
27
26
 
28
- # : (untyped, from: untyped, to: untyped, within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?) -> Analysis
29
- def analyze: (untyped, from: untyped, to: untyped, within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?) -> Analysis
27
+ # : (untyped, from: untyped, to: untyped, within: untyped, ?activity: bool, ?version_scope: untyped, ?snapshots: bool, ?group: Symbol?) -> Analysis
28
+ def analyze: (untyped, from: untyped, to: untyped, within: untyped, ?activity: bool, ?version_scope: untyped, ?snapshots: bool, ?group: Symbol?) -> Analysis
30
29
 
31
30
  # Analyzes many roots over one shared range, preparing their history once.
32
- # : (Array[untyped], within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?) -> Hash[identity, Analysis]
33
- def analyze_many: (Array[untyped], within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?) -> Hash[identity, Analysis]
31
+ # : (Array[untyped], within: untyped, ?activity: bool, ?version_scope: untyped, ?snapshots: bool, ?group: Symbol?) -> Hash[identity, Analysis]
32
+ def analyze_many: (Array[untyped], within: untyped, ?activity: bool, ?version_scope: untyped, ?snapshots: bool, ?group: Symbol?) -> Hash[identity, Analysis]
34
33
 
35
34
  # Selects the roots from a relation before running the ordinary batch, so a
36
35
  # caller reporting on a population does not have to rediscover which of its
37
36
  # members changed. The roots the relation could not reach come back named
38
37
  # rather than dropped -- see `PaperTrailDiff.analyze_scope`.
39
- # : (untyped, limit: Integer?, within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?, ?historical_filter: untyped, ?max_filter_versions: Integer, ?include_deleted: bool) -> ScopedAnalysis
40
- def analyze_scope: (untyped, limit: Integer?, within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?, ?historical_filter: untyped, ?max_filter_versions: Integer, ?include_deleted: bool) -> ScopedAnalysis
38
+ # : (untyped, limit: Integer?, within: untyped, ?activity: bool, ?version_scope: untyped, ?snapshots: bool, ?group: Symbol?, ?historical_filter: untyped, ?max_filter_versions: Integer, ?include_deleted: bool) -> ScopedAnalysis
39
+ def analyze_scope: (untyped, limit: Integer?, within: untyped, ?activity: bool, ?version_scope: untyped, ?snapshots: bool, ?group: Symbol?, ?historical_filter: untyped, ?max_filter_versions: Integer, ?include_deleted: bool) -> ScopedAnalysis
41
40
 
42
41
  private
43
42
 
44
- # Selecting by activity reads association history, so it must be available
45
- # before the first selection query rather than once analysis starts.
46
- # : (untyped, bool) -> void
47
- def check_scope: (untyped, bool) -> void
43
+ # : (bool, bool, Symbol?) -> ActivityView
44
+ def activity_view: (bool, bool, Symbol?) -> ActivityView
45
+
46
+ # : (untyped) -> Hash[Symbol, untyped]
47
+ def model_payload: (untyped) -> Hash[Symbol, untyped]
48
+
49
+ # : (Array[untyped], within: untyped, activity: bool, version_scope: untyped, view: ActivityView) -> Hash[identity, Analysis]
50
+ def analyze_batch: (Array[untyped], within: untyped, activity: bool, version_scope: untyped, view: ActivityView) -> Hash[identity, Analysis]
51
+
52
+ # Analysis reads association history, so a request it would refuse is
53
+ # refused before the first selection query rather than once analysis starts.
54
+ # : (untyped) -> void
55
+ def check_scope: (untyped) -> void
48
56
 
49
57
  # : (Array[comparison_input], preparer: untyped) -> ComparisonBatch
50
58
  def comparison_batch: (Array[comparison_input], preparer: untyped) -> ComparisonBatch
51
59
 
60
+ # Refused before the current records load, rather than once analysis starts.
52
61
  # : (Array[untyped]) -> Hash[identity, untyped]
53
62
  def load_batch_endpoints: (Array[untyped]) -> Hash[identity, untyped]
54
63
 
55
- # : (untyped, historical: bool) -> void
56
- def prepare_batch_model: (untyped, historical: bool) -> void
57
-
58
- @batch_safety: BatchSafety
59
-
60
64
  # : (untyped, Array[Array[String]], Hash[Symbol, untyped]) -> Hash[Array[String], Analysis?]
61
65
  def analyze_deleted: (untyped, Array[Array[String]], Hash[Symbol, untyped]) -> Hash[Array[String], Analysis?]
62
66
 
@@ -74,6 +78,8 @@ module PaperTrailDiff
74
78
 
75
79
  @normalizer: SnapshotNormalizer
76
80
 
81
+ @recorded: IgnorePolicy?
82
+
77
83
  @snapshot_pool: SnapshotPool
78
84
 
79
85
  @timeline_snapshotter: TimelineSnapshotProvider
@@ -82,24 +88,19 @@ module PaperTrailDiff
82
88
 
83
89
  @traversal_preparer: TraversalPreparer
84
90
 
85
- # : (untyped, from: untyped, to: untyped, within: untyped, version_scope: untyped, live_endpoint: untyped, ?snapshots: bool, ?group: Symbol?) -> Analysis
86
- def analyze_activity: (untyped, from: untyped, to: untyped, within: untyped, version_scope: untyped, live_endpoint: untyped, ?snapshots: bool, ?group: Symbol?) -> Analysis
87
-
88
- # Association membership is resolved by timestamp, so endpoints sharing one
89
- # cannot be told apart and any association change between them is invisible.
90
- # The result may still be correct -- nothing associated may have changed --
91
- # and the gem cannot tell which, since not seeing the change is the symptom.
92
- # So it reports the condition and leaves the judgement to the application.
91
+ # The result may still be correct -- nothing the path reaches may have
92
+ # changed -- and the gem cannot tell which, since not seeing the change is
93
+ # the symptom. So it reports the condition and leaves the judgement to the
94
+ # application.
93
95
  # : (untyped, untyped) -> void
94
96
  def notify_ambiguous_association_boundary: (untyped, untyped) -> void
95
97
 
96
- # A window with nothing recorded after its last change ends at the live
97
- # record, which holds the state that change produced -- whether the window
98
- # ended a moment ago or long before. `close_on: :current` asked for this
99
- # before it was the default and is still accepted. It names what ends a
100
- # wall-clock window, so it is meaningless for explicit endpoints.
101
- # : (Symbol?, untyped) -> bool
102
- def close_on_current?: (Symbol?, untyped) -> bool
98
+ # Loaded history orders two versions that share a timestamp by id, but
99
+ # PT-AT places association changes by timestamp alone, so only a path it
100
+ # reads can lose a change between them. A request the preparer refuses
101
+ # raises here, before anything is reported.
102
+ # : (untyped, untyped) -> bool
103
+ def ambiguous_association_boundary?: (untyped, untyped) -> bool
103
104
 
104
105
  # With neither a window nor endpoints, a range is the record's whole
105
106
  # history through its current saved state; a missing endpoint takes that
@@ -107,26 +108,30 @@ module PaperTrailDiff
107
108
  # : (untyped, untyped, untyped, untyped) -> [untyped, untyped]
108
109
  def default_range: (untyped, untyped, untyped, untyped) -> [ untyped, untyped ]
109
110
 
111
+ # A window with nothing recorded after its last change ends at the live
112
+ # record, which holds the state that change produced -- whether the window
113
+ # ended a moment ago or long before. Explicit endpoints name their own end.
110
114
  # Only a saved row has current state to close on. A destroyed root's own
111
115
  # destroy version already terminates its history, and a reified or unsaved
112
116
  # instance stands for a state that is not in the table.
113
- # : (untyped, Symbol?, untyped) -> untyped
114
- def live_endpoint_for: (untyped, Symbol?, untyped) -> untyped
117
+ # : (untyped, untyped) -> untyped
118
+ def live_endpoint_for: (untyped, untyped) -> untyped
115
119
 
116
120
  # : () -> void
117
121
  def build_snapshotters: () -> void
118
122
 
119
- # : (bool, within: untyped, snapshots: bool, group: Symbol?) -> BatchedRootAnalyzer
120
- def batched_root_analyzer: (bool, within: untyped, snapshots: bool, group: Symbol?) -> BatchedRootAnalyzer
123
+ # : (bool, within: untyped, view: ActivityView) -> BatchedRootAnalyzer
124
+ def batched_root_analyzer: (bool, within: untyped, view: ActivityView) -> BatchedRootAnalyzer
121
125
 
122
126
  # : () -> HistoricalSnapshotStore
123
127
  def build_historical_store: () -> HistoricalSnapshotStore
124
128
 
125
- # : () -> ActivitySnapshotProvider
126
- def build_activity_snapshotter: () -> ActivitySnapshotProvider
129
+ # Refreshes one root's snapshots, reading with the history prepared for it.
130
+ # : (PreparedActivitySnapshots) -> BranchSnapshotRefresher
131
+ def branch_refresher: (PreparedActivitySnapshots) -> BranchSnapshotRefresher
127
132
 
128
- # : (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?live_endpoint: untyped, ?snapshots: bool, ?group: Symbol?) -> ActivityTimelineBuilder
129
- def activity_builder: (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?live_endpoint: untyped, ?snapshots: bool, ?group: Symbol?) -> ActivityTimelineBuilder
133
+ # : (untyped, from: untyped, to: untyped, within: untyped, version_scope: untyped, live_endpoint: untyped, view: ActivityView) -> ActivityTimelineBuilder
134
+ def activity_builder: (untyped, from: untyped, to: untyped, within: untyped, version_scope: untyped, live_endpoint: untyped, view: ActivityView) -> ActivityTimelineBuilder
130
135
 
131
136
  # : (untyped) -> RecordSnapshot?
132
137
  def snapshot_for_endpoint: (untyped) -> RecordSnapshot?
@@ -134,11 +139,8 @@ module PaperTrailDiff
134
139
  # : (untyped) -> RecordSnapshot?
135
140
  def historical_snapshot: (untyped) -> RecordSnapshot?
136
141
 
137
- # : (untyped) -> RecordSnapshot?
138
- def uncached_historical_snapshot: (untyped) -> RecordSnapshot?
139
-
140
- # : (untyped, untyped) -> RecordSnapshot?
141
- def snapshot_at: (untyped, untyped) -> RecordSnapshot?
142
+ # : (untyped, untyped, ?history: PreparedHistory?) -> RecordSnapshot?
143
+ def snapshot_at: (untyped, untyped, ?history: PreparedHistory?) -> RecordSnapshot?
142
144
 
143
145
  # : (untyped) -> RecordSnapshot
144
146
  def live_snapshot: (untyped) -> RecordSnapshot
@@ -1,7 +1,8 @@
1
1
  # Generated from lib/paper_trail_diff/preloaded_endpoint_batch_loader.rb with RBS::Inline
2
2
 
3
3
  module PaperTrailDiff
4
- # Validates and reuses caller-owned live records without issuing association queries.
4
+ # Validates and reuses caller-owned live records, reading again only the
5
+ # associations a preload of many records cannot have loaded correctly.
5
6
  class PreloadedEndpointBatchLoader
6
7
  # : (tree: AssociationTree, traversal: AssociationTraversal) -> void
7
8
  def initialize: (tree: AssociationTree, traversal: AssociationTraversal) -> void
@@ -20,5 +21,12 @@ module PaperTrailDiff
20
21
 
21
22
  # : (untyped, AssociationTree, String, untyped) -> void
22
23
  def validate_association: (untyped, AssociationTree, String, untyped) -> void
24
+
25
+ # Preloading many records applies a limit or offset to all of them
26
+ # together, so what a caller preloaded cannot be trusted: one record keeps
27
+ # another's share, or none. Such an association is read again for each
28
+ # record, as reloading reads it.
29
+ # : (untyped) -> void
30
+ def read_alone: (untyped) -> void
23
31
  end
24
32
  end
@@ -74,6 +74,11 @@ module PaperTrailDiff
74
74
  # : (Hash[Symbol, untyped], untyped) -> Array[untyped]
75
75
  def child_ids_for: (Hash[Symbol, untyped], untyped) -> Array[untyped]
76
76
 
77
+ # PT-AT records a checkpoint's membership as it is when the checkpoint is
78
+ # saved, so a tag attached earlier in the checkpoint's own transaction is a
79
+ # member there, unlike other associations, which a version reads as they
80
+ # were before its transaction. A member created in that transaction has no
81
+ # earlier state, so it is read as it was created.
77
82
  # : (untyped, untyped, untyped, untyped, bool) -> Array[untyped]?
78
83
  def habtm_records: (untyped, untyped, untyped, untyped, bool) -> Array[untyped]?
79
84
 
@@ -68,6 +68,12 @@ module PaperTrailDiff
68
68
  # : (untyped) -> untyped
69
69
  def record_before: (untyped) -> untyped
70
70
 
71
+ # A record created in the boundary's own transaction has no state before
72
+ # it, yet a HABTM checkpoint saved later in that transaction records it as
73
+ # a member. Such a member is read as it was created.
74
+ # : (untyped) -> untyped
75
+ def record_created_in: (untyped) -> untyped
76
+
71
77
  # : (untyped) -> PreparedRecordState?
72
78
  def state_before: (untyped) -> PreparedRecordState?
73
79
 
@@ -90,8 +96,8 @@ module PaperTrailDiff
90
96
  # : (untyped) -> Array[PreparedRecordState]
91
97
  def states_around: (untyped) -> Array[PreparedRecordState]
92
98
 
93
- # : (untyped) -> [Hash[untyped, untyped], Hash[untyped, untyped]]?
94
- def transition: (untyped) -> [ Hash[untyped, untyped], Hash[untyped, untyped] ]?
99
+ # : (untyped) -> [PreparedRecordState, PreparedRecordState]?
100
+ def transition: (untyped) -> [ PreparedRecordState, PreparedRecordState ]?
95
101
 
96
102
  private
97
103
 
@@ -144,9 +150,15 @@ module PaperTrailDiff
144
150
  # : (untyped, untyped, untyped) -> untyped
145
151
  def record_before: (untyped, untyped, untyped) -> untyped
146
152
 
153
+ # : (untyped, untyped, untyped) -> untyped
154
+ def record_created_in: (untyped, untyped, untyped) -> untyped
155
+
147
156
  # : (untyped, untyped, untyped) -> [bool, PreparedRecordState?]
148
157
  def snapshot_state_before: (untyped, untyped, untyped) -> [ bool, PreparedRecordState? ]
149
158
 
159
+ # Typed as a reified record's attributes are. A JSON or JSONB `object`
160
+ # column stores times and dates as strings, which PaperTrail does not cast
161
+ # back, and the transition is carried into snapshots built from records.
150
162
  # : (untyped, untyped, untyped) -> [Hash[untyped, untyped], Hash[untyped, untyped]]?
151
163
  def transition: (untyped, untyped, untyped) -> [ Hash[untyped, untyped], Hash[untyped, untyped] ]?
152
164
 
@@ -173,6 +185,11 @@ module PaperTrailDiff
173
185
 
174
186
  private
175
187
 
188
+ # A model whose records need their own initialization is cast by
189
+ # instantiating it, as reify does.
190
+ # : (PreparedRecordState) -> Hash[untyped, untyped]
191
+ def typed_attributes: (PreparedRecordState) -> Hash[untyped, untyped]
192
+
176
193
  # : (untyped, String) -> bool
177
194
  def generated_reader?: (untyped, String) -> bool
178
195
 
@@ -13,6 +13,8 @@ module PaperTrailDiff
13
13
  # An error is a specification, so it names the way out as well as the wall.
14
14
  # A window ending at the present can never gain a later version, which makes
15
15
  # "write a checkpoint afterwards" impossible advice on its own.
16
+ # A window closes on the current record when it reaches the present, so
17
+ # this is raised only when there is none to read.
16
18
  INCOMPLETE: ::String
17
19
 
18
20
  # : (in_range: Array[untyped], selected: Array[untyped], after_range: untyped, windowed: bool, ?context_required: bool, ?filtered: bool, ?live_endpoint: untyped) -> void
@@ -13,6 +13,9 @@ module PaperTrailDiff
13
13
  # ranking alone.
14
14
  RELATION_VALUES: untyped
15
15
 
16
+ # Relation parts that load or shape rows without choosing them.
17
+ LOADING_VALUES: untyped
18
+
16
19
  # Types whose stored values compare in Ruby as the database compares them,
17
20
  # named rather than referenced so that loading needs no Active Model.
18
21
  VALUE_TYPES: untyped
@@ -47,6 +50,15 @@ module PaperTrailDiff
47
50
  # : (untyped, ?untyped, ?ranked: bool) -> ScopeCondition?
48
51
  def self.build: (untyped, ?untyped, ?ranked: bool) -> ScopeCondition?
49
52
 
53
+ # A relation's own where clause, default scope included, read as
54
+ # conditions on stored attributes; nil when it cannot be. Its order,
55
+ # limit, and offset choose a page of current rows rather than membership,
56
+ # and loading or selecting columns changes neither, so they are left out.
57
+ # A single-table subclass's type condition is left to the caller, as for
58
+ # an association.
59
+ # : (untyped, untyped) -> ScopeCondition?
60
+ def self.for_relation: (untyped, untyped) -> ScopeCondition?
61
+
50
62
  # nil when the relation picks its members by conditions alone.
51
63
  # : (untyped, untyped, untyped) -> ScopeRanking?
52
64
  def self.ranking: (untyped, untyped, untyped) -> ScopeRanking?
@@ -78,6 +90,8 @@ module PaperTrailDiff
78
90
  # : (untyped, Array[untyped], ?ScopeRanking?) -> void
79
91
  def initialize: (untyped, Array[untyped], ?ScopeRanking?) -> void
80
92
 
93
+ # SQL admits a row only when its condition is true, not when it is
94
+ # unknown.
81
95
  # : (Hash[String, untyped]) -> bool
82
96
  def matches?: (Hash[String, untyped]) -> bool
83
97
 
@@ -91,8 +105,10 @@ module PaperTrailDiff
91
105
  # : (Array[untyped]) -> Array[untyped]
92
106
  def rank: (Array[untyped]) -> Array[untyped]
93
107
 
94
- # SQL compares NULL with nothing, so only an IS NULL test, an equality
95
- # with nil, matches it.
108
+ # SQL's three values: a comparison with NULL is unknown (nil) rather than
109
+ # false, except an IS NULL or IS NOT NULL test, an equality with nil, which
110
+ # is always known. Unknown matters only under NOT, which leaves it
111
+ # unknown where false would become true.
96
112
  COMPARISONS: untyped
97
113
 
98
114
  private
@@ -103,8 +119,20 @@ module PaperTrailDiff
103
119
 
104
120
  @ranking: ScopeRanking?
105
121
 
106
- # : (Array[untyped], Hash[String, untyped]) -> bool
107
- def evaluate: (Array[untyped], Hash[String, untyped]) -> bool
122
+ # true, false, or nil for unknown.
123
+ # : (Array[untyped], Hash[String, untyped]) -> bool?
124
+ def evaluate: (Array[untyped], Hash[String, untyped]) -> bool?
125
+
126
+ # False if any part is, otherwise unknown if any part is.
127
+ # : (Array[bool?]) -> bool?
128
+ def all: (Array[bool?]) -> bool?
129
+
130
+ # True if any part is, otherwise unknown if any part is.
131
+ # : (Array[bool?]) -> bool?
132
+ def any: (Array[bool?]) -> bool?
133
+
134
+ # : (bool?) -> bool?
135
+ def negate: (bool?) -> bool?
108
136
 
109
137
  # The form the database holds, as the scope's own values were cast.
110
138
  # : (String, Hash[String, untyped]) -> untyped
@@ -146,6 +174,7 @@ module PaperTrailDiff
146
174
  # : (untyped) -> Array[untyped]
147
175
  def comparison: (untyped) -> Array[untyped]
148
176
 
177
+ # Whether a value is NULL compares alike on every type.
149
178
  # : (Symbol, untyped) -> Array[untyped]
150
179
  def binary: (Symbol, untyped) -> Array[untyped]
151
180
 
@@ -158,12 +187,17 @@ module PaperTrailDiff
158
187
  def list: (Symbol, untyped, Array[untyped]) -> Array[untyped]
159
188
 
160
189
  # Ruby comparison stands in for SQL only on the record's own column of
161
- # a type both compare alike.
162
- # : (untyped) -> String
163
- def column: (untyped) -> String
190
+ # a type both compare alike. Whether a value is NULL, any column's
191
+ # type compares alike.
192
+ # : (untyped, ?null_test: bool) -> String
193
+ def column: (untyped, ?null_test: bool) -> String
194
+
195
+ # : (String, untyped) -> bool
196
+ def comparable?: (String, untyped) -> bool
164
197
 
165
- # : (String) -> bool
166
- def comparable?: (String) -> bool
198
+ # PostgreSQL's citext is text that compares without regard to case.
199
+ # : (untyped) -> bool
200
+ def exact_text?: (untyped) -> bool
167
201
 
168
202
  # An enum compares the values it stores, which its subtype describes. A
169
203
  # wrapper such as a serialized or encrypted type is none of these.
@@ -13,6 +13,10 @@ module PaperTrailDiff
13
13
 
14
14
  ENUM_TYPE: ::String
15
15
 
16
+ # Rails wraps a time-zone-aware attribute's type, by default every
17
+ # datetime's, in this delegator; the time it stores is the wrapped type's.
18
+ TIME_ZONE_TYPE: ::String
19
+
16
20
  # Integers, booleans and dates sort alike everywhere. Strings follow a
17
21
  # collation, so they are not read.
18
22
  ORDER_TYPES: untyped
@@ -67,6 +71,10 @@ module PaperTrailDiff
67
71
  # : (untyped, String) -> bool
68
72
  def self.orderable?: (untyped, String) -> bool
69
73
 
74
+ # The type of the value a column stores, under an enum or time zone wrapper.
75
+ # : (untyped) -> untyped
76
+ def self.stored_type: (untyped) -> untyped
77
+
70
78
  # Active Record truncates a time to its column's precision before writing
71
79
  # it, so history holds the stored value when it keeps that many digits.
72
80
  # Times the type stores in UTC sort as the instants they name; local
@@ -29,6 +29,12 @@ module PaperTrailDiff
29
29
  # : (analyses: Hash[Array[String], Analysis], unreachable: Array[Array[String]], ?roots: Array[untyped], ?deleted: Hash[Array[String], Analysis?]) -> void
30
30
  def initialize: (analyses: Hash[Array[String], Analysis], unreachable: Array[Array[String]], ?roots: Array[untyped], ?deleted: Hash[Array[String], Analysis?]) -> void
31
31
 
32
+ # Each root's change rows, keyed as `analyses` is: the roots that still
33
+ # exist, then the deleted ones that have history to report. A deleted root
34
+ # without destroy history has no rows and is left out.
35
+ # : () -> Hash[Array[String], Array[ChangeRow]]
36
+ def change_rows: () -> Hash[Array[String], Array[ChangeRow]]
37
+
32
38
  # Two elements, not three: `roots` is reached by name so that existing
33
39
  # destructuring keeps working.
34
40
  # : () -> [Hash[Array[String], Analysis], Array[Array[String]]]
@@ -9,11 +9,16 @@ module PaperTrailDiff
9
9
  # : () -> ScopedAnalysis
10
10
  def call: () -> ScopedAnalysis
11
11
 
12
+ private
13
+
14
+ # : (ScopedRootSelection::Result, Array[Array[String]], Hash[Array[String], Analysis?]) -> ScopedAnalysis
15
+ def analysis: (ScopedRootSelection::Result, Array[Array[String]], Hash[Array[String], Analysis?]) -> ScopedAnalysis
16
+
12
17
  @analyzer: untyped
13
18
 
14
19
  @deleted_analyzer: untyped
15
20
 
16
- @include_deleted: bool
21
+ @deleted_filter: DeletedRootFilter?
17
22
 
18
23
  @limit: Integer
19
24
 
@@ -71,6 +71,12 @@ module PaperTrailDiff
71
71
  # : (Array[AssociationActivitySelection::source]) -> Array[AssociationActivitySelection::Query]?
72
72
  def subqueries: (Array[AssociationActivitySelection::source]) -> Array[AssociationActivitySelection::Query]?
73
73
 
74
+ # Which candidates still have a row is asked without the relation's
75
+ # conditions, so that "filtered out" and "no longer exists" stay
76
+ # separable. A filter then reads the history of the relation's records
77
+ # only, whose order and page choose among those that match, and of the
78
+ # deleted ones, which no relation can test. Those it lists, as without a
79
+ # filter, but does not count toward `limit:`.
74
80
  # : (Array[String]) -> Result
75
81
  def plucked_selection: (Array[String]) -> Result
76
82
 
@@ -113,10 +119,8 @@ module PaperTrailDiff
113
119
  # : () -> Array[String]
114
120
  def versioned_ids: () -> Array[String]
115
121
 
116
- # Which of those candidates still have a row, asked without the relation's
117
- # conditions so that "filtered out" and "no longer exists" stay separable.
118
- # : (Array[String]) -> Array[String]
119
- def live_ids: (Array[String]) -> Array[String]
122
+ # : (untyped, Array[String]) -> Array[String]
123
+ def ids_in: (untyped, Array[String]) -> Array[String]
120
124
 
121
125
  # Deduplicate records rather than adding SQL DISTINCT: an otherwise valid
122
126
  # relation may order by joined columns or select a PostgreSQL json column.