paper_trail_diff 0.12.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +158 -0
- data/QUICKSTART.md +149 -79
- data/README.md +179 -1388
- data/docs/architecture.md +88 -0
- data/docs/performance.md +309 -0
- data/docs/reference.md +977 -0
- data/docs/testing.md +169 -0
- data/lib/paper_trail_diff/activity_belongs_to_event_applier.rb +2 -12
- data/lib/paper_trail_diff/activity_boundary.rb +20 -8
- data/lib/paper_trail_diff/activity_child_candidate_loader.rb +29 -0
- data/lib/paper_trail_diff/activity_collection_event_applier.rb +10 -7
- data/lib/paper_trail_diff/activity_collection_route_updater.rb +1 -11
- data/lib/paper_trail_diff/activity_event_record_normalizer.rb +4 -0
- data/lib/paper_trail_diff/activity_event_record_resolver.rb +66 -6
- data/lib/paper_trail_diff/activity_event_snapshot_refresher.rb +6 -4
- data/lib/paper_trail_diff/activity_grouping.rb +12 -0
- data/lib/paper_trail_diff/activity_history.rb +9 -3
- data/lib/paper_trail_diff/activity_relationship.rb +2 -0
- data/lib/paper_trail_diff/activity_root_snapshot_refresher.rb +1 -0
- data/lib/paper_trail_diff/activity_snapshot_sequence.rb +9 -2
- data/lib/paper_trail_diff/activity_timeline_builder.rb +39 -35
- data/lib/paper_trail_diff/activity_version_collector.rb +27 -18
- data/lib/paper_trail_diff/analysis.rb +9 -4
- data/lib/paper_trail_diff/analysis_batch.rb +28 -7
- data/lib/paper_trail_diff/association_traversal.rb +23 -0
- data/lib/paper_trail_diff/batch_boundary_resolver.rb +18 -26
- data/lib/paper_trail_diff/batch_safety.rb +55 -0
- data/lib/paper_trail_diff/batched_root_analyzer.rb +14 -5
- data/lib/paper_trail_diff/batched_root_versions.rb +10 -38
- data/lib/paper_trail_diff/branch_snapshot_refresher.rb +7 -8
- data/lib/paper_trail_diff/collection_comparator.rb +18 -20
- data/lib/paper_trail_diff/collection_identity_index.rb +8 -12
- data/lib/paper_trail_diff/deleted_analysis_batch.rb +94 -0
- data/lib/paper_trail_diff/direct_record_state_loader.rb +81 -0
- data/lib/paper_trail_diff/endpoint.rb +10 -0
- data/lib/paper_trail_diff/engine.rb +4 -2
- data/lib/paper_trail_diff/errors.rb +9 -0
- data/lib/paper_trail_diff/historical_association_reifier.rb +26 -1
- data/lib/paper_trail_diff/historical_filter.rb +176 -0
- data/lib/paper_trail_diff/historical_snapshot_store.rb +51 -35
- data/lib/paper_trail_diff/instrumentation.rb +2 -2
- data/lib/paper_trail_diff/nested_comparator.rb +39 -15
- data/lib/paper_trail_diff/paper_trail_adapter.rb +109 -55
- data/lib/paper_trail_diff/prepared_activity_index.rb +121 -0
- data/lib/paper_trail_diff/prepared_edge_loader.rb +11 -11
- data/lib/paper_trail_diff/prepared_history.rb +21 -30
- data/lib/paper_trail_diff/prepared_history_loader.rb +14 -2
- data/lib/paper_trail_diff/prepared_record_index.rb +102 -29
- data/lib/paper_trail_diff/prepared_snapshot_builder.rb +64 -0
- data/lib/paper_trail_diff/prepared_state_association_reader.rb +77 -0
- data/lib/paper_trail_diff/root_version_plan.rb +35 -3
- data/lib/paper_trail_diff/root_version_selection.rb +16 -5
- data/lib/paper_trail_diff/scoped_analysis.rb +17 -2
- data/lib/paper_trail_diff/scoped_analysis_builder.rb +47 -0
- data/lib/paper_trail_diff/scoped_root_selection.rb +106 -13
- data/lib/paper_trail_diff/snapshot.rb +14 -1
- data/lib/paper_trail_diff/snapshot_normalizer.rb +75 -40
- data/lib/paper_trail_diff/step.rb +10 -5
- data/lib/paper_trail_diff/stored_time_precision.rb +68 -0
- data/lib/paper_trail_diff/support.rb +13 -3
- data/lib/paper_trail_diff/time_activity_timeline_builder.rb +19 -7
- data/lib/paper_trail_diff/time_range.rb +21 -2
- data/lib/paper_trail_diff/time_version_range.rb +2 -16
- data/lib/paper_trail_diff/timeline_builder.rb +5 -4
- data/lib/paper_trail_diff/timeline_range.rb +29 -46
- data/lib/paper_trail_diff/timeline_snapshot_provider.rb +13 -2
- data/lib/paper_trail_diff/traversal.rb +1 -1
- data/lib/paper_trail_diff/version.rb +1 -1
- data/lib/paper_trail_diff/version_association_candidate_scope.rb +1 -1
- data/lib/paper_trail_diff/version_boundary.rb +48 -0
- data/lib/paper_trail_diff/version_query.rb +41 -0
- data/lib/paper_trail_diff/version_range.rb +10 -38
- data/lib/paper_trail_diff.rb +78 -42
- data/sig/generated/paper_trail_diff/activity_belongs_to_event_applier.rbs +0 -3
- data/sig/generated/paper_trail_diff/activity_boundary.rbs +9 -4
- data/sig/generated/paper_trail_diff/activity_child_candidate_loader.rbs +11 -0
- data/sig/generated/paper_trail_diff/activity_collection_event_applier.rbs +4 -4
- data/sig/generated/paper_trail_diff/activity_collection_route_updater.rbs +0 -3
- data/sig/generated/paper_trail_diff/activity_event_record_resolver.rbs +19 -3
- data/sig/generated/paper_trail_diff/activity_event_snapshot_refresher.rbs +2 -2
- data/sig/generated/paper_trail_diff/activity_grouping.rbs +3 -0
- data/sig/generated/paper_trail_diff/activity_history.rbs +6 -2
- data/sig/generated/paper_trail_diff/activity_snapshot_sequence.rbs +7 -2
- data/sig/generated/paper_trail_diff/activity_timeline_builder.rbs +8 -3
- data/sig/generated/paper_trail_diff/activity_version_collector.rbs +4 -8
- data/sig/generated/paper_trail_diff/analysis.rbs +2 -2
- data/sig/generated/paper_trail_diff/analysis_batch.rbs +14 -3
- data/sig/generated/paper_trail_diff/association_traversal.rbs +11 -0
- data/sig/generated/paper_trail_diff/batch_boundary_resolver.rbs +7 -10
- data/sig/generated/paper_trail_diff/batch_safety.rbs +24 -0
- data/sig/generated/paper_trail_diff/batched_root_analyzer.rbs +8 -2
- data/sig/generated/paper_trail_diff/batched_root_versions.rbs +4 -16
- data/sig/generated/paper_trail_diff/branch_snapshot_refresher.rbs +2 -2
- data/sig/generated/paper_trail_diff/collection_comparator.rbs +6 -3
- data/sig/generated/paper_trail_diff/collection_identity_index.rbs +3 -3
- data/sig/generated/paper_trail_diff/deleted_analysis_batch.rbs +44 -0
- data/sig/generated/paper_trail_diff/direct_record_state_loader.rbs +42 -0
- data/sig/generated/paper_trail_diff/endpoint.rbs +5 -0
- data/sig/generated/paper_trail_diff/engine.rbs +4 -2
- data/sig/generated/paper_trail_diff/errors.rbs +12 -0
- data/sig/generated/paper_trail_diff/historical_association_reifier.rbs +8 -0
- data/sig/generated/paper_trail_diff/historical_filter.rbs +75 -0
- data/sig/generated/paper_trail_diff/historical_snapshot_store.rbs +17 -4
- data/sig/generated/paper_trail_diff/nested_comparator.rbs +15 -7
- data/sig/generated/paper_trail_diff/paper_trail_adapter.rbs +36 -15
- data/sig/generated/paper_trail_diff/prepared_activity_index.rbs +56 -0
- data/sig/generated/paper_trail_diff/prepared_edge_loader.rbs +3 -3
- data/sig/generated/paper_trail_diff/prepared_history.rbs +12 -14
- data/sig/generated/paper_trail_diff/prepared_history_loader.rbs +2 -0
- data/sig/generated/paper_trail_diff/prepared_record_index.rbs +43 -6
- data/sig/generated/paper_trail_diff/prepared_snapshot_builder.rbs +24 -0
- data/sig/generated/paper_trail_diff/prepared_state_association_reader.rbs +33 -0
- data/sig/generated/paper_trail_diff/root_version_plan.rbs +22 -2
- data/sig/generated/paper_trail_diff/root_version_selection.rbs +8 -1
- data/sig/generated/paper_trail_diff/scoped_analysis.rbs +14 -2
- data/sig/generated/paper_trail_diff/scoped_analysis_builder.rbs +22 -0
- data/sig/generated/paper_trail_diff/scoped_root_selection.rbs +46 -6
- data/sig/generated/paper_trail_diff/snapshot.rbs +4 -0
- data/sig/generated/paper_trail_diff/snapshot_normalizer.rbs +34 -11
- data/sig/generated/paper_trail_diff/step.rbs +8 -2
- data/sig/generated/paper_trail_diff/stored_time_precision.rbs +36 -0
- data/sig/generated/paper_trail_diff/support.rbs +7 -0
- data/sig/generated/paper_trail_diff/time_activity_timeline_builder.rbs +7 -2
- data/sig/generated/paper_trail_diff/time_range.rbs +9 -0
- data/sig/generated/paper_trail_diff/time_version_range.rbs +0 -6
- data/sig/generated/paper_trail_diff/timeline_builder.rbs +2 -2
- data/sig/generated/paper_trail_diff/timeline_range.rbs +12 -15
- data/sig/generated/paper_trail_diff/timeline_snapshot_provider.rbs +2 -0
- data/sig/generated/paper_trail_diff/version_boundary.rbs +23 -0
- data/sig/generated/paper_trail_diff/version_query.rbs +21 -0
- data/sig/generated/paper_trail_diff/version_range.rbs +5 -14
- data/sig/generated/paper_trail_diff.rbs +31 -25
- metadata +31 -5
|
@@ -25,6 +25,8 @@ module PaperTrailDiff
|
|
|
25
25
|
# window. `where(status: 'published')` selects what is published now, which is
|
|
26
26
|
# not the same set as what was published while the window was open.
|
|
27
27
|
class ScopedRootSelection
|
|
28
|
+
PAGE_SIZE: ::Integer
|
|
29
|
+
|
|
28
30
|
# A plain class rather than Data.define, which arrived in Ruby 3.2 while this
|
|
29
31
|
# gem supports 3.1.
|
|
30
32
|
class Result
|
|
@@ -36,20 +38,43 @@ module PaperTrailDiff
|
|
|
36
38
|
def initialize: (records: Array[untyped], unreachable: Array[Array[String]]) -> void
|
|
37
39
|
end
|
|
38
40
|
|
|
39
|
-
# : (untyped, time_range: TimeRange?, limit: Integer) -> void
|
|
40
|
-
def initialize: (untyped, time_range: TimeRange?, limit: Integer) -> void
|
|
41
|
+
# : (untyped, time_range: TimeRange?, limit: Integer, ?historical_filter: untyped, ?max_filter_versions: Integer) -> void
|
|
42
|
+
def initialize: (untyped, time_range: TimeRange?, limit: Integer, ?historical_filter: untyped, ?max_filter_versions: Integer) -> void
|
|
41
43
|
|
|
42
44
|
# : () -> Result
|
|
43
45
|
def call: () -> Result
|
|
44
46
|
|
|
45
47
|
private
|
|
46
48
|
|
|
49
|
+
@historical_filter: untyped
|
|
50
|
+
|
|
47
51
|
@limit: Integer
|
|
48
52
|
|
|
49
53
|
@scope: untyped
|
|
50
54
|
|
|
51
55
|
@time_range: TimeRange?
|
|
52
56
|
|
|
57
|
+
# Leaves the candidate set in the database. Plucking every root changed in
|
|
58
|
+
# the window would cost memory and a statement proportional to all activity
|
|
59
|
+
# in the window -- not to the relation or `limit:` -- so a report on one
|
|
60
|
+
# record could read every id in a busy table. A subquery keeps the work
|
|
61
|
+
# bounded by what the relation and the deletions actually contain.
|
|
62
|
+
# : () -> Result
|
|
63
|
+
def database_selection: () -> Result
|
|
64
|
+
|
|
65
|
+
# Comparing the two columns in SQL needs them to share a type: PostgreSQL
|
|
66
|
+
# rejects an integer key against a string item_id outright.
|
|
67
|
+
# : () -> bool
|
|
68
|
+
def comparable_keys?: () -> bool
|
|
69
|
+
|
|
70
|
+
# Changed in the window but without a row, found with an anti-join instead
|
|
71
|
+
# of comparing two plucked lists.
|
|
72
|
+
# : () -> Array[String]
|
|
73
|
+
def missing_ids: () -> Array[String]
|
|
74
|
+
|
|
75
|
+
# : () -> untyped
|
|
76
|
+
def window_versions: () -> untyped
|
|
77
|
+
|
|
53
78
|
# Accepts a model class as readily as a relation: `Article` and
|
|
54
79
|
# `Article.where(...)` both name a population, and `all` is what makes them
|
|
55
80
|
# the same kind of thing.
|
|
@@ -70,10 +95,25 @@ module PaperTrailDiff
|
|
|
70
95
|
# : (Array[String]) -> Array[String]
|
|
71
96
|
def live_ids: (Array[String]) -> Array[String]
|
|
72
97
|
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
|
|
98
|
+
# Deduplicate records rather than adding SQL DISTINCT: an otherwise valid
|
|
99
|
+
# relation may order by joined columns or select a PostgreSQL json column.
|
|
100
|
+
# Read bounded pages until the relation ends or too many unique roots appear.
|
|
101
|
+
# : (untyped) -> Array[untyped]
|
|
102
|
+
def selected_records: (untyped) -> Array[untyped]
|
|
103
|
+
|
|
104
|
+
# : (untyped, Integer) -> Array[untyped]
|
|
105
|
+
def record_page: (untyped, Integer) -> Array[untyped]
|
|
106
|
+
|
|
107
|
+
# Preserve explicit ordering: SQL Server rejects duplicate ORDER BY columns,
|
|
108
|
+
# even when the extra ordering would only have served as a tie-breaker.
|
|
109
|
+
# : () -> untyped
|
|
110
|
+
def ordered_scope: () -> untyped
|
|
111
|
+
|
|
112
|
+
# : (Integer) -> Integer
|
|
113
|
+
def page_limit: (Integer) -> Integer
|
|
114
|
+
|
|
115
|
+
# : (Integer) -> void
|
|
116
|
+
def reject_oversized!: (Integer) -> void
|
|
77
117
|
|
|
78
118
|
# : (String) -> Array[String]
|
|
79
119
|
def identity: (String) -> Array[String]
|
|
@@ -113,6 +113,10 @@ module PaperTrailDiff
|
|
|
113
113
|
# : () -> RecordReference
|
|
114
114
|
def reference: () -> RecordReference
|
|
115
115
|
|
|
116
|
+
# A copy holding a different state for one association.
|
|
117
|
+
# : (String, AssociationSnapshot) -> RecordSnapshot
|
|
118
|
+
def with_association: (String, AssociationSnapshot) -> RecordSnapshot
|
|
119
|
+
|
|
116
120
|
# : () -> Hash[Symbol, untyped]
|
|
117
121
|
def to_h: () -> Hash[Symbol, untyped]
|
|
118
122
|
|
|
@@ -19,6 +19,9 @@ module PaperTrailDiff
|
|
|
19
19
|
|
|
20
20
|
# : (RecordSnapshot, RecordSnapshot) -> bool
|
|
21
21
|
def equivalent_record?: (RecordSnapshot, RecordSnapshot) -> bool
|
|
22
|
+
|
|
23
|
+
# : (AssociationSnapshot, AssociationSnapshot) -> bool
|
|
24
|
+
def same_association?: (AssociationSnapshot, AssociationSnapshot) -> bool
|
|
22
25
|
end
|
|
23
26
|
|
|
24
27
|
# Converts an ActiveRecord graph into immutable, persistence-independent snapshots.
|
|
@@ -37,9 +40,21 @@ module PaperTrailDiff
|
|
|
37
40
|
# : (untyped, tree: AssociationTree, path: String) -> snapshot_attributes
|
|
38
41
|
def attributes_for: (untyped, tree: AssociationTree, path: String) -> snapshot_attributes
|
|
39
42
|
|
|
43
|
+
# Same exclusions as AR normalization, applied to an already typed state.
|
|
44
|
+
# : (PreparedRecordState, tree: AssociationTree, path: String, ?incoming: untyped, ?associations: snapshot_associations) -> RecordSnapshot
|
|
45
|
+
def from_state: (PreparedRecordState, tree: AssociationTree, path: String, ?incoming: untyped, ?associations: snapshot_associations) -> RecordSnapshot
|
|
46
|
+
|
|
40
47
|
private
|
|
41
48
|
|
|
42
|
-
|
|
49
|
+
# The id a live record of the same row reports: one value, or one per
|
|
50
|
+
# column of a composite key.
|
|
51
|
+
# : (untyped, PreparedRecordState) -> untyped
|
|
52
|
+
def state_id: (untyped, PreparedRecordState) -> untyped
|
|
53
|
+
|
|
54
|
+
# : (String, RecordSnapshot) -> RecordSnapshot
|
|
55
|
+
def pool_state: (String, RecordSnapshot) -> RecordSnapshot
|
|
56
|
+
|
|
57
|
+
@excluded_attributes: Hash[Array[untyped], Array[String?]]
|
|
43
58
|
|
|
44
59
|
@ignore_policy: IgnorePolicy
|
|
45
60
|
|
|
@@ -51,8 +66,8 @@ module PaperTrailDiff
|
|
|
51
66
|
|
|
52
67
|
@tree: AssociationTree
|
|
53
68
|
|
|
54
|
-
# : (untyped, AssociationTree, String, untyped, Array[untyped]) -> RecordSnapshot
|
|
55
|
-
def normalize_record: (untyped, AssociationTree, String, untyped, Array[untyped]) -> RecordSnapshot
|
|
69
|
+
# : (untyped, AssociationTree, String, untyped, Array[untyped]) -> RecordSnapshot
|
|
70
|
+
def normalize_record: (untyped, AssociationTree, String, untyped, Array[untyped]) -> RecordSnapshot
|
|
56
71
|
|
|
57
72
|
# : (untyped, Array[untyped], String) -> snapshot_attributes
|
|
58
73
|
def normalized_attributes: (untyped, Array[untyped], String) -> snapshot_attributes
|
|
@@ -60,14 +75,22 @@ module PaperTrailDiff
|
|
|
60
75
|
# Every input is fixed for one model class at one selected path, and the
|
|
61
76
|
# path's structural columns are recorded before its records normalize, so
|
|
62
77
|
# this is resolved once instead of per record per boundary.
|
|
63
|
-
# : (untyped, Array[untyped], String) -> Array[String]
|
|
64
|
-
def excluded_attributes: (untyped, Array[untyped], String) -> Array[String]
|
|
65
|
-
|
|
66
|
-
# : (untyped, Array[untyped], String) -> Array[String]
|
|
67
|
-
def build_excluded_attributes: (untyped, Array[untyped], String) -> Array[String]
|
|
68
|
-
|
|
69
|
-
#
|
|
70
|
-
|
|
78
|
+
# : (untyped, Array[untyped], String) -> Array[String?]
|
|
79
|
+
def excluded_attributes: (untyped, Array[untyped], String) -> Array[String?]
|
|
80
|
+
|
|
81
|
+
# : (untyped, Array[untyped], String) -> Array[String?]
|
|
82
|
+
def build_excluded_attributes: (untyped, Array[untyped], String) -> Array[String?]
|
|
83
|
+
|
|
84
|
+
# `has_paper_trail skip:` columns are never written to a version, so history
|
|
85
|
+
# holds only their defaults. Compared with a live row they would report a
|
|
86
|
+
# change nobody recorded.
|
|
87
|
+
# : (untyped) -> Array[String]
|
|
88
|
+
def skipped_columns: (untyped) -> Array[String]
|
|
89
|
+
|
|
90
|
+
# A non-polymorphic reflection has no foreign type, so it adds nil, which
|
|
91
|
+
# excludes nothing.
|
|
92
|
+
# : (Array[untyped]) -> Array[String?]
|
|
93
|
+
def relationship_columns: (Array[untyped]) -> Array[String?]
|
|
71
94
|
|
|
72
95
|
# : (untyped, AssociationTree, String, untyped, Array[untyped]) -> Hash[String, AssociationSnapshot]
|
|
73
96
|
def normalize_associations: (untyped, AssociationTree, String, untyped, Array[untyped]) -> Hash[String, AssociationSnapshot]
|
|
@@ -22,7 +22,13 @@ module PaperTrailDiff
|
|
|
22
22
|
# : () -> bool
|
|
23
23
|
def empty?: () -> bool
|
|
24
24
|
|
|
25
|
-
#
|
|
26
|
-
|
|
25
|
+
# The root event whose change this step reports.
|
|
26
|
+
# : () -> ActivityBoundary
|
|
27
|
+
def source_boundary: () -> ActivityBoundary
|
|
28
|
+
|
|
29
|
+
# The same shape as `ActivityStep#to_h`, so a consumer reads both step
|
|
30
|
+
# types alike; each boundary carries its own `version_id`.
|
|
31
|
+
# : (?metadata: bool) -> Hash[Symbol, untyped]
|
|
32
|
+
def to_h: (?metadata: bool) -> Hash[Symbol, untyped]
|
|
27
33
|
end
|
|
28
34
|
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Generated from lib/paper_trail_diff/stored_time_precision.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module PaperTrailDiff
|
|
4
|
+
# Matches time values to the precision PaperTrail actually stored.
|
|
5
|
+
#
|
|
6
|
+
# PaperTrail's JSON serializer, and a `json` or `jsonb` object column, write
|
|
7
|
+
# times through ActiveSupport's JSON encoder, which keeps only
|
|
8
|
+
# `ActiveSupport::JSON::Encoding.time_precision` fractional digits
|
|
9
|
+
# (milliseconds by default). The live row keeps the database's precision, so
|
|
10
|
+
# comparing the two would report every timestamp as changed even though
|
|
11
|
+
# nothing was edited. Truncating every side to the stored precision is
|
|
12
|
+
# idempotent, so values that came from history are unaffected and only the
|
|
13
|
+
# digits history could never have held are dropped.
|
|
14
|
+
module StoredTimePrecision
|
|
15
|
+
NANOSECOND_DIGITS: ::Integer
|
|
16
|
+
|
|
17
|
+
JSON_COLUMN_TYPES: untyped
|
|
18
|
+
|
|
19
|
+
# Fractional digits a model's history keeps for times, or nil when the
|
|
20
|
+
# history is stored losslessly.
|
|
21
|
+
# : (untyped) -> Integer?
|
|
22
|
+
def self?.digits: (untyped) -> Integer?
|
|
23
|
+
|
|
24
|
+
# : (Hash[untyped, untyped], Integer?) -> Hash[untyped, untyped]
|
|
25
|
+
def self?.apply: (Hash[untyped, untyped], Integer?) -> Hash[untyped, untyped]
|
|
26
|
+
|
|
27
|
+
# : (untyped, Integer) -> untyped
|
|
28
|
+
def self?.truncate: (untyped, Integer) -> untyped
|
|
29
|
+
|
|
30
|
+
# : (untyped) -> bool
|
|
31
|
+
def self?.json_history?: (untyped) -> bool
|
|
32
|
+
|
|
33
|
+
# : () -> bool
|
|
34
|
+
def self?.json_serializer?: () -> bool
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -43,6 +43,13 @@ module PaperTrailDiff
|
|
|
43
43
|
# : (untyped) -> bool
|
|
44
44
|
def self?.versioned?: (untyped) -> bool
|
|
45
45
|
|
|
46
|
+
# `versions.item_id` is often a string column -- PaperTrail's setup for UUID
|
|
47
|
+
# keys -- while the model's own key is an integer. An id read from history
|
|
48
|
+
# is cast to the key's type, or the same record read once from history and
|
|
49
|
+
# once from its live row would count as two.
|
|
50
|
+
# : (untyped, untyped) -> untyped
|
|
51
|
+
def self?.record_id: (untyped, untyped) -> untyped
|
|
52
|
+
|
|
46
53
|
# : (untyped) -> bool
|
|
47
54
|
def self?.sequential_id?: (untyped) -> bool
|
|
48
55
|
|
|
@@ -29,6 +29,9 @@ module PaperTrailDiff
|
|
|
29
29
|
# : () -> [ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped]
|
|
30
30
|
def history_and_versions: () -> [ ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped ]
|
|
31
31
|
|
|
32
|
+
# : () -> void
|
|
33
|
+
def reject_live_habtm!: () -> void
|
|
34
|
+
|
|
32
35
|
# : (RootVersionPlan, untyped) -> [ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped]
|
|
33
36
|
def history_for: (RootVersionPlan, untyped) -> [ ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped ]
|
|
34
37
|
|
|
@@ -40,8 +43,10 @@ module PaperTrailDiff
|
|
|
40
43
|
# : (Array[untyped], untyped) -> Array[ActivityEvent]
|
|
41
44
|
def events_through: (Array[untyped], untyped) -> Array[ActivityEvent]
|
|
42
45
|
|
|
43
|
-
#
|
|
44
|
-
|
|
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
|
|
45
50
|
|
|
46
51
|
# : (ActivityHistory, ActivityStep?) -> Array[ActivityStep]
|
|
47
52
|
def activity_steps: (ActivityHistory, ActivityStep?) -> Array[ActivityStep]
|
|
@@ -22,10 +22,19 @@ module PaperTrailDiff
|
|
|
22
22
|
# : (untyped) -> untyped
|
|
23
23
|
def trailing_scope: (untyped) -> untyped
|
|
24
24
|
|
|
25
|
+
# Each item's earliest version after the range, keyed by item id. One row
|
|
26
|
+
# per item comes back from a NOT EXISTS rather than a window function, so
|
|
27
|
+
# the query stays portable; a timestamp tie keeps the lower id.
|
|
28
|
+
# : (untyped) -> Hash[String, untyped]
|
|
29
|
+
def first_trailing_versions: (untyped) -> Hash[String, untyped]
|
|
30
|
+
|
|
25
31
|
private
|
|
26
32
|
|
|
27
33
|
@exclude_end: bool
|
|
28
34
|
|
|
35
|
+
# : (untyped) -> untyped
|
|
36
|
+
def after_end: (untyped) -> untyped
|
|
37
|
+
|
|
29
38
|
# : () -> Range[Time]
|
|
30
39
|
def query_range: () -> Range[Time]
|
|
31
40
|
|
|
@@ -6,9 +6,6 @@ module PaperTrailDiff
|
|
|
6
6
|
# : (untyped, time_range: TimeRange, ?version_scope: untyped, ?live_endpoint: untyped) -> void
|
|
7
7
|
def initialize: (untyped, time_range: TimeRange, ?version_scope: untyped, ?live_endpoint: untyped) -> void
|
|
8
8
|
|
|
9
|
-
# : (?context_required: bool) -> Array[untyped]
|
|
10
|
-
def select: (?context_required: bool) -> Array[untyped]
|
|
11
|
-
|
|
12
9
|
# : (?context_required: bool) -> RootVersionPlan
|
|
13
10
|
def select_plan: (?context_required: bool) -> RootVersionPlan
|
|
14
11
|
|
|
@@ -22,9 +19,6 @@ module PaperTrailDiff
|
|
|
22
19
|
|
|
23
20
|
@version_scope: untyped
|
|
24
21
|
|
|
25
|
-
# : () -> untyped
|
|
26
|
-
def versions_relation: () -> untyped
|
|
27
|
-
|
|
28
22
|
# : (untyped) -> untyped
|
|
29
23
|
def trailing_version: (untyped) -> untyped
|
|
30
24
|
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
module PaperTrailDiff
|
|
4
4
|
# Selects and compares a chronological slice of a record's version history.
|
|
5
5
|
class TimelineBuilder
|
|
6
|
-
# : (untyped, from: untyped, to: untyped, snapshotter: untyped, ?within: untyped, ?
|
|
7
|
-
def initialize: (untyped, from: untyped, to: untyped, snapshotter: untyped, ?within: untyped, ?
|
|
6
|
+
# : (untyped, from: untyped, to: untyped, snapshotter: untyped, ?within: untyped, ?version_scope: untyped, ?plan: RootVersionPlan?, ?live_endpoint: untyped) -> void
|
|
7
|
+
def initialize: (untyped, from: untyped, to: untyped, snapshotter: untyped, ?within: untyped, ?version_scope: untyped, ?plan: RootVersionPlan?, ?live_endpoint: untyped) -> void
|
|
8
8
|
|
|
9
9
|
# : () -> Array[Step]
|
|
10
10
|
def build: () -> Array[Step]
|
|
@@ -5,17 +5,22 @@ module PaperTrailDiff
|
|
|
5
5
|
class TimelineRange
|
|
6
6
|
BOUNDARY_SYMBOLS: untyped
|
|
7
7
|
|
|
8
|
+
# `:last` once named the last version, whose recorded state is the one
|
|
9
|
+
# *before* the latest change, so a range ending there silently dropped it.
|
|
10
|
+
LAST_REMOVED: ::String
|
|
11
|
+
|
|
8
12
|
attr_reader from: untyped
|
|
9
13
|
|
|
10
14
|
attr_reader to: untyped
|
|
11
15
|
|
|
12
16
|
attr_reader time_range: TimeRange?
|
|
13
17
|
|
|
14
|
-
# : (untyped, from: untyped, to: untyped, within: untyped, ?
|
|
15
|
-
def initialize: (untyped, from: untyped, to: untyped, within: untyped, ?
|
|
18
|
+
# : (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?plan: RootVersionPlan?, ?live_endpoint: untyped) -> void
|
|
19
|
+
def initialize: (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?plan: RootVersionPlan?, ?live_endpoint: untyped) -> void
|
|
16
20
|
|
|
17
|
-
# A record with no versions has no first
|
|
18
|
-
# is an empty history rather than a bad request.
|
|
21
|
+
# A record with no versions has no first boundary to resolve, which
|
|
22
|
+
# is an empty history rather than a bad request. Both endpoints are
|
|
23
|
+
# required, so only `:first` can resolve to nothing; a window has none.
|
|
19
24
|
# : () -> bool
|
|
20
25
|
def unresolved?: () -> bool
|
|
21
26
|
|
|
@@ -26,9 +31,6 @@ module PaperTrailDiff
|
|
|
26
31
|
# : (?context_required: bool) -> RootVersionPlan
|
|
27
32
|
def select_plan: (?context_required: bool) -> RootVersionPlan
|
|
28
33
|
|
|
29
|
-
# : (?context_required: bool) -> Array[untyped]
|
|
30
|
-
def select: (?context_required: bool) -> Array[untyped]
|
|
31
|
-
|
|
32
34
|
# : () -> bool
|
|
33
35
|
def time?: () -> bool
|
|
34
36
|
|
|
@@ -56,15 +58,13 @@ module PaperTrailDiff
|
|
|
56
58
|
|
|
57
59
|
@version_scope: untyped
|
|
58
60
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
# : () -> Array[untyped]
|
|
62
|
-
def empty_versions: () -> Array[untyped]
|
|
61
|
+
# : () -> RootVersionPlan
|
|
62
|
+
def explicit_plan: () -> RootVersionPlan
|
|
63
63
|
|
|
64
64
|
# : (untyped) -> bool
|
|
65
65
|
def symbolic?: (untyped) -> bool
|
|
66
66
|
|
|
67
|
-
# Resolving `:first`
|
|
67
|
+
# Resolving `:first` here keeps callers from depending on the
|
|
68
68
|
# order PaperTrail happens to give its versions association, which a caller
|
|
69
69
|
# is also free to reorder.
|
|
70
70
|
# : (untyped) -> untyped
|
|
@@ -73,9 +73,6 @@ module PaperTrailDiff
|
|
|
73
73
|
# : () -> untyped
|
|
74
74
|
def ordered_versions: () -> untyped
|
|
75
75
|
|
|
76
|
-
# : () -> untyped
|
|
77
|
-
def versions_relation: () -> untyped
|
|
78
|
-
|
|
79
76
|
# : (untyped) -> TimeRange?
|
|
80
77
|
def build_time_range: (untyped) -> TimeRange?
|
|
81
78
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Generated from lib/paper_trail_diff/version_boundary.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module PaperTrailDiff
|
|
4
|
+
# Places one version relative to another on the recorded timeline.
|
|
5
|
+
module VersionBoundary
|
|
6
|
+
# Whether `candidate` was recorded at or after `boundary`. Versions sharing
|
|
7
|
+
# a timestamp are ordered by id, which reflects insertion order only for
|
|
8
|
+
# sequential ids in one version table; any other tie cannot be ordered, and
|
|
9
|
+
# guessing would place a change on an arbitrary side of the boundary. A
|
|
10
|
+
# bare instant has no position among same-time versions, so it keeps them.
|
|
11
|
+
# : (untyped, untyped) -> bool
|
|
12
|
+
def self?.at_or_after?: (untyped, untyped) -> bool
|
|
13
|
+
|
|
14
|
+
# : (untyped) -> bool
|
|
15
|
+
def self?.version_record?: (untyped) -> bool
|
|
16
|
+
|
|
17
|
+
# : (untyped, untyped) -> bool
|
|
18
|
+
def self?.same_version?: (untyped, untyped) -> bool
|
|
19
|
+
|
|
20
|
+
# : (untyped, untyped) -> bool
|
|
21
|
+
def self?.orderable_tie?: (untyped, untyped) -> bool
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Generated from lib/paper_trail_diff/version_query.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module PaperTrailDiff
|
|
4
|
+
# Query pieces shared by version lookups.
|
|
5
|
+
module VersionQuery
|
|
6
|
+
# Oldest first. Ids break ties between versions written in the same
|
|
7
|
+
# instant, and the timestamp comes first because ids need not follow time:
|
|
8
|
+
# a UUID key does not.
|
|
9
|
+
# : (untyped) -> untyped
|
|
10
|
+
def self?.chronological: (untyped) -> untyped
|
|
11
|
+
|
|
12
|
+
# Keeps each item's earliest version among those the block also accepts:
|
|
13
|
+
# no other version of the same item that the block accepts is earlier. A
|
|
14
|
+
# correlated NOT EXISTS stays portable where window functions are not.
|
|
15
|
+
# : (untyped) ?{ (untyped) -> untyped } -> untyped
|
|
16
|
+
def self?.no_earlier_version: (untyped) ?{ (untyped) -> untyped } -> untyped
|
|
17
|
+
|
|
18
|
+
# : (untyped, untyped) ?{ (untyped) -> untyped } -> untyped
|
|
19
|
+
def self?.earlier_version: (untyped, untyped) ?{ (untyped) -> untyped } -> untyped
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -6,18 +6,15 @@ module PaperTrailDiff
|
|
|
6
6
|
# : (untyped, from: untyped, to: untyped, ?version_scope: untyped) -> void
|
|
7
7
|
def initialize: (untyped, from: untyped, to: untyped, ?version_scope: untyped) -> void
|
|
8
8
|
|
|
9
|
-
#
|
|
9
|
+
# Keep both reconstruction boundaries and the step pairs a filter implies.
|
|
10
10
|
# : () -> RootVersionPlan
|
|
11
11
|
def select_plan_for_range: () -> RootVersionPlan
|
|
12
12
|
|
|
13
13
|
# : () -> untyped
|
|
14
14
|
def validated_relation: () -> untyped
|
|
15
15
|
|
|
16
|
-
# : () ->
|
|
17
|
-
def
|
|
18
|
-
|
|
19
|
-
# : () -> Array[untyped]
|
|
20
|
-
def select_through_latest: () -> Array[untyped]
|
|
16
|
+
# : (untyped) -> RootVersionPlan
|
|
17
|
+
def select_plan_through_current: (untyped) -> RootVersionPlan
|
|
21
18
|
|
|
22
19
|
private
|
|
23
20
|
|
|
@@ -29,14 +26,8 @@ module PaperTrailDiff
|
|
|
29
26
|
|
|
30
27
|
@version_scope: untyped
|
|
31
28
|
|
|
32
|
-
# : () ->
|
|
33
|
-
def
|
|
34
|
-
|
|
35
|
-
# : (untyped, ?through: untyped) -> Array[untyped]
|
|
36
|
-
def select_relation: (untyped, ?through: untyped) -> Array[untyped]
|
|
37
|
-
|
|
38
|
-
# : (untyped, ?through: untyped) -> RootVersionPlan
|
|
39
|
-
def select_plan: (untyped, ?through: untyped) -> RootVersionPlan
|
|
29
|
+
# : (untyped, ?through: untyped, ?live_endpoint: untyped) -> RootVersionPlan
|
|
30
|
+
def select_plan: (untyped, ?through: untyped, ?live_endpoint: untyped) -> RootVersionPlan
|
|
40
31
|
|
|
41
32
|
# : (Array[untyped]) -> Array[untyped]
|
|
42
33
|
def ordered: (Array[untyped]) -> Array[untyped]
|
|
@@ -13,42 +13,39 @@ module PaperTrailDiff
|
|
|
13
13
|
def self.compare: (untyped, untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?reload_live_endpoints: bool) -> Diff
|
|
14
14
|
|
|
15
15
|
# Compares many independent endpoint pairs while batching current-record loading.
|
|
16
|
-
# : (Array[comparison_input], ?associations: Array[String | Symbol], ?ignore: ignore_option, ?reload_live_endpoints: bool) -> comparison_results
|
|
17
|
-
def self.compare_many: (Array[comparison_input], ?associations: Array[String | Symbol], ?ignore: ignore_option, ?reload_live_endpoints: bool) -> comparison_results
|
|
16
|
+
# : (Array[comparison_input], ?associations: Array[String | Symbol], ?ignore: ignore_option, ?reload_live_endpoints: bool, ?allow_expensive: bool) -> comparison_results
|
|
17
|
+
def self.compare_many: (Array[comparison_input], ?associations: Array[String | Symbol], ?ignore: ignore_option, ?reload_live_endpoints: bool, ?allow_expensive: bool) -> comparison_results
|
|
18
18
|
|
|
19
|
-
# Compares every adjacent reconstructed state in an inclusive version range
|
|
19
|
+
# Compares every adjacent reconstructed state in an inclusive version range,
|
|
20
|
+
# by default the record's whole history through its current state.
|
|
20
21
|
# : (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?version_scope: untyped, ?close_on: Symbol?) -> Array[Step]
|
|
21
22
|
def self.timeline: (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?version_scope: untyped, ?close_on: Symbol?) -> Array[Step]
|
|
22
23
|
|
|
23
24
|
# Compares adjacent root and selected-descendant activity boundaries.
|
|
24
|
-
# `reload_live_endpoints:` applies
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
|
|
25
|
+
# `reload_live_endpoints:` applies whenever the range ends at the current
|
|
26
|
+
# record: an explicit `to:` record, the default range, or a window with no
|
|
27
|
+
# later version.
|
|
28
|
+
# : (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?reload_live_endpoints: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?) -> Array[ActivityStep]
|
|
29
|
+
def self.activity_timeline: (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?reload_live_endpoints: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?) -> Array[ActivityStep]
|
|
28
30
|
|
|
29
31
|
# Builds an endpoint diff and root-checkpoint timeline while normalizing each version once.
|
|
30
32
|
# : (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?) -> Analysis
|
|
31
33
|
def self.analyze: (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?) -> Analysis
|
|
32
34
|
|
|
33
|
-
# Analyzes
|
|
34
|
-
# history once for the batch instead of once per record
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
# plus the roots the relation could not reach. See `analyze_scope` for why
|
|
41
|
-
# that second collection exists.
|
|
42
|
-
# : (?Array[untyped]?, ?scope: untyped, ?limit: Integer?, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?) -> (Hash[identity, Analysis] | ScopedAnalysis)
|
|
43
|
-
def self.analyze_many: (?Array[untyped]?, ?scope: untyped, ?limit: Integer?, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?) -> (Hash[identity, Analysis] | ScopedAnalysis)
|
|
35
|
+
# Analyzes an array of records over one shared range, preparing their
|
|
36
|
+
# selected history once for the batch instead of once per record, and
|
|
37
|
+
# returns a frozen Hash keyed by identity. Roots with nothing to report in
|
|
38
|
+
# the window return an empty `Analysis`. To let a relation choose the
|
|
39
|
+
# records, use `analyze_scope`.
|
|
40
|
+
# : (Array[untyped], ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?, ?allow_expensive: bool) -> Hash[identity, Analysis]
|
|
41
|
+
def self.analyze_many: (Array[untyped], ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?, ?allow_expensive: bool) -> Hash[identity, Analysis]
|
|
44
42
|
|
|
45
43
|
# Analyzes every root the relation reaches whose history moved inside the
|
|
46
|
-
# window
|
|
47
|
-
# caller rediscover them.
|
|
44
|
+
# window. Joined duplicates are read in bounded pages and analyzed once.
|
|
48
45
|
#
|
|
49
46
|
# `limit:` is required and exceeding it raises. Selection moves into the gem
|
|
50
|
-
# here
|
|
51
|
-
#
|
|
47
|
+
# here. The relation's own limit/offset select the live page; this separate
|
|
48
|
+
# ceiling rejects too many unique roots rather than truncating that page.
|
|
52
49
|
#
|
|
53
50
|
# Returns a `ScopedAnalysis`, which destructures:
|
|
54
51
|
#
|
|
@@ -57,7 +54,8 @@ module PaperTrailDiff
|
|
|
57
54
|
# )
|
|
58
55
|
#
|
|
59
56
|
# `unreachable` names roots that changed in the window but have no live row
|
|
60
|
-
# left
|
|
57
|
+
# left, independently of the live page's pagination and safety ceiling.
|
|
58
|
+
# A relation's conditions are evaluated against the live table, so a
|
|
61
59
|
# destroyed root cannot be tested against them at all -- its history is
|
|
62
60
|
# intact and the state it held at destruction may well have matched. Those
|
|
63
61
|
# roots are reported rather than dropped so that a page auditing deletions
|
|
@@ -66,8 +64,14 @@ module PaperTrailDiff
|
|
|
66
64
|
# Note also that a relation selects on current state, not on state during
|
|
67
65
|
# the window: `where(status: 'published')` means published *now*, which is a
|
|
68
66
|
# different set from what was published while the window was open.
|
|
69
|
-
# : (untyped, limit: Integer?, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?) -> ScopedAnalysis
|
|
70
|
-
def self.analyze_scope: (untyped, limit: Integer?, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?) -> ScopedAnalysis
|
|
67
|
+
# : (untyped, limit: Integer?, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?, ?historical_filter: untyped, ?max_filter_versions: Integer, ?include_deleted: bool, ?allow_expensive: bool) -> ScopedAnalysis
|
|
68
|
+
def self.analyze_scope: (untyped, limit: Integer?, ?within: untyped, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?, ?historical_filter: untyped, ?max_filter_versions: Integer, ?include_deleted: bool, ?allow_expensive: bool) -> ScopedAnalysis
|
|
69
|
+
|
|
70
|
+
# Recent root and selected-child activity through the current saved state.
|
|
71
|
+
# Historical windows remain explicit on analyze_scope; this entry point is
|
|
72
|
+
# deliberately named for a report through now rather than an as-of query.
|
|
73
|
+
# : (untyped, since: untyped, limit: Integer, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?snapshots: bool, ?group: Symbol?, ?include_deleted: bool, ?historical_filter: untyped, ?max_filter_versions: Integer, ?allow_expensive: bool) -> ScopedAnalysis
|
|
74
|
+
def self.report: (untyped, since: untyped, limit: Integer, ?associations: Array[String | Symbol], ?ignore: ignore_option, ?snapshots: bool, ?group: Symbol?, ?include_deleted: bool, ?historical_filter: untyped, ?max_filter_versions: Integer, ?allow_expensive: bool) -> ScopedAnalysis
|
|
71
75
|
|
|
72
76
|
# Returns the association macros this release can normalize.
|
|
73
77
|
# : () -> Array[Symbol]
|
|
@@ -126,6 +130,8 @@ module PaperTrailDiff
|
|
|
126
130
|
|
|
127
131
|
type comparison_results = Hash[identity, Diff]
|
|
128
132
|
|
|
133
|
+
type report_options = { within: untyped, activity: bool, version_scope: untyped, close_on: Symbol?, snapshots: bool, group: Symbol? }
|
|
134
|
+
|
|
129
135
|
type ignore_option = Array[String | Symbol] | Hash[String | Symbol, untyped]
|
|
130
136
|
|
|
131
137
|
type reference_key = :type | :id | "type" | "id"
|