paper_trail_diff 0.13.1 → 0.14.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +287 -0
- data/QUICKSTART.md +9 -6
- data/README.md +84 -9
- data/docs/architecture.md +32 -9
- data/docs/performance.md +106 -13
- data/docs/reference.md +242 -29
- data/docs/testing.md +34 -11
- data/lib/paper_trail_diff/activity_belongs_to_event_applier.rb +19 -9
- data/lib/paper_trail_diff/activity_collection_event_applier.rb +11 -1
- data/lib/paper_trail_diff/activity_collection_record_updater.rb +16 -2
- data/lib/paper_trail_diff/activity_event_record_resolver.rb +20 -1
- data/lib/paper_trail_diff/activity_event_route_finder.rb +2 -1
- data/lib/paper_trail_diff/activity_event_snapshot_refresher.rb +1 -1
- data/lib/paper_trail_diff/activity_range.rb +14 -2
- data/lib/paper_trail_diff/activity_relationship.rb +12 -0
- data/lib/paper_trail_diff/activity_root_snapshot_refresher.rb +5 -2
- data/lib/paper_trail_diff/activity_target_candidate_loader.rb +131 -0
- data/lib/paper_trail_diff/activity_timeline_builder.rb +30 -24
- data/lib/paper_trail_diff/activity_version_collector.rb +31 -20
- data/lib/paper_trail_diff/association_activity_edge.rb +236 -0
- data/lib/paper_trail_diff/association_activity_selection.rb +197 -0
- data/lib/paper_trail_diff/association_discovery.rb +8 -13
- data/lib/paper_trail_diff/association_traversal.rb +169 -45
- data/lib/paper_trail_diff/batch_safety.rb +23 -15
- data/lib/paper_trail_diff/batched_root_versions.rb +21 -39
- data/lib/paper_trail_diff/comparison_batch.rb +37 -10
- data/lib/paper_trail_diff/deleted_analysis_batch.rb +34 -16
- data/lib/paper_trail_diff/diagnostics.rb +26 -8
- data/lib/paper_trail_diff/direct_record_state_loader.rb +10 -1
- data/lib/paper_trail_diff/errors.rb +2 -1
- data/lib/paper_trail_diff/habtm_transaction_owners.rb +68 -0
- data/lib/paper_trail_diff/historical_association_reifier.rb +32 -2
- data/lib/paper_trail_diff/historical_snapshot_store.rb +6 -9
- data/lib/paper_trail_diff/live_endpoint_batch_loader.rb +35 -2
- data/lib/paper_trail_diff/live_graph_collector.rb +1 -1
- data/lib/paper_trail_diff/paper_trail_adapter.rb +22 -6
- data/lib/paper_trail_diff/preloaded_endpoint_batch_loader.rb +1 -1
- data/lib/paper_trail_diff/prepared_activity_index.rb +110 -36
- data/lib/paper_trail_diff/prepared_edge_loader.rb +29 -45
- data/lib/paper_trail_diff/prepared_history.rb +63 -54
- data/lib/paper_trail_diff/prepared_history_loader.rb +33 -43
- data/lib/paper_trail_diff/prepared_record_index.rb +26 -0
- data/lib/paper_trail_diff/prepared_snapshot_builder.rb +1 -1
- data/lib/paper_trail_diff/prepared_state_association_reader.rb +119 -29
- data/lib/paper_trail_diff/recorded_changes.rb +83 -0
- data/lib/paper_trail_diff/recorded_class.rb +53 -0
- data/lib/paper_trail_diff/scope_condition.rb +331 -0
- data/lib/paper_trail_diff/scope_ranking.rb +196 -0
- data/lib/paper_trail_diff/scoped_analysis_builder.rb +4 -3
- data/lib/paper_trail_diff/scoped_root_selection.rb +93 -43
- data/lib/paper_trail_diff/snapshot_normalizer.rb +10 -3
- data/lib/paper_trail_diff/support.rb +9 -4
- data/lib/paper_trail_diff/time_activity_timeline_builder.rb +0 -1
- data/lib/paper_trail_diff/time_range.rb +9 -12
- data/lib/paper_trail_diff/time_version_range.rb +5 -5
- data/lib/paper_trail_diff/version.rb +1 -1
- data/lib/paper_trail_diff/version_association_candidate_scope.rb +13 -3
- data/lib/paper_trail_diff/version_range.rb +8 -15
- data/lib/paper_trail_diff/version_scope_filter.rb +11 -3
- data/lib/paper_trail_diff.rb +31 -2
- data/sig/generated/paper_trail_diff/activity_belongs_to_event_applier.rbs +10 -2
- data/sig/generated/paper_trail_diff/activity_collection_event_applier.rbs +5 -0
- data/sig/generated/paper_trail_diff/activity_collection_record_updater.rbs +5 -0
- data/sig/generated/paper_trail_diff/activity_event_record_resolver.rbs +7 -0
- data/sig/generated/paper_trail_diff/activity_range.rbs +6 -2
- data/sig/generated/paper_trail_diff/activity_relationship.rbs +3 -0
- data/sig/generated/paper_trail_diff/activity_root_snapshot_refresher.rbs +2 -0
- data/sig/generated/paper_trail_diff/activity_target_candidate_loader.rbs +79 -0
- data/sig/generated/paper_trail_diff/activity_timeline_builder.rbs +11 -5
- data/sig/generated/paper_trail_diff/activity_version_collector.rbs +10 -4
- data/sig/generated/paper_trail_diff/association_activity_edge.rbs +148 -0
- data/sig/generated/paper_trail_diff/association_activity_selection.rbs +112 -0
- data/sig/generated/paper_trail_diff/association_discovery.rbs +3 -5
- data/sig/generated/paper_trail_diff/association_traversal.rbs +84 -9
- data/sig/generated/paper_trail_diff/batch_safety.rbs +10 -4
- data/sig/generated/paper_trail_diff/batched_root_versions.rbs +2 -8
- data/sig/generated/paper_trail_diff/comparison_batch.rbs +22 -4
- data/sig/generated/paper_trail_diff/deleted_analysis_batch.rbs +16 -4
- data/sig/generated/paper_trail_diff/diagnostics.rbs +7 -2
- data/sig/generated/paper_trail_diff/direct_record_state_loader.rbs +6 -0
- data/sig/generated/paper_trail_diff/errors.rbs +2 -1
- data/sig/generated/paper_trail_diff/habtm_transaction_owners.rbs +40 -0
- data/sig/generated/paper_trail_diff/historical_association_reifier.rbs +13 -0
- data/sig/generated/paper_trail_diff/historical_snapshot_store.rbs +3 -2
- data/sig/generated/paper_trail_diff/live_endpoint_batch_loader.rbs +17 -0
- data/sig/generated/paper_trail_diff/paper_trail_adapter.rbs +5 -0
- data/sig/generated/paper_trail_diff/prepared_activity_index.rbs +52 -10
- data/sig/generated/paper_trail_diff/prepared_edge_loader.rbs +4 -12
- data/sig/generated/paper_trail_diff/prepared_history.rbs +33 -22
- data/sig/generated/paper_trail_diff/prepared_history_loader.rbs +15 -13
- data/sig/generated/paper_trail_diff/prepared_record_index.rbs +15 -0
- data/sig/generated/paper_trail_diff/prepared_state_association_reader.rbs +56 -11
- data/sig/generated/paper_trail_diff/recorded_changes.rbs +47 -0
- data/sig/generated/paper_trail_diff/recorded_class.rbs +22 -0
- data/sig/generated/paper_trail_diff/scope_condition.rbs +185 -0
- data/sig/generated/paper_trail_diff/scope_ranking.rbs +117 -0
- data/sig/generated/paper_trail_diff/scoped_analysis_builder.rbs +2 -2
- data/sig/generated/paper_trail_diff/scoped_root_selection.rbs +41 -7
- data/sig/generated/paper_trail_diff/snapshot_normalizer.rbs +6 -0
- data/sig/generated/paper_trail_diff/support.rbs +5 -2
- data/sig/generated/paper_trail_diff/time_range.rbs +4 -0
- data/sig/generated/paper_trail_diff/time_version_range.rbs +4 -4
- data/sig/generated/paper_trail_diff/version_association_candidate_scope.rbs +7 -0
- data/sig/generated/paper_trail_diff/version_range.rbs +4 -2
- data/sig/generated/paper_trail_diff/version_scope_filter.rbs +4 -0
- data/sig/generated/paper_trail_diff.rbs +21 -2
- metadata +20 -4
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
module PaperTrailDiff
|
|
5
5
|
# Selects an inclusive, chronological range from a record's root versions.
|
|
6
6
|
class VersionRange
|
|
7
|
-
#: (untyped, from: untyped, to: untyped,
|
|
8
|
-
def initialize(record, from:, to:, version_scope:
|
|
7
|
+
#: (untyped, from: untyped, to: untyped, version_scope: untyped) -> void
|
|
8
|
+
def initialize(record, from:, to:, version_scope:)
|
|
9
9
|
@record = record
|
|
10
10
|
@from = from
|
|
11
11
|
@to = to
|
|
@@ -51,11 +51,9 @@ module PaperTrailDiff
|
|
|
51
51
|
|
|
52
52
|
#: (untyped, ?through: untyped, ?live_endpoint: untyped) -> RootVersionPlan
|
|
53
53
|
def select_plan(relation, through: nil, live_endpoint: nil)
|
|
54
|
-
in_range = ordered(relation.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
Support.compare_versions(version, through) <= 0
|
|
54
|
+
in_range = ordered(relation.select do |version|
|
|
55
|
+
Support.compare_versions(@from, version) <= 0 &&
|
|
56
|
+
(!through || Support.compare_versions(version, through) <= 0)
|
|
59
57
|
end)
|
|
60
58
|
RootVersionSelection.new(
|
|
61
59
|
in_range: in_range,
|
|
@@ -73,21 +71,16 @@ module PaperTrailDiff
|
|
|
73
71
|
|
|
74
72
|
#: (untyped, untyped, boundary: Symbol) -> void
|
|
75
73
|
def validate_boundary!(requested, relation, boundary:)
|
|
76
|
-
Endpoint.validate!(requested)
|
|
77
74
|
return if valid_boundary?(requested, relation)
|
|
78
75
|
|
|
79
|
-
raise InvalidTimelineRangeError, "`#{boundary}` version is not in the record history"
|
|
80
|
-
rescue InvalidEndpointError, NoMethodError
|
|
81
76
|
raise InvalidTimelineRangeError, "`#{boundary}` version is not in the record history"
|
|
82
77
|
end
|
|
83
78
|
|
|
79
|
+
# The relation holds only this record's versions, so finding the id there
|
|
80
|
+
# also settles which record the version belongs to.
|
|
84
81
|
#: (untyped, untyped) -> bool
|
|
85
82
|
def valid_boundary?(requested, relation)
|
|
86
|
-
|
|
87
|
-
Endpoint.version?(requested) &&
|
|
88
|
-
requested.instance_of?(relation.klass) &&
|
|
89
|
-
Endpoint.identity(requested) == expected_identity &&
|
|
90
|
-
relation.where(id: requested.id).exists?
|
|
83
|
+
requested.instance_of?(relation.klass) && relation.exists?(requested.id)
|
|
91
84
|
end
|
|
92
85
|
end
|
|
93
86
|
end
|
|
@@ -14,13 +14,21 @@ module PaperTrailDiff
|
|
|
14
14
|
|
|
15
15
|
#: (untyped, Array[untyped]) -> Array[untyped]
|
|
16
16
|
def call(relation, in_range)
|
|
17
|
+
chosen = chosen_ids(relation)
|
|
18
|
+
return in_range unless chosen
|
|
19
|
+
|
|
20
|
+
in_range.select { |version| chosen.include?(version.id) }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The ids of the versions the scope keeps, or nil when there is no scope.
|
|
24
|
+
#: (untyped) -> Set[untyped]?
|
|
25
|
+
def chosen_ids(relation)
|
|
17
26
|
scope = @scope
|
|
18
|
-
return
|
|
27
|
+
return unless scope
|
|
19
28
|
|
|
20
29
|
# A narrowed relation is the expected return. Active Support also gives
|
|
21
30
|
# `pluck` to plain enumerables, so an array of versions works too.
|
|
22
|
-
|
|
23
|
-
in_range.select { |version| chosen.include?(version.id) }
|
|
31
|
+
Set.new(scope.call(relation).pluck(:id))
|
|
24
32
|
end
|
|
25
33
|
|
|
26
34
|
private
|
data/lib/paper_trail_diff.rb
CHANGED
|
@@ -12,7 +12,10 @@ require_relative 'paper_trail_diff/instrumentation'
|
|
|
12
12
|
require_relative 'paper_trail_diff/errors'
|
|
13
13
|
require_relative 'paper_trail_diff/configuration'
|
|
14
14
|
require_relative 'paper_trail_diff/endpoint'
|
|
15
|
+
require_relative 'paper_trail_diff/recorded_class'
|
|
15
16
|
require_relative 'paper_trail_diff/association_traversal'
|
|
17
|
+
require_relative 'paper_trail_diff/scope_ranking'
|
|
18
|
+
require_relative 'paper_trail_diff/scope_condition'
|
|
16
19
|
require_relative 'paper_trail_diff/batch_safety'
|
|
17
20
|
require_relative 'paper_trail_diff/traversal_preparer'
|
|
18
21
|
require_relative 'paper_trail_diff/association_discovery'
|
|
@@ -30,6 +33,7 @@ require_relative 'paper_trail_diff/traversal'
|
|
|
30
33
|
require_relative 'paper_trail_diff/collection_comparator'
|
|
31
34
|
require_relative 'paper_trail_diff/nested_comparator'
|
|
32
35
|
require_relative 'paper_trail_diff/engine'
|
|
36
|
+
require_relative 'paper_trail_diff/habtm_transaction_owners'
|
|
33
37
|
require_relative 'paper_trail_diff/historical_association_reifier'
|
|
34
38
|
require_relative 'paper_trail_diff/prepared_record_index'
|
|
35
39
|
require_relative 'paper_trail_diff/direct_record_state_loader'
|
|
@@ -56,6 +60,7 @@ require_relative 'paper_trail_diff/snapshot_normalizer'
|
|
|
56
60
|
require_relative 'paper_trail_diff/historical_snapshot_store'
|
|
57
61
|
require_relative 'paper_trail_diff/timeline_snapshot_provider'
|
|
58
62
|
require_relative 'paper_trail_diff/activity_event_record_resolver'
|
|
63
|
+
require_relative 'paper_trail_diff/recorded_changes'
|
|
59
64
|
require_relative 'paper_trail_diff/activity_event_route_finder'
|
|
60
65
|
require_relative 'paper_trail_diff/activity_relationship'
|
|
61
66
|
require_relative 'paper_trail_diff/activity_collection_route_change'
|
|
@@ -77,6 +82,8 @@ require_relative 'paper_trail_diff/analysis_batch'
|
|
|
77
82
|
require_relative 'paper_trail_diff/deleted_analysis_batch'
|
|
78
83
|
require_relative 'paper_trail_diff/scoped_analysis'
|
|
79
84
|
require_relative 'paper_trail_diff/historical_filter'
|
|
85
|
+
require_relative 'paper_trail_diff/association_activity_edge'
|
|
86
|
+
require_relative 'paper_trail_diff/association_activity_selection'
|
|
80
87
|
require_relative 'paper_trail_diff/scoped_root_selection'
|
|
81
88
|
require_relative 'paper_trail_diff/scoped_analysis_builder'
|
|
82
89
|
require_relative 'paper_trail_diff/batched_root_analyzer'
|
|
@@ -89,6 +96,7 @@ require_relative 'paper_trail_diff/timeline_builder'
|
|
|
89
96
|
require_relative 'paper_trail_diff/activity_range'
|
|
90
97
|
require_relative 'paper_trail_diff/activity_event'
|
|
91
98
|
require_relative 'paper_trail_diff/activity_child_candidate_loader'
|
|
99
|
+
require_relative 'paper_trail_diff/activity_target_candidate_loader'
|
|
92
100
|
require_relative 'paper_trail_diff/activity_version_collector'
|
|
93
101
|
require_relative 'paper_trail_diff/activity_snapshot_sequence'
|
|
94
102
|
require_relative 'paper_trail_diff/activity_history'
|
|
@@ -278,7 +286,9 @@ module PaperTrailDiff # rubocop:disable Metrics/ModuleLength
|
|
|
278
286
|
end
|
|
279
287
|
|
|
280
288
|
# Analyzes every root the relation reaches whose history moved inside the
|
|
281
|
-
# window.
|
|
289
|
+
# window. With `activity: true`, a change to a record on one of the
|
|
290
|
+
# requested association paths also counts, because the activity timeline
|
|
291
|
+
# reports it. Joined duplicates are read in bounded pages and analyzed once.
|
|
282
292
|
#
|
|
283
293
|
# `limit:` is required and exceeding it raises. Selection moves into the gem
|
|
284
294
|
# here. The relation's own limit/offset select the live page; this separate
|
|
@@ -328,7 +338,8 @@ module PaperTrailDiff # rubocop:disable Metrics/ModuleLength
|
|
|
328
338
|
)
|
|
329
339
|
end
|
|
330
340
|
|
|
331
|
-
# Recent root and selected-child activity through the current saved state
|
|
341
|
+
# Recent root and selected-child activity through the current saved state,
|
|
342
|
+
# for every root that changed or whose requested associations did.
|
|
332
343
|
# Historical windows remain explicit on analyze_scope; this entry point is
|
|
333
344
|
# deliberately named for a report through now rather than an as-of query.
|
|
334
345
|
#: (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
|
|
@@ -390,5 +401,23 @@ module PaperTrailDiff # rubocop:disable Metrics/ModuleLength
|
|
|
390
401
|
|
|
391
402
|
NestedComparator.call(change, to_value)
|
|
392
403
|
end
|
|
404
|
+
|
|
405
|
+
# Reads what PaperTrail recorded with one version: its `object_changes`,
|
|
406
|
+
# typed by the version's model, as `{ "title" => <from "Draft" to "Published"> }`.
|
|
407
|
+
#
|
|
408
|
+
# versions = PaperTrail::Version.where(id: steps.map { _1.source_boundary.version_id })
|
|
409
|
+
# versions.to_h { [_1.id, PaperTrailDiff.recorded_changes(_1)] }
|
|
410
|
+
#
|
|
411
|
+
# A step's diff compares the states before and after an event, so it also
|
|
412
|
+
# includes anything saved without a version in between; this does not.
|
|
413
|
+
# Returns nil when nothing was recorded (no `object_changes` column, or no
|
|
414
|
+
# value for this version), which differs from a recorded empty hash. Unlike
|
|
415
|
+
# PaperTrail's `version.changeset`, it never loads the record, so it costs no
|
|
416
|
+
# query per version and still types a deleted record's changes. `ignore:`
|
|
417
|
+
# takes attribute names, as an array, and defaults to `updated_at`.
|
|
418
|
+
#: (untyped, ?ignore: Array[String | Symbol]) -> Hash[String, ValueChange]?
|
|
419
|
+
def recorded_changes(version, ignore: DEFAULT_IGNORED_ATTRIBUTES)
|
|
420
|
+
RecordedChanges.call(version, ignore: ignore)
|
|
421
|
+
end
|
|
393
422
|
end
|
|
394
423
|
end
|
|
@@ -17,6 +17,11 @@ module PaperTrailDiff
|
|
|
17
17
|
|
|
18
18
|
@route_finder: ActivityEventRouteFinder
|
|
19
19
|
|
|
20
|
+
# An edit can move a target into or out of a scope, which a replacement
|
|
21
|
+
# in place cannot show, so a scoped route's branch is read again instead.
|
|
22
|
+
# : (Array[Array[untyped]]) -> bool
|
|
23
|
+
def scoped_route?: (Array[Array[untyped]]) -> bool
|
|
24
|
+
|
|
20
25
|
# : (Array[untyped], untyped, SnapshotNormalizer, untyped, untyped) -> RecordSnapshot?
|
|
21
26
|
def normalized_route_record: (Array[untyped], untyped, SnapshotNormalizer, untyped, untyped) -> RecordSnapshot?
|
|
22
27
|
|
|
@@ -29,8 +34,11 @@ module PaperTrailDiff
|
|
|
29
34
|
# : (Array[RecordSnapshot], untyped, RecordSnapshot?) -> [Array[RecordSnapshot], bool]
|
|
30
35
|
def replace_existing_target: (Array[RecordSnapshot], untyped, RecordSnapshot?) -> [ Array[RecordSnapshot], bool ]
|
|
31
36
|
|
|
32
|
-
#
|
|
33
|
-
|
|
37
|
+
# The target is matched by id within its table rather than by type name.
|
|
38
|
+
# Without `item_subtype`, a version names only the STI base class, and a
|
|
39
|
+
# row whose type changes is replaced by a record of another class.
|
|
40
|
+
# : (RecordSnapshot, untyped) -> bool
|
|
41
|
+
def same_table?: (RecordSnapshot, untyped) -> bool
|
|
34
42
|
|
|
35
43
|
# : (untyped) -> untyped
|
|
36
44
|
def endpoint_model_class: (untyped) -> untyped
|
|
@@ -25,6 +25,11 @@ module PaperTrailDiff
|
|
|
25
25
|
# : (RecordSnapshot, Array[untyped], untyped, ActivitySnapshotDelta?, Array[untyped], SnapshotNormalizer, Array[untyped], ?direct: bool) -> RecordSnapshot
|
|
26
26
|
def apply_route: (RecordSnapshot, Array[untyped], untyped, ActivitySnapshotDelta?, Array[untyped], SnapshotNormalizer, Array[untyped], ?direct: bool) -> RecordSnapshot
|
|
27
27
|
|
|
28
|
+
# Any edit can move a record into or out of a scope, so a scoped route's
|
|
29
|
+
# branch is read again from history instead.
|
|
30
|
+
# : (Array[Array[untyped]]) -> bool
|
|
31
|
+
def scoped_route?: (Array[Array[untyped]]) -> bool
|
|
32
|
+
|
|
28
33
|
# : (Array[Array[untyped]], untyped) -> bool
|
|
29
34
|
def unsafe_through_membership_event?: (Array[Array[untyped]], untyped) -> bool
|
|
30
35
|
|
|
@@ -19,6 +19,11 @@ module PaperTrailDiff
|
|
|
19
19
|
|
|
20
20
|
@relationship: ActivityRelationship
|
|
21
21
|
|
|
22
|
+
# A row whose STI type changed is the same row under a new identity, so it
|
|
23
|
+
# is found by id among the records of its table.
|
|
24
|
+
# : (AssociationSnapshot, untyped) -> Integer?
|
|
25
|
+
def retyped_position: (AssociationSnapshot, untyped) -> Integer?
|
|
26
|
+
|
|
22
27
|
# : (Array[RecordSnapshot], Integer?, RecordSnapshot?) -> Array[RecordSnapshot]
|
|
23
28
|
def replacement_records: (Array[RecordSnapshot], Integer?, RecordSnapshot?) -> Array[RecordSnapshot]
|
|
24
29
|
end
|
|
@@ -45,6 +45,13 @@ module PaperTrailDiff
|
|
|
45
45
|
# : (untyped) -> PreparedRecordState?
|
|
46
46
|
def direct_record_after: (untyped) -> PreparedRecordState?
|
|
47
47
|
|
|
48
|
+
# Without `item_subtype` a version names only the STI base class, and a
|
|
49
|
+
# change can move the row to another class, so the type the row holds
|
|
50
|
+
# after the change decides. A type that names no class has no direct
|
|
51
|
+
# state; the caller falls back to reifying the record.
|
|
52
|
+
# : (untyped, Hash[untyped, untyped]) -> untyped
|
|
53
|
+
def class_after: (untyped, Hash[untyped, untyped]) -> untyped
|
|
54
|
+
|
|
48
55
|
# : (untyped) -> untyped
|
|
49
56
|
def prepared_changeset: (untyped) -> untyped
|
|
50
57
|
|
|
@@ -21,14 +21,18 @@ module PaperTrailDiff
|
|
|
21
21
|
# : () -> bool
|
|
22
22
|
def current_end?: () -> bool
|
|
23
23
|
|
|
24
|
+
# Whether the state a version recorded after its change held inside the
|
|
25
|
+
# range. A range ending at a version ends at the state before it, so the
|
|
26
|
+
# change that version records is past the end, unlike #include?.
|
|
27
|
+
# : (untyped, untyped) -> bool
|
|
28
|
+
def recorded_before_end?: (untyped, untyped) -> bool
|
|
29
|
+
|
|
24
30
|
private
|
|
25
31
|
|
|
26
32
|
@end_boundary: untyped
|
|
27
33
|
|
|
28
34
|
@end_key: Array[untyped]?
|
|
29
35
|
|
|
30
|
-
@start_boundary: untyped
|
|
31
|
-
|
|
32
36
|
@start_key: Array[untyped]?
|
|
33
37
|
|
|
34
38
|
@start_time: untyped
|
|
@@ -11,6 +11,9 @@ module PaperTrailDiff
|
|
|
11
11
|
|
|
12
12
|
private
|
|
13
13
|
|
|
14
|
+
# : (untyped, untyped, Symbol) -> bool
|
|
15
|
+
def sti_member?: (untyped, untyped, Symbol) -> bool
|
|
16
|
+
|
|
14
17
|
# : (untyped, untyped, state: Symbol) -> Array[String]
|
|
15
18
|
def relationship_owner_values: (untyped, untyped, state: Symbol) -> Array[String]
|
|
16
19
|
|
|
@@ -36,6 +36,8 @@ module PaperTrailDiff
|
|
|
36
36
|
# : (untyped) -> Array[String]
|
|
37
37
|
def unsupported_branches: (untyped) -> Array[String]
|
|
38
38
|
|
|
39
|
+
# A polymorphic target's class is known only per record, so it cannot be
|
|
40
|
+
# checked here; a target that is not versioned raises when it is read.
|
|
39
41
|
# : (untyped) -> bool
|
|
40
42
|
def direct_versioned_reflection?: (untyped) -> bool
|
|
41
43
|
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Generated from lib/paper_trail_diff/activity_target_candidate_loader.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module PaperTrailDiff
|
|
4
|
+
# Loads the `belongs_to` targets owners pointed at during one activity range:
|
|
5
|
+
# at its start and after each change inside it.
|
|
6
|
+
#
|
|
7
|
+
# PT-AT records a version's foreign keys as they stood after its change, so
|
|
8
|
+
# the target at the start comes from the latest version before it, and the
|
|
9
|
+
# version a range ends at names a target only after the range. Reading only
|
|
10
|
+
# the owners' versions inside the range misses a target whose owner did not
|
|
11
|
+
# change there -- an author renamed under an article nobody edited.
|
|
12
|
+
class ActivityTargetCandidateLoader
|
|
13
|
+
# A plain class rather than Data.define, which arrived in Ruby 3.2 while this
|
|
14
|
+
# gem supports 3.1.
|
|
15
|
+
class Row
|
|
16
|
+
attr_reader owner_id: untyped
|
|
17
|
+
|
|
18
|
+
attr_reader foreign_key_id: untyped
|
|
19
|
+
|
|
20
|
+
attr_reader foreign_type: String?
|
|
21
|
+
|
|
22
|
+
attr_reader created_at: untyped
|
|
23
|
+
|
|
24
|
+
attr_reader event: String?
|
|
25
|
+
|
|
26
|
+
attr_reader version_id: untyped
|
|
27
|
+
|
|
28
|
+
# : (untyped, untyped, String?, ?untyped, ?String?, ?untyped) -> void
|
|
29
|
+
def initialize: (untyped, untyped, String?, ?untyped, ?String?, ?untyped) -> void
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# : (untyped, Array[untyped], untyped, ActivityRange) -> void
|
|
33
|
+
def initialize: (untyped, Array[untyped], untyped, ActivityRange) -> void
|
|
34
|
+
|
|
35
|
+
# : () -> Array[Row]
|
|
36
|
+
def call: () -> Array[Row]
|
|
37
|
+
|
|
38
|
+
# One shared query keeps the owner and timestamp needed to partition a
|
|
39
|
+
# batch's candidates back into each root's exact activity range.
|
|
40
|
+
# : () -> Array[Row]
|
|
41
|
+
def historical_rows: () -> Array[Row]
|
|
42
|
+
|
|
43
|
+
# The live row holds the state at a range that ends at the current record.
|
|
44
|
+
# : () -> Array[Row]
|
|
45
|
+
def current_rows: () -> Array[Row]
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
@owner_class: untyped
|
|
50
|
+
|
|
51
|
+
@owner_ids: Array[untyped]
|
|
52
|
+
|
|
53
|
+
@range: ActivityRange
|
|
54
|
+
|
|
55
|
+
@reflection: untyped
|
|
56
|
+
|
|
57
|
+
# : (untyped) -> untyped
|
|
58
|
+
def historical_relation: (untyped) -> untyped
|
|
59
|
+
|
|
60
|
+
# : (untyped, untyped) -> Array[Array[untyped]]
|
|
61
|
+
def historical_columns: (untyped, untyped) -> Array[Array[untyped]]
|
|
62
|
+
|
|
63
|
+
# A polymorphic key names its target in a type column; any other names it
|
|
64
|
+
# in the reflection, so no type is read. Like the history, the live read
|
|
65
|
+
# takes owners of every class under the base class: a batch can ask for
|
|
66
|
+
# the owners of an association through one subclass.
|
|
67
|
+
# : () -> Array[Array[untyped]]
|
|
68
|
+
def live_rows: () -> Array[Array[untyped]]
|
|
69
|
+
|
|
70
|
+
# : (untyped) -> VersionAssociationCandidateScope
|
|
71
|
+
def candidate_scope: (untyped) -> VersionAssociationCandidateScope
|
|
72
|
+
|
|
73
|
+
# : () -> untyped
|
|
74
|
+
def association_class: () -> untyped
|
|
75
|
+
|
|
76
|
+
# : () -> bool
|
|
77
|
+
def composite_foreign_key?: () -> bool
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -5,8 +5,8 @@ module PaperTrailDiff
|
|
|
5
5
|
class ActivityTimelineBuilder
|
|
6
6
|
include ActivityGrouping
|
|
7
7
|
|
|
8
|
-
# : (untyped, range: TimelineRange, tree: AssociationTree, snapshotter: untyped,
|
|
9
|
-
def initialize: (untyped, range: TimelineRange, tree: AssociationTree, snapshotter: untyped,
|
|
8
|
+
# : (untyped, range: TimelineRange, tree: AssociationTree, snapshotter: untyped, snapshots: bool, group: Symbol?) -> void
|
|
9
|
+
def initialize: (untyped, range: TimelineRange, tree: AssociationTree, snapshotter: untyped, snapshots: bool, group: Symbol?) -> void
|
|
10
10
|
|
|
11
11
|
# : () -> Array[ActivityStep]
|
|
12
12
|
def build: () -> Array[ActivityStep]
|
|
@@ -17,8 +17,6 @@ module PaperTrailDiff
|
|
|
17
17
|
|
|
18
18
|
private
|
|
19
19
|
|
|
20
|
-
@from: untyped
|
|
21
|
-
|
|
22
20
|
@range: TimelineRange
|
|
23
21
|
|
|
24
22
|
@record: untyped
|
|
@@ -37,7 +35,8 @@ module PaperTrailDiff
|
|
|
37
35
|
# Every boundary in the span, filtered out or not. A filter narrows where the
|
|
38
36
|
# span starts and ends, but the sequence inside it stays complete: dropping
|
|
39
37
|
# boundaries would fold the changes they carried into a neighbouring step and
|
|
40
|
-
# credit them to whoever made that one.
|
|
38
|
+
# credit them to whoever made that one. A filter that keeps no version
|
|
39
|
+
# leaves no span, as it does for a checkpoint timeline.
|
|
41
40
|
# : () -> Array[ActivityStep]
|
|
42
41
|
def build_between_versions: () -> Array[ActivityStep]
|
|
43
42
|
|
|
@@ -52,6 +51,12 @@ module PaperTrailDiff
|
|
|
52
51
|
# : () -> [RootVersionPlan, Array[ActivityEvent], ActivityHistory, RecordSnapshot?, ActivityBoundary?]
|
|
53
52
|
def current_history: () -> [ RootVersionPlan, Array[ActivityEvent], ActivityHistory, RecordSnapshot?, ActivityBoundary? ]
|
|
54
53
|
|
|
54
|
+
# Without a closing boundary nothing reads the events or the closing pair,
|
|
55
|
+
# so their mutations cannot change an answer.
|
|
56
|
+
# mutant:disable
|
|
57
|
+
# : (RootVersionPlan) -> [RootVersionPlan, Array[ActivityEvent], ActivityHistory, RecordSnapshot?, ActivityBoundary?]
|
|
58
|
+
def empty_history: (RootVersionPlan) -> [ RootVersionPlan, Array[ActivityEvent], ActivityHistory, RecordSnapshot?, ActivityBoundary? ]
|
|
59
|
+
|
|
55
60
|
# : () -> [RecordSnapshot?, untyped]
|
|
56
61
|
def capture_current: () -> [ RecordSnapshot?, untyped ]
|
|
57
62
|
|
|
@@ -66,6 +71,7 @@ module PaperTrailDiff
|
|
|
66
71
|
|
|
67
72
|
# Appends the transition into an explicit closing boundary, which is either
|
|
68
73
|
# a requested current record or the absence a final root destroy leaves.
|
|
74
|
+
# Either follows the root versions, which are themselves events.
|
|
69
75
|
# : (ActivityHistory, Array[ActivityEvent], ActivityBoundary?, RecordSnapshot?) -> Array[ActivityStep]
|
|
70
76
|
def activity_steps: (ActivityHistory, Array[ActivityEvent], ActivityBoundary?, RecordSnapshot?) -> Array[ActivityStep]
|
|
71
77
|
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
module PaperTrailDiff
|
|
4
4
|
# Collects root and selected-descendant versions relevant to one root history range.
|
|
5
5
|
class ActivityVersionCollector
|
|
6
|
-
# : (untyped, root_versions: Array[untyped], tree: AssociationTree,
|
|
7
|
-
def initialize: (untyped, root_versions: Array[untyped], tree: AssociationTree,
|
|
6
|
+
# : (untyped, root_versions: Array[untyped], tree: AssociationTree, ?range_start: untyped, ?range_end: untyped, ?prepared: PreparedActivityIndex?) -> void
|
|
7
|
+
def initialize: (untyped, root_versions: Array[untyped], tree: AssociationTree, ?range_start: untyped, ?range_end: untyped, ?prepared: PreparedActivityIndex?) -> void
|
|
8
8
|
|
|
9
9
|
# : () -> Array[ActivityEvent]
|
|
10
10
|
def call: () -> Array[ActivityEvent]
|
|
@@ -37,17 +37,23 @@ module PaperTrailDiff
|
|
|
37
37
|
# : (Hash[Symbol, untyped], untyped, String) -> Hash[String, untyped]
|
|
38
38
|
def through_groups: (Hash[Symbol, untyped], untyped, String) -> Hash[String, untyped]
|
|
39
39
|
|
|
40
|
+
# Every target the owners pointed at in the range, not only those their own
|
|
41
|
+
# changes there recorded: an author renamed under an article nobody edited
|
|
42
|
+
# is still that article's activity.
|
|
40
43
|
# : (untyped, Array[untyped], untyped) -> Hash[String, untyped]
|
|
41
44
|
def belongs_to_groups: (untyped, Array[untyped], untyped) -> Hash[String, untyped]
|
|
42
45
|
|
|
46
|
+
# : (untyped, Array[untyped], untyped) -> Hash[String, untyped]
|
|
47
|
+
def unprepared_belongs_to_groups: (untyped, Array[untyped], untyped) -> Hash[String, untyped]
|
|
48
|
+
|
|
43
49
|
# : (untyped, Array[untyped], untyped) -> Hash[String, untyped]
|
|
44
50
|
def direct_child_groups: (untyped, Array[untyped], untyped) -> Hash[String, untyped]
|
|
45
51
|
|
|
46
52
|
# : (untyped, Array[untyped], untyped) -> Hash[String, untyped]
|
|
47
53
|
def habtm_groups: (untyped, Array[untyped], untyped) -> Hash[String, untyped]
|
|
48
54
|
|
|
49
|
-
# : (Array[untyped], untyped) -> Hash[String, untyped]
|
|
50
|
-
def identity_groups: (Array[untyped], untyped) -> Hash[String, untyped]
|
|
55
|
+
# : (Array[untyped], untyped) ?{ (untyped, untyped) -> bool } -> Hash[String, untyped]
|
|
56
|
+
def identity_groups: (Array[untyped], untyped) ?{ (untyped, untyped) -> bool } -> Hash[String, untyped]
|
|
51
57
|
|
|
52
58
|
# : (untyped, Array[untyped]) -> Hash[Symbol, untyped]
|
|
53
59
|
def group: (untyped, Array[untyped]) -> Hash[Symbol, untyped]
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Generated from lib/paper_trail_diff/association_activity_edge.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module PaperTrailDiff
|
|
4
|
+
# One association edge read backwards for a window: given the ids of records
|
|
5
|
+
# on its far side, the query for the owners that held them while it was open.
|
|
6
|
+
#
|
|
7
|
+
# `filter_type` is the type of the column those ids are compared with, and
|
|
8
|
+
# `type` that of the owner column the query selects, so a caller can keep a
|
|
9
|
+
# subquery only where the database can compare the two.
|
|
10
|
+
class AssociationActivityEdge
|
|
11
|
+
attr_reader column: untyped
|
|
12
|
+
|
|
13
|
+
attr_reader type: Symbol?
|
|
14
|
+
|
|
15
|
+
attr_reader filter_type: Symbol?
|
|
16
|
+
|
|
17
|
+
# : (untyped, TimeRange?) -> void
|
|
18
|
+
def initialize: (untyped, TimeRange?) -> void
|
|
19
|
+
|
|
20
|
+
# : () -> bool
|
|
21
|
+
def available?: () -> bool
|
|
22
|
+
|
|
23
|
+
# : (untyped) -> untyped
|
|
24
|
+
def relation: (untyped) -> untyped
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
@owner: untyped
|
|
29
|
+
|
|
30
|
+
@reflection: untyped
|
|
31
|
+
|
|
32
|
+
@time_range: TimeRange?
|
|
33
|
+
|
|
34
|
+
# The window plus the state when it opened, which is what
|
|
35
|
+
# VersionAssociationCandidateScope selects for an activity analysis.
|
|
36
|
+
# : (untyped, untyped) -> untyped
|
|
37
|
+
def candidates: (untyped, untyped) -> untyped
|
|
38
|
+
|
|
39
|
+
# : (untyped, String) -> Symbol?
|
|
40
|
+
def column_type: (untyped, String) -> Symbol?
|
|
41
|
+
|
|
42
|
+
# A relation is compared as its SQL; ids are compared as values.
|
|
43
|
+
# : (untyped) -> untyped
|
|
44
|
+
def arel_value: (untyped) -> untyped
|
|
45
|
+
|
|
46
|
+
# The owners a child pointed at when the window opened or after a change
|
|
47
|
+
# inside it, as PT-AT recorded them on the child's own versions.
|
|
48
|
+
class Child < AssociationActivityEdge
|
|
49
|
+
# : (untyped, TimeRange?) -> void
|
|
50
|
+
def initialize: (untyped, TimeRange?) -> void
|
|
51
|
+
|
|
52
|
+
# : (untyped) -> untyped
|
|
53
|
+
def relation: (untyped) -> untyped
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
@associations: untyped
|
|
58
|
+
|
|
59
|
+
@versions: untyped
|
|
60
|
+
|
|
61
|
+
# : () -> Array[String?]
|
|
62
|
+
def owner_types: () -> Array[String?]
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# The owners that pointed at a target when the window opened or after a
|
|
66
|
+
# change inside it, as PT-AT recorded them on the owners' versions.
|
|
67
|
+
class BelongsTo < AssociationActivityEdge
|
|
68
|
+
# : (untyped, TimeRange?, ?foreign_type: String?) -> void
|
|
69
|
+
def initialize: (untyped, TimeRange?, ?foreign_type: String?) -> void
|
|
70
|
+
|
|
71
|
+
# The types a polymorphic key's rows name, the only way to know its targets.
|
|
72
|
+
# : () -> Array[String]
|
|
73
|
+
def target_types: () -> Array[String]
|
|
74
|
+
|
|
75
|
+
# The owners that pointed at a target of this edge's type when the window
|
|
76
|
+
# opened or after a change inside it, whichever target that was.
|
|
77
|
+
# : () -> untyped
|
|
78
|
+
def typed_owners: () -> untyped
|
|
79
|
+
|
|
80
|
+
# : (untyped) -> untyped
|
|
81
|
+
def relation: (untyped) -> untyped
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
@associations: untyped
|
|
86
|
+
|
|
87
|
+
@foreign_type: String?
|
|
88
|
+
|
|
89
|
+
@versions: untyped
|
|
90
|
+
|
|
91
|
+
# : (Hash[Symbol, untyped]) -> untyped
|
|
92
|
+
def owner_rows: (Hash[Symbol, untyped]) -> untyped
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# The owners a HABTM join row links to a target now. A join table has no
|
|
96
|
+
# model to type its columns, so the owner column is read by quoted name.
|
|
97
|
+
class LiveHabtm < AssociationActivityEdge
|
|
98
|
+
# : (untyped, TimeRange?) -> void
|
|
99
|
+
def initialize: (untyped, TimeRange?) -> void
|
|
100
|
+
|
|
101
|
+
# : (untyped) -> untyped
|
|
102
|
+
def relation: (untyped) -> untyped
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
@table: untyped
|
|
107
|
+
|
|
108
|
+
# : () -> Hash[String, Symbol?]
|
|
109
|
+
def join_columns: () -> Hash[String, Symbol?]
|
|
110
|
+
|
|
111
|
+
# Some adapters take the quoted name as a plain string too, but a raw SQL
|
|
112
|
+
# check may not, so it is marked as SQL; no SQLite spec can tell.
|
|
113
|
+
# mutant:disable
|
|
114
|
+
# : () -> untyped
|
|
115
|
+
def quoted_owner_column: () -> untyped
|
|
116
|
+
|
|
117
|
+
# Arel ships no signatures; see VersionQuery. Any spelling of the
|
|
118
|
+
# constant finds the same module.
|
|
119
|
+
# mutant:disable
|
|
120
|
+
# : () -> untyped
|
|
121
|
+
def arel: () -> untyped
|
|
122
|
+
|
|
123
|
+
# Active Record 7.1 has no `with_connection`; see VersionAssociationCandidateScope.
|
|
124
|
+
# Only that version takes the fallback, so no spec run on a newer one can
|
|
125
|
+
# tell its mutations apart.
|
|
126
|
+
# mutant:disable
|
|
127
|
+
# : [T] () { (untyped) -> T } -> T
|
|
128
|
+
def connected: [T] () { (untyped) -> T } -> T
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# The owners whose versions PT-AT recorded a HABTM target with, keyed by
|
|
132
|
+
# the transaction each version was saved in.
|
|
133
|
+
class RecordedHabtm < AssociationActivityEdge
|
|
134
|
+
# : (untyped, TimeRange?) -> void
|
|
135
|
+
def initialize: (untyped, TimeRange?) -> void
|
|
136
|
+
|
|
137
|
+
# : () -> bool
|
|
138
|
+
def available?: () -> bool
|
|
139
|
+
|
|
140
|
+
# : (untyped) -> untyped
|
|
141
|
+
def relation: (untyped) -> untyped
|
|
142
|
+
|
|
143
|
+
@associations: untyped
|
|
144
|
+
|
|
145
|
+
@versions: untyped
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|