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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +343 -0
- data/QUICKSTART.md +21 -4
- data/README.md +58 -55
- data/docs/architecture.md +32 -11
- data/docs/performance.md +116 -391
- data/docs/reference.md +345 -106
- data/docs/testing.md +63 -7
- data/lib/paper_trail_diff/activity_boundary.rb +83 -26
- data/lib/paper_trail_diff/activity_child_candidate_loader.rb +2 -1
- data/lib/paper_trail_diff/activity_event.rb +41 -10
- data/lib/paper_trail_diff/activity_history.rb +34 -9
- data/lib/paper_trail_diff/activity_range.rb +18 -3
- data/lib/paper_trail_diff/activity_snapshot_sequence.rb +76 -37
- data/lib/paper_trail_diff/activity_target_candidate_loader.rb +5 -5
- data/lib/paper_trail_diff/activity_timeline_builder.rb +266 -111
- data/lib/paper_trail_diff/activity_transaction_grouper.rb +2 -1
- data/lib/paper_trail_diff/activity_tree_filter.rb +114 -0
- data/lib/paper_trail_diff/activity_version_collector.rb +7 -5
- data/lib/paper_trail_diff/activity_view.rb +69 -0
- data/lib/paper_trail_diff/analysis.rb +13 -1
- data/lib/paper_trail_diff/analysis_batch.rb +24 -30
- data/lib/paper_trail_diff/approximate_paths.rb +48 -0
- data/lib/paper_trail_diff/association_activity_edge.rb +6 -6
- data/lib/paper_trail_diff/association_discovery.rb +9 -1
- data/lib/paper_trail_diff/association_key.rb +56 -0
- data/lib/paper_trail_diff/association_traversal.rb +13 -4
- data/lib/paper_trail_diff/batch_boundary_resolver.rb +3 -4
- data/lib/paper_trail_diff/batch_preload.rb +53 -0
- data/lib/paper_trail_diff/batched_root_analyzer.rb +12 -14
- data/lib/paper_trail_diff/batched_root_versions.rb +38 -14
- data/lib/paper_trail_diff/change_row.rb +133 -0
- data/lib/paper_trail_diff/collection_comparator.rb +5 -32
- data/lib/paper_trail_diff/configuration.rb +35 -12
- data/lib/paper_trail_diff/deleted_analysis_batch.rb +18 -18
- data/lib/paper_trail_diff/deleted_root_filter.rb +79 -0
- data/lib/paper_trail_diff/deprecation.rb +61 -0
- data/lib/paper_trail_diff/diagnostics.rb +84 -99
- data/lib/paper_trail_diff/endpoint.rb +26 -3
- data/lib/paper_trail_diff/errors.rb +3 -1
- data/lib/paper_trail_diff/fallback_paths.rb +38 -0
- data/lib/paper_trail_diff/historical_association_reifier.rb +2 -4
- data/lib/paper_trail_diff/historical_filter.rb +19 -10
- data/lib/paper_trail_diff/historical_snapshot_store.rb +57 -67
- data/lib/paper_trail_diff/live_endpoint_batch_loader.rb +1 -35
- data/lib/paper_trail_diff/nested_comparator.rb +19 -18
- data/lib/paper_trail_diff/paper_trail_adapter.rb +122 -127
- data/lib/paper_trail_diff/preloaded_endpoint_batch_loader.rb +13 -1
- data/lib/paper_trail_diff/prepared_activity_index.rb +1 -1
- data/lib/paper_trail_diff/prepared_edge_loader.rb +2 -1
- data/lib/paper_trail_diff/prepared_history.rb +9 -1
- data/lib/paper_trail_diff/prepared_history_loader.rb +1 -0
- data/lib/paper_trail_diff/prepared_record_index.rb +38 -7
- data/lib/paper_trail_diff/root_version_selection.rb +4 -2
- data/lib/paper_trail_diff/scope_condition.rb +76 -21
- data/lib/paper_trail_diff/scope_ranking.rb +12 -2
- data/lib/paper_trail_diff/scoped_analysis.rb +11 -0
- data/lib/paper_trail_diff/scoped_analysis_builder.rb +16 -8
- data/lib/paper_trail_diff/scoped_root_selection.rb +18 -12
- data/lib/paper_trail_diff/snapshot.rb +16 -1
- data/lib/paper_trail_diff/support.rb +29 -2
- data/lib/paper_trail_diff/time_range.rb +32 -10
- data/lib/paper_trail_diff/timeline_builder.rb +7 -7
- data/lib/paper_trail_diff/timeline_range.rb +9 -1
- data/lib/paper_trail_diff/timeline_snapshot_provider.rb +24 -17
- data/lib/paper_trail_diff/transaction_cut.rb +181 -0
- data/lib/paper_trail_diff/traversal_preparer.rb +134 -29
- data/lib/paper_trail_diff/version.rb +1 -1
- data/lib/paper_trail_diff/version_boundary.rb +37 -8
- data/lib/paper_trail_diff/version_query.rb +34 -10
- data/lib/paper_trail_diff/version_scope_filter.rb +9 -10
- data/lib/paper_trail_diff/version_sequence_diagnostics.rb +31 -16
- data/lib/paper_trail_diff/version_table.rb +60 -0
- data/lib/paper_trail_diff.rb +98 -56
- data/sig/generated/paper_trail_diff/activity_boundary.rbs +54 -19
- data/sig/generated/paper_trail_diff/activity_event.rbs +25 -4
- data/sig/generated/paper_trail_diff/activity_history.rbs +21 -6
- data/sig/generated/paper_trail_diff/activity_range.rbs +9 -2
- data/sig/generated/paper_trail_diff/activity_snapshot_sequence.rbs +48 -9
- data/sig/generated/paper_trail_diff/activity_timeline_builder.rbs +134 -30
- data/sig/generated/paper_trail_diff/activity_tree_filter.rbs +73 -0
- data/sig/generated/paper_trail_diff/activity_version_collector.rbs +2 -0
- data/sig/generated/paper_trail_diff/activity_view.rbs +42 -0
- data/sig/generated/paper_trail_diff/analysis.rbs +8 -1
- data/sig/generated/paper_trail_diff/analysis_batch.rbs +8 -15
- data/sig/generated/paper_trail_diff/approximate_paths.rbs +23 -0
- data/sig/generated/paper_trail_diff/association_discovery.rbs +5 -0
- data/sig/generated/paper_trail_diff/association_key.rbs +29 -0
- data/sig/generated/paper_trail_diff/association_traversal.rbs +4 -0
- data/sig/generated/paper_trail_diff/batch_preload.rbs +29 -0
- data/sig/generated/paper_trail_diff/batched_root_analyzer.rbs +9 -10
- data/sig/generated/paper_trail_diff/batched_root_versions.rbs +14 -4
- data/sig/generated/paper_trail_diff/change_row.rbs +86 -0
- data/sig/generated/paper_trail_diff/collection_comparator.rbs +2 -16
- data/sig/generated/paper_trail_diff/configuration.rbs +19 -3
- data/sig/generated/paper_trail_diff/deleted_analysis_batch.rbs +8 -8
- data/sig/generated/paper_trail_diff/deleted_root_filter.rbs +48 -0
- data/sig/generated/paper_trail_diff/deprecation.rbs +35 -0
- data/sig/generated/paper_trail_diff/diagnostics.rbs +37 -30
- data/sig/generated/paper_trail_diff/endpoint.rbs +11 -2
- data/sig/generated/paper_trail_diff/errors.rbs +3 -1
- data/sig/generated/paper_trail_diff/fallback_paths.rbs +22 -0
- data/sig/generated/paper_trail_diff/historical_filter.rbs +12 -2
- data/sig/generated/paper_trail_diff/historical_snapshot_store.rbs +33 -28
- data/sig/generated/paper_trail_diff/live_endpoint_batch_loader.rbs +0 -15
- data/sig/generated/paper_trail_diff/nested_comparator.rbs +10 -7
- data/sig/generated/paper_trail_diff/paper_trail_adapter.rbs +54 -52
- data/sig/generated/paper_trail_diff/preloaded_endpoint_batch_loader.rbs +9 -1
- data/sig/generated/paper_trail_diff/prepared_history.rbs +5 -0
- data/sig/generated/paper_trail_diff/prepared_record_index.rbs +19 -2
- data/sig/generated/paper_trail_diff/root_version_selection.rbs +2 -0
- data/sig/generated/paper_trail_diff/scope_condition.rbs +43 -9
- data/sig/generated/paper_trail_diff/scope_ranking.rbs +8 -0
- data/sig/generated/paper_trail_diff/scoped_analysis.rbs +6 -0
- data/sig/generated/paper_trail_diff/scoped_analysis_builder.rbs +6 -1
- data/sig/generated/paper_trail_diff/scoped_root_selection.rbs +8 -4
- data/sig/generated/paper_trail_diff/snapshot.rbs +8 -0
- data/sig/generated/paper_trail_diff/support.rbs +15 -0
- data/sig/generated/paper_trail_diff/time_range.rbs +20 -6
- data/sig/generated/paper_trail_diff/timeline_builder.rbs +2 -2
- data/sig/generated/paper_trail_diff/timeline_range.rbs +2 -0
- data/sig/generated/paper_trail_diff/timeline_snapshot_provider.rbs +17 -7
- data/sig/generated/paper_trail_diff/transaction_cut.rbs +94 -0
- data/sig/generated/paper_trail_diff/traversal_preparer.rbs +66 -8
- data/sig/generated/paper_trail_diff/version_boundary.rbs +20 -3
- data/sig/generated/paper_trail_diff/version_query.rbs +18 -2
- data/sig/generated/paper_trail_diff/version_scope_filter.rbs +4 -5
- data/sig/generated/paper_trail_diff/version_sequence_diagnostics.rbs +16 -9
- data/sig/generated/paper_trail_diff/version_table.rbs +30 -0
- data/sig/generated/paper_trail_diff.rbs +29 -26
- metadata +46 -10
- data/lib/paper_trail_diff/activity_grouping.rb +0 -38
- data/lib/paper_trail_diff/batch_safety.rb +0 -63
- data/lib/paper_trail_diff/time_activity_timeline_builder.rb +0 -223
- data/sig/generated/paper_trail_diff/activity_grouping.rbs +0 -22
- data/sig/generated/paper_trail_diff/batch_safety.rbs +0 -30
- data/sig/generated/paper_trail_diff/time_activity_timeline_builder.rbs +0 -92
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
module PaperTrailDiff
|
|
5
5
|
# Checks the recorded sequence itself, before any question of association
|
|
6
6
|
# setup: whether the versions can be ordered at all, and whether timestamps
|
|
7
|
-
# they share hide changes
|
|
7
|
+
# they share hide changes from a path PT-AT reads.
|
|
8
8
|
class VersionSequenceDiagnostics
|
|
9
|
-
#: (untyped, untyped, ?
|
|
10
|
-
def initialize(from_version, to_version,
|
|
9
|
+
#: (untyped, untyped, ?fallback_selected: bool) -> void
|
|
10
|
+
def initialize(from_version, to_version, fallback_selected: false)
|
|
11
11
|
@from_version = from_version
|
|
12
12
|
@to_version = to_version
|
|
13
|
-
@
|
|
13
|
+
@fallback_selected = fallback_selected
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
#: () -> Array[DiagnosticIssue]
|
|
@@ -18,7 +18,7 @@ module PaperTrailDiff
|
|
|
18
18
|
versions = ordered_range_versions
|
|
19
19
|
issues = [] #: Array[DiagnosticIssue?]
|
|
20
20
|
issues << unorderable(versions)
|
|
21
|
-
issues << tied_timestamps(versions) if @
|
|
21
|
+
issues << tied_timestamps(versions) if @fallback_selected
|
|
22
22
|
issues.compact
|
|
23
23
|
rescue StandardError
|
|
24
24
|
[]
|
|
@@ -28,7 +28,7 @@ module PaperTrailDiff
|
|
|
28
28
|
|
|
29
29
|
# @rbs @from_version: untyped
|
|
30
30
|
# @rbs @to_version: untyped
|
|
31
|
-
# @rbs @
|
|
31
|
+
# @rbs @fallback_selected: bool
|
|
32
32
|
|
|
33
33
|
# Ordering falls back to the id when timestamps tie, which only recovers the
|
|
34
34
|
# real sequence for ids that increase with insertion. Reported before a run
|
|
@@ -46,11 +46,12 @@ module PaperTrailDiff
|
|
|
46
46
|
)
|
|
47
47
|
end
|
|
48
48
|
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
#
|
|
53
|
-
#
|
|
49
|
+
# Loaded history orders versions that share a timestamp by id, but PT-AT
|
|
50
|
+
# places association changes by timestamp alone, so a path it reads loses
|
|
51
|
+
# any change between such a pair. Unlike an unorderable sequence this is
|
|
52
|
+
# not always wrong: if nothing that path reaches changed between them the
|
|
53
|
+
# result is correct, and the gem cannot tell which it is, because not
|
|
54
|
+
# seeing the change is the symptom. So it warns.
|
|
54
55
|
#: (Array[untyped]) -> DiagnosticIssue?
|
|
55
56
|
def tied_timestamps(versions)
|
|
56
57
|
pair = Support.tied_timestamp_pair(versions)
|
|
@@ -61,22 +62,36 @@ module PaperTrailDiff
|
|
|
61
62
|
severity: :warning,
|
|
62
63
|
code: :tied_version_timestamps,
|
|
63
64
|
message: "versions #{left.id.inspect} and #{right.id.inspect} share the timestamp " \
|
|
64
|
-
"#{left.created_at.inspect},
|
|
65
|
-
'
|
|
65
|
+
"#{left.created_at.inspect}, and an association requested here is read with " \
|
|
66
|
+
'PT-AT, which places association changes by timestamp alone, so a change ' \
|
|
67
|
+
'between them can be missed; record versions at sub-second precision to ' \
|
|
68
|
+
'separate them',
|
|
66
69
|
version_id: left.id
|
|
67
70
|
)
|
|
68
71
|
end
|
|
69
72
|
|
|
70
73
|
#: () -> Array[untyped]
|
|
71
74
|
def ordered_range_versions
|
|
72
|
-
|
|
73
|
-
return [] unless
|
|
75
|
+
range = time_range
|
|
76
|
+
return [] unless range
|
|
74
77
|
|
|
75
78
|
@from_version.class
|
|
76
79
|
.where(item_type: @from_version.item_type, item_id: @from_version.item_id)
|
|
77
|
-
.where(created_at:
|
|
80
|
+
.where(created_at: range)
|
|
78
81
|
.to_a
|
|
79
82
|
.sort_by { |version| Support.chronological_version_key(version) }
|
|
80
83
|
end
|
|
84
|
+
|
|
85
|
+
# Between the two versions, in either order, or from the one version on
|
|
86
|
+
# when the other endpoint is the current record. A version that recorded
|
|
87
|
+
# no time bounds nothing.
|
|
88
|
+
#: () -> Range[untyped]?
|
|
89
|
+
def time_range
|
|
90
|
+
from = @from_version.created_at
|
|
91
|
+
return from && (from..) unless @to_version
|
|
92
|
+
|
|
93
|
+
bounds = [from, @to_version.created_at].compact.sort
|
|
94
|
+
bounds.first..bounds.last if bounds.length == 2
|
|
95
|
+
end
|
|
81
96
|
end
|
|
82
97
|
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
module PaperTrailDiff
|
|
5
|
+
# PT-AT reads an association's history from its owner's version tables: it
|
|
6
|
+
# looks for a child's versions in the owner's version table, and numbers a
|
|
7
|
+
# transaction by the first version saved in it, in whichever table. A model
|
|
8
|
+
# whose versions, or version associations, live in another table than its
|
|
9
|
+
# owner's would read other records' versions or none, so it is refused.
|
|
10
|
+
module VersionTable
|
|
11
|
+
# Why the association, or a part of a through, cannot be read across
|
|
12
|
+
# version tables.
|
|
13
|
+
#: (untyped) -> String?
|
|
14
|
+
def self.unsupported_reason(reflection)
|
|
15
|
+
part = separate_part(reflection)
|
|
16
|
+
return unless part
|
|
17
|
+
|
|
18
|
+
"#{part.active_record.name}##{part.name} keeps versions in #{tables(part.active_record)} " \
|
|
19
|
+
"but #{part.klass.name} in #{tables(part.klass)}, and association history is read " \
|
|
20
|
+
'from one set of version tables'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# The reflection, or the first part of a through, whose ends keep versions
|
|
24
|
+
# in different tables. A polymorphic belongs_to names no single target,
|
|
25
|
+
# and an unversioned target has no history to compare.
|
|
26
|
+
#: (untyped) -> untyped
|
|
27
|
+
def self.separate_part(reflection)
|
|
28
|
+
if reflection.through_reflection?
|
|
29
|
+
return [reflection.through_reflection, reflection.source_reflection].filter_map do |part|
|
|
30
|
+
separate_part(part)
|
|
31
|
+
end.first
|
|
32
|
+
end
|
|
33
|
+
return if reflection.polymorphic?
|
|
34
|
+
|
|
35
|
+
owner = reflection.active_record
|
|
36
|
+
target = reflection.klass
|
|
37
|
+
return unless Support.versioned?(owner) && Support.versioned?(target)
|
|
38
|
+
|
|
39
|
+
reflection unless tables(owner) == tables(target)
|
|
40
|
+
end
|
|
41
|
+
private_class_method :separate_part
|
|
42
|
+
|
|
43
|
+
# PT-AT's version association rows joined to the versions they were saved
|
|
44
|
+
# with. Its `belongs_to :version` names PaperTrail's default class whatever
|
|
45
|
+
# table a model's versions are in, so the join names the table instead.
|
|
46
|
+
#: (untyped, untyped) -> untyped
|
|
47
|
+
def self.joined(associations, versions)
|
|
48
|
+
rows = associations.arel_table
|
|
49
|
+
table = versions.arel_table
|
|
50
|
+
associations.joins(rows.join(table).on(table[:id].eq(rows[:version_id])).join_sources)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
#: (untyped) -> String
|
|
54
|
+
def self.tables(model)
|
|
55
|
+
history = model.paper_trail
|
|
56
|
+
"#{history.version_class.table_name} and #{history.version_association_class.table_name}"
|
|
57
|
+
end
|
|
58
|
+
private_class_method :tables
|
|
59
|
+
end
|
|
60
|
+
end
|
data/lib/paper_trail_diff.rb
CHANGED
|
@@ -9,14 +9,18 @@ require_relative 'paper_trail_diff/version_query'
|
|
|
9
9
|
require_relative 'paper_trail_diff/stored_time_precision'
|
|
10
10
|
require_relative 'paper_trail_diff/version_boundary'
|
|
11
11
|
require_relative 'paper_trail_diff/instrumentation'
|
|
12
|
+
require_relative 'paper_trail_diff/deprecation'
|
|
12
13
|
require_relative 'paper_trail_diff/errors'
|
|
13
14
|
require_relative 'paper_trail_diff/configuration'
|
|
14
15
|
require_relative 'paper_trail_diff/endpoint'
|
|
15
16
|
require_relative 'paper_trail_diff/recorded_class'
|
|
17
|
+
require_relative 'paper_trail_diff/association_key'
|
|
18
|
+
require_relative 'paper_trail_diff/version_table'
|
|
16
19
|
require_relative 'paper_trail_diff/association_traversal'
|
|
17
20
|
require_relative 'paper_trail_diff/scope_ranking'
|
|
18
21
|
require_relative 'paper_trail_diff/scope_condition'
|
|
19
|
-
require_relative 'paper_trail_diff/
|
|
22
|
+
require_relative 'paper_trail_diff/approximate_paths'
|
|
23
|
+
require_relative 'paper_trail_diff/fallback_paths'
|
|
20
24
|
require_relative 'paper_trail_diff/traversal_preparer'
|
|
21
25
|
require_relative 'paper_trail_diff/association_discovery'
|
|
22
26
|
require_relative 'paper_trail_diff/diagnostics'
|
|
@@ -30,6 +34,7 @@ require_relative 'paper_trail_diff/traversal_emitter'
|
|
|
30
34
|
require_relative 'paper_trail_diff/snapshot_traversal'
|
|
31
35
|
require_relative 'paper_trail_diff/association_diff_traversal'
|
|
32
36
|
require_relative 'paper_trail_diff/traversal'
|
|
37
|
+
require_relative 'paper_trail_diff/change_row'
|
|
33
38
|
require_relative 'paper_trail_diff/collection_comparator'
|
|
34
39
|
require_relative 'paper_trail_diff/nested_comparator'
|
|
35
40
|
require_relative 'paper_trail_diff/engine'
|
|
@@ -46,6 +51,7 @@ require_relative 'paper_trail_diff/prepared_edge_loader'
|
|
|
46
51
|
require_relative 'paper_trail_diff/prepared_history_loader'
|
|
47
52
|
require_relative 'paper_trail_diff/prepared_association_reifier'
|
|
48
53
|
require_relative 'paper_trail_diff/live_association_reader'
|
|
54
|
+
require_relative 'paper_trail_diff/batch_preload'
|
|
49
55
|
require_relative 'paper_trail_diff/live_endpoint_batch_loader'
|
|
50
56
|
require_relative 'paper_trail_diff/preloaded_endpoint_batch_loader'
|
|
51
57
|
require_relative 'paper_trail_diff/live_endpoint_provider'
|
|
@@ -73,7 +79,7 @@ require_relative 'paper_trail_diff/activity_event_snapshot_refresher'
|
|
|
73
79
|
require_relative 'paper_trail_diff/activity_root_snapshot_refresher'
|
|
74
80
|
require_relative 'paper_trail_diff/branch_snapshot_refresher'
|
|
75
81
|
require_relative 'paper_trail_diff/activity_boundary'
|
|
76
|
-
require_relative 'paper_trail_diff/
|
|
82
|
+
require_relative 'paper_trail_diff/activity_view'
|
|
77
83
|
require_relative 'paper_trail_diff/activity_transaction_grouper'
|
|
78
84
|
require_relative 'paper_trail_diff/step'
|
|
79
85
|
require_relative 'paper_trail_diff/analysis'
|
|
@@ -85,11 +91,13 @@ require_relative 'paper_trail_diff/historical_filter'
|
|
|
85
91
|
require_relative 'paper_trail_diff/association_activity_edge'
|
|
86
92
|
require_relative 'paper_trail_diff/association_activity_selection'
|
|
87
93
|
require_relative 'paper_trail_diff/scoped_root_selection'
|
|
94
|
+
require_relative 'paper_trail_diff/deleted_root_filter'
|
|
88
95
|
require_relative 'paper_trail_diff/scoped_analysis_builder'
|
|
89
96
|
require_relative 'paper_trail_diff/batched_root_analyzer'
|
|
90
97
|
require_relative 'paper_trail_diff/version_range'
|
|
91
98
|
require_relative 'paper_trail_diff/version_sequence_diagnostics'
|
|
92
99
|
require_relative 'paper_trail_diff/time_range'
|
|
100
|
+
require_relative 'paper_trail_diff/transaction_cut'
|
|
93
101
|
require_relative 'paper_trail_diff/time_version_range'
|
|
94
102
|
require_relative 'paper_trail_diff/timeline_range'
|
|
95
103
|
require_relative 'paper_trail_diff/timeline_builder'
|
|
@@ -99,8 +107,8 @@ require_relative 'paper_trail_diff/activity_child_candidate_loader'
|
|
|
99
107
|
require_relative 'paper_trail_diff/activity_target_candidate_loader'
|
|
100
108
|
require_relative 'paper_trail_diff/activity_version_collector'
|
|
101
109
|
require_relative 'paper_trail_diff/activity_snapshot_sequence'
|
|
110
|
+
require_relative 'paper_trail_diff/activity_tree_filter'
|
|
102
111
|
require_relative 'paper_trail_diff/activity_history'
|
|
103
|
-
require_relative 'paper_trail_diff/time_activity_timeline_builder'
|
|
104
112
|
require_relative 'paper_trail_diff/activity_timeline_builder'
|
|
105
113
|
require_relative 'paper_trail_diff/paper_trail_adapter'
|
|
106
114
|
|
|
@@ -118,10 +126,10 @@ require_relative 'paper_trail_diff/paper_trail_adapter'
|
|
|
118
126
|
# type comparison_input = Hash[String | Symbol, untyped]
|
|
119
127
|
# type comparison_results = Hash[identity, Diff]
|
|
120
128
|
# type report_options = {
|
|
121
|
-
# within: untyped, activity: bool, version_scope: untyped,
|
|
122
|
-
# close_on: Symbol?, snapshots: bool, group: Symbol?
|
|
129
|
+
# within: untyped, activity: bool, version_scope: untyped, view: ActivityView
|
|
123
130
|
# }
|
|
124
131
|
# type ignore_option = Array[String | Symbol] | Hash[String | Symbol, untyped]
|
|
132
|
+
# type association_option = String | Symbol | Array[untyped] | Hash[untyped, untyped]
|
|
125
133
|
# type reference_key = :type | :id | "type" | "id"
|
|
126
134
|
# type traversal_context = :change | :included_state
|
|
127
135
|
# type traversal_state = :before | :after
|
|
@@ -142,43 +150,45 @@ module PaperTrailDiff # rubocop:disable Metrics/ModuleLength
|
|
|
142
150
|
|
|
143
151
|
class << self # rubocop:disable Metrics/ClassLength
|
|
144
152
|
# Compares net state between explicit PaperTrail-version or current-record endpoints.
|
|
145
|
-
#: (untyped, untyped, ?associations:
|
|
146
|
-
def compare(
|
|
153
|
+
#: (untyped, untyped, ?associations: association_option, ?ignore: ignore_option, ?reload_live_endpoints: bool?, ?allow_approximate: bool) -> Diff
|
|
154
|
+
def compare( # rubocop:disable Metrics/ParameterLists
|
|
147
155
|
from_version,
|
|
148
156
|
to_version,
|
|
149
157
|
associations: [],
|
|
150
158
|
ignore: DEFAULT_IGNORED_ATTRIBUTES,
|
|
151
|
-
reload_live_endpoints:
|
|
159
|
+
reload_live_endpoints: nil,
|
|
160
|
+
allow_approximate: false
|
|
152
161
|
)
|
|
153
162
|
PaperTrailAdapter.new(
|
|
154
163
|
associations: associations,
|
|
155
164
|
ignore: ignore,
|
|
156
|
-
reload_live_endpoints: reload_live_endpoints
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
to_version
|
|
160
|
-
)
|
|
165
|
+
reload_live_endpoints: Deprecation.reload_live_endpoints!(reload_live_endpoints),
|
|
166
|
+
allow_approximate: allow_approximate
|
|
167
|
+
).compare(from_version, to_version)
|
|
161
168
|
end
|
|
162
169
|
|
|
163
170
|
# Compares many independent endpoint pairs while batching current-record loading.
|
|
164
|
-
#: (Array[comparison_input], ?associations:
|
|
165
|
-
def compare_many(
|
|
171
|
+
#: (Array[comparison_input], ?associations: association_option, ?ignore: ignore_option, ?reload_live_endpoints: bool?, ?allow_expensive: bool?, ?allow_approximate: bool) -> comparison_results
|
|
172
|
+
def compare_many( # rubocop:disable Metrics/ParameterLists
|
|
166
173
|
comparisons,
|
|
167
174
|
associations: [],
|
|
168
175
|
ignore: DEFAULT_IGNORED_ATTRIBUTES,
|
|
169
|
-
reload_live_endpoints:
|
|
170
|
-
allow_expensive:
|
|
176
|
+
reload_live_endpoints: nil,
|
|
177
|
+
allow_expensive: nil,
|
|
178
|
+
allow_approximate: false
|
|
171
179
|
)
|
|
180
|
+
Deprecation.allow_expensive!(allow_expensive)
|
|
172
181
|
PaperTrailAdapter.new(
|
|
173
182
|
associations: associations,
|
|
174
183
|
ignore: ignore,
|
|
175
|
-
reload_live_endpoints: reload_live_endpoints,
|
|
184
|
+
reload_live_endpoints: Deprecation.reload_live_endpoints!(reload_live_endpoints),
|
|
185
|
+
allow_approximate: allow_approximate
|
|
176
186
|
).compare_many(comparisons)
|
|
177
187
|
end
|
|
178
188
|
|
|
179
189
|
# Compares every adjacent reconstructed state in an inclusive version range,
|
|
180
190
|
# by default the record's whole history through its current state.
|
|
181
|
-
#: (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations:
|
|
191
|
+
#: (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: association_option, ?ignore: ignore_option, ?version_scope: untyped, ?close_on: Symbol?, ?allow_approximate: bool) -> Array[Step]
|
|
182
192
|
def timeline( # rubocop:disable Metrics/ParameterLists
|
|
183
193
|
record,
|
|
184
194
|
from: nil,
|
|
@@ -187,23 +197,23 @@ module PaperTrailDiff # rubocop:disable Metrics/ModuleLength
|
|
|
187
197
|
associations: [],
|
|
188
198
|
ignore: DEFAULT_IGNORED_ATTRIBUTES,
|
|
189
199
|
version_scope: nil,
|
|
190
|
-
close_on: nil
|
|
200
|
+
close_on: nil,
|
|
201
|
+
allow_approximate: false
|
|
191
202
|
)
|
|
192
|
-
|
|
203
|
+
Deprecation.close_on!(close_on, within)
|
|
204
|
+
PaperTrailAdapter.new(
|
|
205
|
+
associations: associations, ignore: ignore, allow_approximate: allow_approximate
|
|
206
|
+
).timeline(
|
|
193
207
|
record,
|
|
194
208
|
from: from,
|
|
195
209
|
to: to,
|
|
196
210
|
within: within,
|
|
197
|
-
version_scope: version_scope
|
|
198
|
-
close_on: close_on
|
|
211
|
+
version_scope: version_scope
|
|
199
212
|
)
|
|
200
213
|
end
|
|
201
214
|
|
|
202
215
|
# Compares adjacent root and selected-descendant activity boundaries.
|
|
203
|
-
|
|
204
|
-
# record: an explicit `to:` record, the default range, or a window with no
|
|
205
|
-
# later version.
|
|
206
|
-
#: (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]
|
|
216
|
+
#: (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: association_option, ?ignore: ignore_option, ?reload_live_endpoints: bool?, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?, ?recorded_changes: bool, ?allow_approximate: bool) -> Array[ActivityStep]
|
|
207
217
|
def activity_timeline( # rubocop:disable Metrics/ParameterLists
|
|
208
218
|
record,
|
|
209
219
|
from: nil,
|
|
@@ -211,30 +221,32 @@ module PaperTrailDiff # rubocop:disable Metrics/ModuleLength
|
|
|
211
221
|
within: nil,
|
|
212
222
|
associations: [],
|
|
213
223
|
ignore: DEFAULT_IGNORED_ATTRIBUTES,
|
|
214
|
-
reload_live_endpoints:
|
|
224
|
+
reload_live_endpoints: nil,
|
|
215
225
|
version_scope: nil,
|
|
216
226
|
close_on: nil,
|
|
217
227
|
snapshots: false,
|
|
218
|
-
group: nil
|
|
228
|
+
group: nil,
|
|
229
|
+
recorded_changes: false,
|
|
230
|
+
allow_approximate: false
|
|
219
231
|
)
|
|
232
|
+
Deprecation.close_on!(close_on, within)
|
|
220
233
|
PaperTrailAdapter.new(
|
|
221
|
-
associations: associations,
|
|
222
|
-
|
|
223
|
-
|
|
234
|
+
associations: associations, ignore: ignore,
|
|
235
|
+
reload_live_endpoints: Deprecation.reload_live_endpoints!(reload_live_endpoints),
|
|
236
|
+
recorded_changes: recorded_changes, allow_approximate: allow_approximate
|
|
224
237
|
).activity_timeline(
|
|
225
238
|
record,
|
|
226
239
|
from: from,
|
|
227
240
|
to: to,
|
|
228
241
|
within: within,
|
|
229
242
|
version_scope: version_scope,
|
|
230
|
-
close_on: close_on,
|
|
231
243
|
group: group,
|
|
232
244
|
snapshots: snapshots
|
|
233
245
|
)
|
|
234
246
|
end
|
|
235
247
|
|
|
236
248
|
# Builds an endpoint diff and root-checkpoint timeline while normalizing each version once.
|
|
237
|
-
#: (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations:
|
|
249
|
+
#: (untyped, ?from: untyped, ?to: untyped, ?within: untyped, ?associations: association_option, ?ignore: ignore_option, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?, ?recorded_changes: bool, ?allow_approximate: bool) -> Analysis
|
|
238
250
|
def analyze( # rubocop:disable Metrics/ParameterLists
|
|
239
251
|
record,
|
|
240
252
|
from: nil,
|
|
@@ -246,16 +258,21 @@ module PaperTrailDiff # rubocop:disable Metrics/ModuleLength
|
|
|
246
258
|
version_scope: nil,
|
|
247
259
|
close_on: nil,
|
|
248
260
|
snapshots: false,
|
|
249
|
-
group: nil
|
|
261
|
+
group: nil,
|
|
262
|
+
recorded_changes: false,
|
|
263
|
+
allow_approximate: false
|
|
250
264
|
)
|
|
251
|
-
|
|
265
|
+
Deprecation.close_on!(close_on, within)
|
|
266
|
+
PaperTrailAdapter.new(
|
|
267
|
+
associations: associations, ignore: ignore, recorded_changes: recorded_changes,
|
|
268
|
+
allow_approximate: allow_approximate
|
|
269
|
+
).analyze(
|
|
252
270
|
record,
|
|
253
271
|
from: from,
|
|
254
272
|
to: to,
|
|
255
273
|
within: within,
|
|
256
274
|
activity: activity,
|
|
257
275
|
version_scope: version_scope,
|
|
258
|
-
close_on: close_on,
|
|
259
276
|
snapshots: snapshots,
|
|
260
277
|
group: group
|
|
261
278
|
)
|
|
@@ -266,7 +283,7 @@ module PaperTrailDiff # rubocop:disable Metrics/ModuleLength
|
|
|
266
283
|
# returns a frozen Hash keyed by identity. Roots with nothing to report in
|
|
267
284
|
# the window return an empty `Analysis`. To let a relation choose the
|
|
268
285
|
# records, use `analyze_scope`.
|
|
269
|
-
#: (Array[untyped], ?within: untyped, ?associations:
|
|
286
|
+
#: (Array[untyped], ?within: untyped, ?associations: association_option, ?ignore: ignore_option, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool, ?group: Symbol?, ?allow_expensive: bool?, ?recorded_changes: bool, ?allow_approximate: bool) -> Hash[identity, Analysis]
|
|
270
287
|
def analyze_many( # rubocop:disable Metrics/ParameterLists
|
|
271
288
|
records,
|
|
272
289
|
within: nil,
|
|
@@ -277,12 +294,17 @@ module PaperTrailDiff # rubocop:disable Metrics/ModuleLength
|
|
|
277
294
|
close_on: nil,
|
|
278
295
|
snapshots: false,
|
|
279
296
|
group: nil,
|
|
280
|
-
allow_expensive:
|
|
297
|
+
allow_expensive: nil,
|
|
298
|
+
recorded_changes: false,
|
|
299
|
+
allow_approximate: false
|
|
281
300
|
)
|
|
301
|
+
Deprecation.close_on!(close_on, within)
|
|
302
|
+
Deprecation.allow_expensive!(allow_expensive)
|
|
282
303
|
PaperTrailAdapter.new(
|
|
283
|
-
associations: associations, ignore: ignore,
|
|
304
|
+
associations: associations, ignore: ignore,
|
|
305
|
+
recorded_changes: recorded_changes, allow_approximate: allow_approximate
|
|
284
306
|
).analyze_many(records, within: within, activity: activity, version_scope: version_scope,
|
|
285
|
-
|
|
307
|
+
snapshots: snapshots, group: group)
|
|
286
308
|
end
|
|
287
309
|
|
|
288
310
|
# Analyzes every root the relation reaches whose history moved inside the
|
|
@@ -311,7 +333,7 @@ module PaperTrailDiff # rubocop:disable Metrics/ModuleLength
|
|
|
311
333
|
# Note also that a relation selects on current state, not on state during
|
|
312
334
|
# the window: `where(status: 'published')` means published *now*, which is a
|
|
313
335
|
# different set from what was published while the window was open.
|
|
314
|
-
#: (untyped, limit: Integer?, ?within: untyped, ?associations:
|
|
336
|
+
#: (untyped, limit: Integer?, ?within: untyped, ?associations: association_option, ?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?, ?recorded_changes: bool, ?allow_approximate: bool) -> ScopedAnalysis
|
|
315
337
|
def analyze_scope( # rubocop:disable Metrics/ParameterLists
|
|
316
338
|
scope,
|
|
317
339
|
limit:,
|
|
@@ -326,13 +348,18 @@ module PaperTrailDiff # rubocop:disable Metrics/ModuleLength
|
|
|
326
348
|
historical_filter: nil,
|
|
327
349
|
max_filter_versions: HistoricalFilter::DEFAULT_VERSION_LIMIT,
|
|
328
350
|
include_deleted: false,
|
|
329
|
-
allow_expensive:
|
|
351
|
+
allow_expensive: nil,
|
|
352
|
+
recorded_changes: false,
|
|
353
|
+
allow_approximate: false
|
|
330
354
|
)
|
|
355
|
+
Deprecation.close_on!(close_on, within)
|
|
356
|
+
Deprecation.allow_expensive!(allow_expensive)
|
|
331
357
|
adapter = PaperTrailAdapter.new(associations: associations, ignore: ignore,
|
|
332
|
-
|
|
358
|
+
recorded_changes: recorded_changes,
|
|
359
|
+
allow_approximate: allow_approximate)
|
|
333
360
|
adapter.analyze_scope(
|
|
334
361
|
scope, limit: limit, within: within, activity: activity,
|
|
335
|
-
version_scope: version_scope,
|
|
362
|
+
version_scope: version_scope, snapshots: snapshots, group: group,
|
|
336
363
|
historical_filter: historical_filter, max_filter_versions: max_filter_versions,
|
|
337
364
|
include_deleted: include_deleted
|
|
338
365
|
)
|
|
@@ -342,21 +369,35 @@ module PaperTrailDiff # rubocop:disable Metrics/ModuleLength
|
|
|
342
369
|
# for every root that changed or whose requested associations did.
|
|
343
370
|
# Historical windows remain explicit on analyze_scope; this entry point is
|
|
344
371
|
# deliberately named for a report through now rather than an as-of query.
|
|
345
|
-
#: (untyped, since: untyped, limit: Integer, ?associations:
|
|
372
|
+
#: (untyped, since: untyped, limit: Integer, ?associations: association_option, ?ignore: ignore_option, ?snapshots: bool, ?group: Symbol?, ?include_deleted: bool, ?historical_filter: untyped, ?max_filter_versions: Integer, ?allow_expensive: bool?, ?recorded_changes: bool, ?allow_approximate: bool) -> ScopedAnalysis
|
|
346
373
|
def report( # rubocop:disable Metrics/ParameterLists
|
|
347
374
|
scope, since:, limit:, associations: [], ignore: DEFAULT_IGNORED_ATTRIBUTES,
|
|
348
375
|
snapshots: false, group: nil, include_deleted: false, historical_filter: nil,
|
|
349
|
-
max_filter_versions: HistoricalFilter::DEFAULT_VERSION_LIMIT, allow_expensive:
|
|
376
|
+
max_filter_versions: HistoricalFilter::DEFAULT_VERSION_LIMIT, allow_expensive: nil,
|
|
377
|
+
recorded_changes: false, allow_approximate: false
|
|
350
378
|
)
|
|
379
|
+
# A range here would become the start of another range.
|
|
380
|
+
if since.nil? || since.is_a?(Range)
|
|
381
|
+
raise InvalidTimeRangeError, '`since` must be a time, date, or string; for a range, ' \
|
|
382
|
+
'pass it as `within:` to analyze_scope'
|
|
383
|
+
end
|
|
384
|
+
|
|
351
385
|
analyze_scope(
|
|
352
386
|
scope,
|
|
353
387
|
within: since..Time.now.utc, limit: limit, associations: associations,
|
|
354
|
-
ignore: ignore, activity: true,
|
|
388
|
+
ignore: ignore, activity: true, snapshots: snapshots, group: group,
|
|
355
389
|
include_deleted: include_deleted, historical_filter: historical_filter,
|
|
356
|
-
max_filter_versions: max_filter_versions, allow_expensive: allow_expensive
|
|
390
|
+
max_filter_versions: max_filter_versions, allow_expensive: allow_expensive,
|
|
391
|
+
recorded_changes: recorded_changes, allow_approximate: allow_approximate
|
|
357
392
|
)
|
|
358
393
|
end
|
|
359
394
|
|
|
395
|
+
# Warns about deprecated options; see Deprecation.
|
|
396
|
+
#: () -> untyped
|
|
397
|
+
def deprecator
|
|
398
|
+
Deprecation.deprecator
|
|
399
|
+
end
|
|
400
|
+
|
|
360
401
|
# Returns the association macros this release can normalize.
|
|
361
402
|
#: () -> Array[Symbol]
|
|
362
403
|
def supported_association_macros
|
|
@@ -369,10 +410,13 @@ module PaperTrailDiff # rubocop:disable Metrics/ModuleLength
|
|
|
369
410
|
AssociationDiscovery.new(model_or_record, max_depth: max_depth).call
|
|
370
411
|
end
|
|
371
412
|
|
|
372
|
-
# Reports known reconstruction hazards without mutating application state
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
413
|
+
# Reports known reconstruction hazards without mutating application state,
|
|
414
|
+
# for the endpoints `compare` takes: two versions, or a version and the
|
|
415
|
+
# current record. Every reason `compare` would refuse them is an error.
|
|
416
|
+
#: (untyped, untyped, ?associations: association_option, ?allow_approximate: bool) -> DiagnosticReport
|
|
417
|
+
def diagnose(from, to, associations: [], allow_approximate: false)
|
|
418
|
+
HistoryDiagnostics.new(from, to, associations: associations,
|
|
419
|
+
allow_approximate: allow_approximate).call
|
|
376
420
|
end
|
|
377
421
|
|
|
378
422
|
# Looks inside an attribute the database stores whole, such as a JSON or
|
|
@@ -404,9 +448,7 @@ module PaperTrailDiff # rubocop:disable Metrics/ModuleLength
|
|
|
404
448
|
|
|
405
449
|
# Reads what PaperTrail recorded with one version: its `object_changes`,
|
|
406
450
|
# 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)] }
|
|
451
|
+
# Activity methods read it for every step source with `recorded_changes: true`.
|
|
410
452
|
#
|
|
411
453
|
# A step's diff compares the states before and after an event, so it also
|
|
412
454
|
# includes anything saved without a version in between; this does not.
|
|
@@ -24,26 +24,49 @@ module PaperTrailDiff
|
|
|
24
24
|
# report one save as one step rather than as its parts.
|
|
25
25
|
attr_reader transaction_id: untyped
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
def self.from_version: (untyped) -> ActivityBoundary
|
|
27
|
+
@recorded_changes: untyped
|
|
29
28
|
|
|
30
|
-
#
|
|
31
|
-
|
|
29
|
+
# Stands for recorded changes nobody asked to read, which differs from a
|
|
30
|
+
# version that recorded nothing (nil).
|
|
31
|
+
NOT_READ: untyped
|
|
32
|
+
|
|
33
|
+
NOT_READ_MESSAGE: ::String
|
|
34
|
+
|
|
35
|
+
# `recorded:` names the attributes to leave out of the version's recorded
|
|
36
|
+
# changes, or is nil when they were not asked for. Reading them decodes
|
|
37
|
+
# `object_changes`, which a caller who does not want them should not pay.
|
|
38
|
+
# : (untyped, ?recorded: Array[String]?) -> ActivityBoundary
|
|
39
|
+
def self.from_version: (untyped, ?recorded: Array[String]?) -> ActivityBoundary
|
|
40
|
+
|
|
41
|
+
# The current record has no version, so it recorded nothing.
|
|
42
|
+
# : (untyped, captured_at: untyped, ?recorded: Array[String]?) -> ActivityBoundary
|
|
43
|
+
def self.current: (untyped, captured_at: untyped, ?recorded: Array[String]?) -> ActivityBoundary
|
|
32
44
|
|
|
33
45
|
# The state a `destroy` version leaves behind. A version records the state
|
|
34
46
|
# before its own event, so the boundary built from a destroy version still
|
|
35
47
|
# holds the record; this one is the absence that follows it.
|
|
36
|
-
# : (untyped) -> ActivityBoundary
|
|
37
|
-
def self.destroyed: (untyped) -> ActivityBoundary
|
|
38
|
-
|
|
39
|
-
#
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
#
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
48
|
+
# : (untyped, ?recorded: Array[String]?) -> ActivityBoundary
|
|
49
|
+
def self.destroyed: (untyped, ?recorded: Array[String]?) -> ActivityBoundary
|
|
50
|
+
|
|
51
|
+
# : (untyped, Array[String]?) -> untyped
|
|
52
|
+
private def self.recorded_changes: (untyped, Array[String]?) -> untyped
|
|
53
|
+
|
|
54
|
+
# `recorded_changes:` takes what `PaperTrailDiff.recorded_changes` returns
|
|
55
|
+
# for the version; leave it out when they were not read.
|
|
56
|
+
# : (kind: Symbol, version_id: untyped, item_type: untyped, item_id: untyped, recorded_at: untyped, ?event: untyped, ?whodunnit: untyped, ?transaction_id: untyped, ?recorded_changes: untyped) -> void
|
|
57
|
+
def initialize: (kind: Symbol, version_id: untyped, item_type: untyped, item_id: untyped, recorded_at: untyped, ?event: untyped, ?whodunnit: untyped, ?transaction_id: untyped, ?recorded_changes: untyped) -> void
|
|
58
|
+
|
|
59
|
+
# What PaperTrail recorded with this boundary's version, as
|
|
60
|
+
# `PaperTrailDiff.recorded_changes` returns it: nil when nothing was
|
|
61
|
+
# recorded, and always nil for the current record, which has no version.
|
|
62
|
+
# Raises unless the timeline was built with `recorded_changes: true`, so a
|
|
63
|
+
# forgotten option cannot pass for a version that recorded nothing.
|
|
64
|
+
# : () -> Hash[String, ValueChange]?
|
|
65
|
+
def recorded_changes: () -> Hash[String, ValueChange]?
|
|
66
|
+
|
|
67
|
+
# Whether this boundary's recorded changes were read.
|
|
68
|
+
# : () -> bool
|
|
69
|
+
def recorded_changes?: () -> bool
|
|
47
70
|
|
|
48
71
|
# : () -> bool
|
|
49
72
|
def version?: () -> bool
|
|
@@ -75,14 +98,20 @@ module PaperTrailDiff
|
|
|
75
98
|
|
|
76
99
|
attr_reader to_snapshot: RecordSnapshot?
|
|
77
100
|
|
|
101
|
+
# The events whose changes this step reports, in the order they happened.
|
|
102
|
+
# That is `source_boundary` alone, unless the step is a transaction group,
|
|
103
|
+
# which holds every event in it: an attribution that names only the first
|
|
104
|
+
# would credit the rest of the save to whoever opened it.
|
|
105
|
+
attr_reader sources: Array[ActivityBoundary]
|
|
106
|
+
|
|
78
107
|
# Compares two reconstructed states and keeps them only when asked, which is
|
|
79
108
|
# every caller's shape: the diff always comes from the pair, the pair itself
|
|
80
109
|
# is retained on request.
|
|
81
|
-
# : (from_boundary: ActivityBoundary, to_boundary: ActivityBoundary, from_snapshot: RecordSnapshot?, to_snapshot: RecordSnapshot?, retain: bool) -> ActivityStep
|
|
82
|
-
def self.between: (from_boundary: ActivityBoundary, to_boundary: ActivityBoundary, from_snapshot: RecordSnapshot?, to_snapshot: RecordSnapshot?, retain: bool) -> ActivityStep
|
|
110
|
+
# : (from_boundary: ActivityBoundary, to_boundary: ActivityBoundary, from_snapshot: RecordSnapshot?, to_snapshot: RecordSnapshot?, retain: bool, ?sources: Array[ActivityBoundary]?) -> ActivityStep
|
|
111
|
+
def self.between: (from_boundary: ActivityBoundary, to_boundary: ActivityBoundary, from_snapshot: RecordSnapshot?, to_snapshot: RecordSnapshot?, retain: bool, ?sources: Array[ActivityBoundary]?) -> ActivityStep
|
|
83
112
|
|
|
84
|
-
# : (from_boundary: ActivityBoundary, to_boundary: ActivityBoundary, diff: Diff, ?from_snapshot: RecordSnapshot?, ?to_snapshot: RecordSnapshot?) -> void
|
|
85
|
-
def initialize: (from_boundary: ActivityBoundary, to_boundary: ActivityBoundary, diff: Diff, ?from_snapshot: RecordSnapshot?, ?to_snapshot: RecordSnapshot?) -> void
|
|
113
|
+
# : (from_boundary: ActivityBoundary, to_boundary: ActivityBoundary, diff: Diff, ?from_snapshot: RecordSnapshot?, ?to_snapshot: RecordSnapshot?, ?sources: Array[ActivityBoundary]?) -> void
|
|
114
|
+
def initialize: (from_boundary: ActivityBoundary, to_boundary: ActivityBoundary, diff: Diff, ?from_snapshot: RecordSnapshot?, ?to_snapshot: RecordSnapshot?, ?sources: Array[ActivityBoundary]?) -> void
|
|
86
115
|
|
|
87
116
|
# : () -> bool
|
|
88
117
|
def empty?: () -> bool
|
|
@@ -92,6 +121,12 @@ module PaperTrailDiff
|
|
|
92
121
|
# : () -> ActivityBoundary
|
|
93
122
|
def source_boundary: () -> ActivityBoundary
|
|
94
123
|
|
|
124
|
+
# The diff as flat rows, each attributed to `source_boundary`.
|
|
125
|
+
# : () -> Array[ChangeRow]
|
|
126
|
+
def change_rows: () -> Array[ChangeRow]
|
|
127
|
+
|
|
128
|
+
# `metadata: true` also lists `sources`, since who made a change is
|
|
129
|
+
# metadata as much as the source boundary's own author is.
|
|
95
130
|
# : (?metadata: bool, ?snapshots: bool) -> Hash[Symbol, untyped]
|
|
96
131
|
def to_h: (?metadata: bool, ?snapshots: bool) -> Hash[Symbol, untyped]
|
|
97
132
|
end
|