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
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Generated from lib/paper_trail_diff/approximate_paths.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module PaperTrailDiff
|
|
4
|
+
# The requested paths prepared history cannot read exactly, each with why.
|
|
5
|
+
# PT-AT reads such a path instead, one record at a time, when the caller
|
|
6
|
+
# passes `allow_approximate: true`.
|
|
7
|
+
module ApproximatePaths
|
|
8
|
+
# : (AssociationTraversal, untyped) -> Array[[String, String]]
|
|
9
|
+
def self.call: (AssociationTraversal, untyped) -> Array[[ String, String ]]
|
|
10
|
+
|
|
11
|
+
# A has_many through reads its parts' conditions only, at any depth. A
|
|
12
|
+
# has_one through over a ranked part is refused whole, so its parts, too,
|
|
13
|
+
# are checked for conditions alone.
|
|
14
|
+
# : (untyped, ranked: bool) -> String?
|
|
15
|
+
def self.fallback_reason: (untyped, ranked: bool) -> String?
|
|
16
|
+
|
|
17
|
+
# A scope prepared history can read as conditions on stored attributes
|
|
18
|
+
# is batched like any other path. A polymorphic target's class, and so
|
|
19
|
+
# its scope, is known only per record.
|
|
20
|
+
# : (untyped, bool) -> String?
|
|
21
|
+
def self.reason_for: (untyped, bool) -> String?
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -49,6 +49,11 @@ module PaperTrailDiff
|
|
|
49
49
|
# : (untyped, root: bool) -> Array[untyped]
|
|
50
50
|
def supported_reflections: (untyped, root: bool) -> Array[untyped]
|
|
51
51
|
|
|
52
|
+
# Every request that reads history refuses a path whose target, or a
|
|
53
|
+
# model its has_many :through goes through, has no versions.
|
|
54
|
+
# : (untyped) -> bool
|
|
55
|
+
def unversioned?: (untyped) -> bool
|
|
56
|
+
|
|
52
57
|
# : (untyped) -> untyped
|
|
53
58
|
def reflection_target: (untyped) -> untyped
|
|
54
59
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Generated from lib/paper_trail_diff/association_key.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module PaperTrailDiff
|
|
4
|
+
# PT-AT records which record a child pointed at by that record's id, and
|
|
5
|
+
# history matches children to owners and targets by id. An association
|
|
6
|
+
# keyed on another column (`primary_key:`) would read other records'
|
|
7
|
+
# history, so it is refused.
|
|
8
|
+
module AssociationKey
|
|
9
|
+
# Why the association, or a part of a through, cannot be read by id.
|
|
10
|
+
# : (untyped) -> String?
|
|
11
|
+
def self.unsupported_reason: (untyped) -> String?
|
|
12
|
+
|
|
13
|
+
# The reflection, or the first part of a through, keyed on a column other
|
|
14
|
+
# than its owner's or target's primary key.
|
|
15
|
+
# : (untyped) -> untyped
|
|
16
|
+
def self.keyed_part: (untyped) -> untyped
|
|
17
|
+
|
|
18
|
+
# A belongs_to names a key of its target; any other macro, one of its
|
|
19
|
+
# owner. A polymorphic target has no single primary key to compare with.
|
|
20
|
+
# : (untyped) -> bool
|
|
21
|
+
def self.other_key?: (untyped) -> bool
|
|
22
|
+
|
|
23
|
+
# Column names are strings once the key is, so the one mutation left,
|
|
24
|
+
# `eql?` for `==`, answers the same.
|
|
25
|
+
# mutant:disable
|
|
26
|
+
# : (untyped, untyped) -> bool
|
|
27
|
+
def self.same_column?: (untyped, untyped) -> bool
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -39,6 +39,10 @@ module PaperTrailDiff
|
|
|
39
39
|
# : (untyped, untyped, root: bool) -> String?
|
|
40
40
|
def self.unsupported_reason: (untyped, untyped, root: bool) -> String?
|
|
41
41
|
|
|
42
|
+
# History matches records by primary key, within one set of version tables.
|
|
43
|
+
# : (untyped) -> String?
|
|
44
|
+
def self.storage_reason: (untyped) -> String?
|
|
45
|
+
|
|
42
46
|
# PT-AT records a HABTM's membership only when its owner saves a version,
|
|
43
47
|
# in rows keyed by transaction and association name, not by owner. A root
|
|
44
48
|
# checkpoint so holds the root's membership and no one else's: another
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Generated from lib/paper_trail_diff/batch_preload.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module PaperTrailDiff
|
|
4
|
+
# Which associations Active Record's preloader loads correctly for many
|
|
5
|
+
# owners at once.
|
|
6
|
+
module BatchPreload
|
|
7
|
+
# The preloader applies a limit or offset to all owners' records at once,
|
|
8
|
+
# not to each owner's, so such an association is loaded per record. A
|
|
9
|
+
# has_one is no exception. Through a part with a limit, an offset or a
|
|
10
|
+
# has_one cut, the preloader keeps what the part alone would load, while
|
|
11
|
+
# the through's own query keeps the part's conditions only.
|
|
12
|
+
#
|
|
13
|
+
# The declared scope is read, not a record's association scope: Active
|
|
14
|
+
# Record adds `limit(1)` to every has_one's, which would load every
|
|
15
|
+
# has_one per record.
|
|
16
|
+
# : (untyped) -> bool
|
|
17
|
+
def self.safe?: (untyped) -> bool
|
|
18
|
+
|
|
19
|
+
# : (untyped) -> untyped
|
|
20
|
+
def self.declared_relation: (untyped) -> untyped
|
|
21
|
+
|
|
22
|
+
# : (untyped) -> bool
|
|
23
|
+
def self.cut_part?: (untyped) -> bool
|
|
24
|
+
|
|
25
|
+
# Every part a through passes, at any depth.
|
|
26
|
+
# : (untyped) -> Array[untyped]
|
|
27
|
+
def self.through_parts: (untyped) -> Array[untyped]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -4,11 +4,12 @@ module PaperTrailDiff
|
|
|
4
4
|
# Builds one root's Analysis inside a batch, from versions the batch already
|
|
5
5
|
# selected and history it already prepared.
|
|
6
6
|
class BatchedRootAnalyzer
|
|
7
|
-
# : (tree: AssociationTree, timeline_snapshotter: untyped, activity_snapshotter: untyped, preparer: untyped, activity: bool,
|
|
8
|
-
def initialize: (tree: AssociationTree, timeline_snapshotter: untyped, activity_snapshotter: untyped, preparer: untyped, activity: bool,
|
|
7
|
+
# : (tree: AssociationTree, timeline_snapshotter: untyped, activity_snapshotter: untyped, preparer: untyped, activity: bool, view: ActivityView, ?within: untyped) -> void
|
|
8
|
+
def initialize: (tree: AssociationTree, timeline_snapshotter: untyped, activity_snapshotter: untyped, preparer: untyped, activity: bool, view: ActivityView, ?within: untyped) -> void
|
|
9
9
|
|
|
10
|
-
#
|
|
11
|
-
|
|
10
|
+
# `window` is the root's window as the batch moved it to transaction edges.
|
|
11
|
+
# : (untyped, RootVersionPlan, ?TimeRange?) -> Analysis
|
|
12
|
+
def call: (untyped, RootVersionPlan, ?TimeRange?) -> Analysis
|
|
12
13
|
|
|
13
14
|
private
|
|
14
15
|
|
|
@@ -16,19 +17,17 @@ module PaperTrailDiff
|
|
|
16
17
|
|
|
17
18
|
@activity_snapshotter: untyped
|
|
18
19
|
|
|
19
|
-
@group: Symbol?
|
|
20
|
-
|
|
21
20
|
@preparer: untyped
|
|
22
21
|
|
|
23
|
-
@snapshots: bool
|
|
24
|
-
|
|
25
22
|
@timeline_snapshotter: untyped
|
|
26
23
|
|
|
27
24
|
@tree: AssociationTree
|
|
28
25
|
|
|
26
|
+
@view: ActivityView
|
|
27
|
+
|
|
29
28
|
@within: untyped
|
|
30
29
|
|
|
31
|
-
# : (untyped, RootVersionPlan) -> Analysis
|
|
32
|
-
def activity_analysis: (untyped, RootVersionPlan) -> Analysis
|
|
30
|
+
# : (untyped, RootVersionPlan, TimeRange?) -> Analysis
|
|
31
|
+
def activity_analysis: (untyped, RootVersionPlan, TimeRange?) -> Analysis
|
|
33
32
|
end
|
|
34
33
|
end
|
|
@@ -5,8 +5,8 @@ module PaperTrailDiff
|
|
|
5
5
|
# queries. Only the range forms that mean the same thing for every root are
|
|
6
6
|
# supported: a shared wall-clock window, or each root's own whole history.
|
|
7
7
|
class BatchedRootVersions
|
|
8
|
-
# : (Array[untyped], time_range: TimeRange?, version_scope: untyped, live_endpoints: Hash[Array[String], untyped]?, context_required: bool) -> void
|
|
9
|
-
def initialize: (Array[untyped], time_range: TimeRange?, version_scope: untyped, live_endpoints: Hash[Array[String], untyped]?, context_required: bool) -> void
|
|
8
|
+
# : (Array[untyped], time_range: TimeRange?, version_scope: untyped, live_endpoints: Hash[Array[String], untyped]?, context_required: bool, windows: Hash[Array[String], TimeRange]) -> void
|
|
9
|
+
def initialize: (Array[untyped], time_range: TimeRange?, version_scope: untyped, live_endpoints: Hash[Array[String], untyped]?, context_required: bool, windows: Hash[Array[String], TimeRange]) -> void
|
|
10
10
|
|
|
11
11
|
# Returns a plan per record identity.
|
|
12
12
|
# : () -> Hash[Array[String], RootVersionPlan]
|
|
@@ -24,14 +24,24 @@ module PaperTrailDiff
|
|
|
24
24
|
|
|
25
25
|
@version_scope: untyped
|
|
26
26
|
|
|
27
|
+
@windows: Hash[Array[String], TimeRange]
|
|
28
|
+
|
|
27
29
|
# : (untyped, Array[untyped], Hash[Array[String], RootVersionPlan]) -> void
|
|
28
30
|
def select_model: (untyped, Array[untyped], Hash[Array[String], RootVersionPlan]) -> void
|
|
29
31
|
|
|
32
|
+
# Moved windows only start and end earlier, so the versions read for the
|
|
33
|
+
# widest of them hold each root's own, and the first left past a moved end.
|
|
34
|
+
# : (Array[String], String, Hash[String, Array[untyped]], Hash[String, untyped]) -> [Array[untyped], untyped]
|
|
35
|
+
def own_window: (Array[String], String, Hash[String, Array[untyped]], Hash[String, untyped]) -> [ Array[untyped], untyped ]
|
|
36
|
+
|
|
30
37
|
# : (untyped, Array[untyped]) -> [Hash[String, Array[untyped]], Set[untyped]?, Hash[String, untyped]]
|
|
31
38
|
def model_versions: (untyped, Array[untyped]) -> [ Hash[String, Array[untyped]], Set[untyped]?, Hash[String, untyped] ]
|
|
32
39
|
|
|
33
|
-
# : (Array[untyped]
|
|
34
|
-
def
|
|
40
|
+
# : (Array[untyped]) -> TimeRange?
|
|
41
|
+
def widest_range: (Array[untyped]) -> TimeRange?
|
|
42
|
+
|
|
43
|
+
# : (Array[untyped], untyped, Set[untyped]?, untyped) -> RootVersionPlan
|
|
44
|
+
def versions_for: (Array[untyped], untyped, Set[untyped]?, untyped) -> RootVersionPlan
|
|
35
45
|
|
|
36
46
|
# The batch already loaded every root, so closing on current state reuses
|
|
37
47
|
# that rather than reading each record again.
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Generated from lib/paper_trail_diff/change_row.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module PaperTrailDiff
|
|
4
|
+
# One change from an activity step, flat enough for a history table or an
|
|
5
|
+
# export: a field that changed, or a record that appeared or disappeared,
|
|
6
|
+
# with the event the step attributes it to.
|
|
7
|
+
class ChangeRow
|
|
8
|
+
attr_reader change: Symbol
|
|
9
|
+
|
|
10
|
+
attr_reader association_path: Array[String]
|
|
11
|
+
|
|
12
|
+
attr_reader association_kind: Symbol?
|
|
13
|
+
|
|
14
|
+
# The record the row is about, or nil for the root itself.
|
|
15
|
+
attr_reader record: RecordReference?
|
|
16
|
+
|
|
17
|
+
# The field the row is about, or nil for a row about a whole record.
|
|
18
|
+
attr_reader attribute: String?
|
|
19
|
+
|
|
20
|
+
attr_reader from: untyped
|
|
21
|
+
|
|
22
|
+
attr_reader to: untyped
|
|
23
|
+
|
|
24
|
+
# The step's source boundary: for a transaction group, its first event.
|
|
25
|
+
attr_reader source: ActivityBoundary
|
|
26
|
+
|
|
27
|
+
# : (change: Symbol, association_path: Array[String], association_kind: Symbol?, record: RecordReference?, attribute: String?, from: untyped, to: untyped, source: ActivityBoundary) -> void
|
|
28
|
+
def initialize: (change: Symbol, association_path: Array[String], association_kind: Symbol?, record: RecordReference?, attribute: String?, from: untyped, to: untyped, source: ActivityBoundary) -> void
|
|
29
|
+
|
|
30
|
+
# The source's identifying fields sit beside the change rather than nested,
|
|
31
|
+
# so the hash is one row of a table as it stands.
|
|
32
|
+
# : () -> Hash[Symbol, untyped]
|
|
33
|
+
def to_h: () -> Hash[Symbol, untyped]
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
# : () -> Hash[Symbol, untyped]
|
|
38
|
+
def source_fields: () -> Hash[Symbol, untyped]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Flattens one step's diff into change rows, in the diff's walking order.
|
|
42
|
+
#
|
|
43
|
+
# A row is about one record. An edited field is `:changed`. A record that
|
|
44
|
+
# appeared or disappeared is one `:added` or `:removed` row, followed by a
|
|
45
|
+
# row for each of its fields and for each record it holds, so a table can
|
|
46
|
+
# list a new comment's body without a second walker. That covers a record
|
|
47
|
+
# joining or leaving a collection, a to-one target set, cleared or replaced
|
|
48
|
+
# (the old target removed, then the new one added), and the root's own
|
|
49
|
+
# creation or deletion.
|
|
50
|
+
#
|
|
51
|
+
# Two kinds of entry add nothing a row would show. One that only groups
|
|
52
|
+
# others (an edited record, an association, a relationship) is left out,
|
|
53
|
+
# because the rows under it name the same record. A blank field of an added
|
|
54
|
+
# or removed record is left out too, because the record's own row already
|
|
55
|
+
# says it appeared or disappeared.
|
|
56
|
+
class ChangeRows
|
|
57
|
+
RECORD_ENTRIES: untyped
|
|
58
|
+
|
|
59
|
+
# : (Diff, source: ActivityBoundary) -> Array[ChangeRow]
|
|
60
|
+
def self.call: (Diff, source: ActivityBoundary) -> Array[ChangeRow]
|
|
61
|
+
|
|
62
|
+
# : (ActivityBoundary) -> void
|
|
63
|
+
def initialize: (ActivityBoundary) -> void
|
|
64
|
+
|
|
65
|
+
# : (Diff) -> Array[ChangeRow]
|
|
66
|
+
def call: (Diff) -> Array[ChangeRow]
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
@source: ActivityBoundary
|
|
71
|
+
|
|
72
|
+
# : (TraversalEntry) -> ChangeRow?
|
|
73
|
+
def row_for: (TraversalEntry) -> ChangeRow?
|
|
74
|
+
|
|
75
|
+
# : (TraversalEntry) -> ChangeRow
|
|
76
|
+
def changed_row: (TraversalEntry) -> ChangeRow
|
|
77
|
+
|
|
78
|
+
# An added record's state is the after side and a removed one's the before
|
|
79
|
+
# side, so a field's value goes on the side that has it.
|
|
80
|
+
# : (TraversalEntry, String?) -> ChangeRow
|
|
81
|
+
def presence_row: (TraversalEntry, String?) -> ChangeRow
|
|
82
|
+
|
|
83
|
+
# : (TraversalEntry, Symbol, String?, untyped, untyped) -> ChangeRow
|
|
84
|
+
def row: (TraversalEntry, Symbol, String?, untyped, untyped) -> ChangeRow
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module PaperTrailDiff
|
|
4
4
|
# Compares collection snapshots with a low-allocation path for stable membership.
|
|
5
|
+
# Snapshots hold their records in type and ID order, so results follow it
|
|
6
|
+
# without sorting here.
|
|
5
7
|
class CollectionComparator
|
|
6
8
|
# : (AssociationSnapshot, AssociationSnapshot, record_comparer: untyped) -> void
|
|
7
9
|
def initialize: (AssociationSnapshot, AssociationSnapshot, record_comparer: untyped) -> void
|
|
@@ -47,22 +49,6 @@ module PaperTrailDiff
|
|
|
47
49
|
# : (Array[RecordSnapshot]) -> Hash[identity, RecordSnapshot]
|
|
48
50
|
def index_records: (Array[RecordSnapshot]) -> Hash[identity, RecordSnapshot]
|
|
49
51
|
|
|
50
|
-
# : (Array[identity]) -> Array[identity]
|
|
51
|
-
def sorted_identities: (Array[identity]) -> Array[identity]
|
|
52
|
-
|
|
53
|
-
# Identities sort by type first so that mixed id types stay comparable, then
|
|
54
|
-
# naturally within one type. Ordering by the printed form instead would put
|
|
55
|
-
# id 10 before id 2, which is deterministic but reads as unsorted wherever a
|
|
56
|
-
# result is rendered.
|
|
57
|
-
# : (identity) -> Array[untyped]
|
|
58
|
-
def sortable_identity: (identity) -> Array[untyped]
|
|
59
|
-
|
|
60
|
-
# Only the order of the group numbers matters, so moving the lowest one
|
|
61
|
-
# down or the highest one up changes nothing; the spec checks the order.
|
|
62
|
-
# mutant:disable
|
|
63
|
-
# : (untyped) -> Array[untyped]
|
|
64
|
-
def sortable_id: (untyped) -> Array[untyped]
|
|
65
|
-
|
|
66
52
|
# : (identity) -> void
|
|
67
53
|
def raise_duplicate_identity!: (identity) -> void
|
|
68
54
|
end
|
|
@@ -5,18 +5,27 @@ module PaperTrailDiff
|
|
|
5
5
|
class AssociationTree
|
|
6
6
|
attr_reader children: Hash[String, AssociationTree]
|
|
7
7
|
|
|
8
|
+
# Takes what `includes` takes: a name, a dotted path, a hash of names to
|
|
9
|
+
# what to include below them, or an array of any of these.
|
|
8
10
|
# : (untyped) -> AssociationTree
|
|
9
11
|
def self.build: (untyped) -> AssociationTree
|
|
10
12
|
|
|
13
|
+
# : (untyped, String?) -> Array[String]
|
|
14
|
+
private def self.paths: (untyped, String?) -> Array[String]
|
|
15
|
+
|
|
16
|
+
# `{ comments: :replies }` names comments as well as their replies.
|
|
17
|
+
# : (untyped, untyped, String?) -> Array[String]
|
|
18
|
+
private def self.nested_paths: (untyped, untyped, String?) -> Array[String]
|
|
19
|
+
|
|
20
|
+
# : (String?, String | Symbol) -> String
|
|
21
|
+
private def self.joined: (String?, String | Symbol) -> String
|
|
22
|
+
|
|
11
23
|
# : (Hash[String, untyped], String) -> void
|
|
12
24
|
private def self.add_path: (Hash[String, untyped], String) -> void
|
|
13
25
|
|
|
14
26
|
# : (Hash[String, untyped]) -> AssociationTree
|
|
15
27
|
private def self.from_hash: (Hash[String, untyped]) -> AssociationTree
|
|
16
28
|
|
|
17
|
-
# : (untyped) -> bool
|
|
18
|
-
private def self.valid_values?: (untyped) -> bool
|
|
19
|
-
|
|
20
29
|
# : (Hash[String, AssociationTree]) -> void
|
|
21
30
|
def initialize: (Hash[String, AssociationTree]) -> void
|
|
22
31
|
|
|
@@ -63,5 +72,12 @@ module PaperTrailDiff
|
|
|
63
72
|
|
|
64
73
|
# : (String) -> Array[String]
|
|
65
74
|
def attributes_for: (String) -> Array[String]
|
|
75
|
+
|
|
76
|
+
# The names to leave out of what a version recorded. A root version is
|
|
77
|
+
# known to be the root's, so its own path's names apply; any other version
|
|
78
|
+
# does not say which path reached it, so only the names ignored everywhere
|
|
79
|
+
# do.
|
|
80
|
+
# : (root: bool) -> Array[String]
|
|
81
|
+
def recorded_attributes: (root: bool) -> Array[String]
|
|
66
82
|
end
|
|
67
83
|
end
|
|
@@ -32,11 +32,11 @@ module PaperTrailDiff
|
|
|
32
32
|
|
|
33
33
|
# A root destroyed before the window had no state inside it to read, so
|
|
34
34
|
# only the others take the version after the window as context.
|
|
35
|
-
# : (Array[untyped], Array[untyped]) -> Hash[Array[String], RootVersionPlan]
|
|
36
|
-
def plans: (Array[untyped], Array[untyped]) -> Hash[Array[String], RootVersionPlan]
|
|
35
|
+
# : (Array[untyped], Array[untyped], Hash[Array[String], TimeRange]) -> Hash[Array[String], RootVersionPlan]
|
|
36
|
+
def plans: (Array[untyped], Array[untyped], Hash[Array[String], TimeRange]) -> Hash[Array[String], RootVersionPlan]
|
|
37
37
|
|
|
38
|
-
# : (Array[untyped], bool) -> Hash[Array[String], RootVersionPlan]
|
|
39
|
-
def plans_for: (Array[untyped], bool) -> Hash[Array[String], RootVersionPlan]
|
|
38
|
+
# : (Array[untyped], bool, Hash[Array[String], TimeRange]) -> Hash[Array[String], RootVersionPlan]
|
|
39
|
+
def plans_for: (Array[untyped], bool, Hash[Array[String], TimeRange]) -> Hash[Array[String], RootVersionPlan]
|
|
40
40
|
|
|
41
41
|
# These records supply identity and model metadata only. Every reported
|
|
42
42
|
# value comes from the batched historical plan; no per-root reify or live
|
|
@@ -47,10 +47,10 @@ module PaperTrailDiff
|
|
|
47
47
|
# : () -> Array[untyped]
|
|
48
48
|
def destroy_versions: () -> Array[untyped]
|
|
49
49
|
|
|
50
|
-
# : (Array[untyped]) -> Hash[Symbol, untyped]
|
|
51
|
-
def preparation_range: (Array[untyped]) -> Hash[Symbol, untyped]
|
|
50
|
+
# : (Array[untyped], Hash[Array[String], TimeRange]) -> Hash[Symbol, untyped]
|
|
51
|
+
def preparation_range: (Array[untyped], Hash[Array[String], TimeRange]) -> Hash[Symbol, untyped]
|
|
52
52
|
|
|
53
|
-
# : (Array[untyped], Hash[Array[String], RootVersionPlan]) -> void
|
|
54
|
-
def prepare: (Array[untyped], Hash[Array[String], RootVersionPlan]) -> void
|
|
53
|
+
# : (Array[untyped], Hash[Array[String], RootVersionPlan], Hash[Array[String], TimeRange]) -> void
|
|
54
|
+
def prepare: (Array[untyped], Hash[Array[String], RootVersionPlan], Hash[Array[String], TimeRange]) -> void
|
|
55
55
|
end
|
|
56
56
|
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# Generated from lib/paper_trail_diff/deleted_root_filter.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module PaperTrailDiff
|
|
4
|
+
# Keeps the deleted roots the relation would have selected, by the state
|
|
5
|
+
# each held when it was destroyed. A relation's conditions read the live
|
|
6
|
+
# table, where a destroyed root has no row, so without this a report on one
|
|
7
|
+
# account's records would include every account's deletions.
|
|
8
|
+
class DeletedRootFilter
|
|
9
|
+
# : (untyped) -> void
|
|
10
|
+
def initialize: (untyped) -> void
|
|
11
|
+
|
|
12
|
+
# A root whose destroy PaperTrail did not record has no state to test
|
|
13
|
+
# and stays, as it has no state to report either. One whose destroy
|
|
14
|
+
# recorded no state is left out.
|
|
15
|
+
# : (Array[Array[String]]) -> Array[Array[String]]
|
|
16
|
+
def call: (Array[Array[String]]) -> Array[Array[String]]
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
@condition: ScopeCondition
|
|
21
|
+
|
|
22
|
+
@loader: DirectRecordStateLoader
|
|
23
|
+
|
|
24
|
+
@relation: untyped
|
|
25
|
+
|
|
26
|
+
# A relation delegates class methods to its model, so every mutation here,
|
|
27
|
+
# which drops `.model`, answers the same.
|
|
28
|
+
# mutant:disable
|
|
29
|
+
# : () -> untyped
|
|
30
|
+
def model: () -> untyped
|
|
31
|
+
|
|
32
|
+
# : (Hash[String, untyped]?) -> bool
|
|
33
|
+
def selected?: (Hash[String, untyped]?) -> bool
|
|
34
|
+
|
|
35
|
+
# The state each root held before its latest destroy.
|
|
36
|
+
# : (Array[String]) -> Hash[String, Hash[String, untyped]?]
|
|
37
|
+
def destroyed_states: (Array[String]) -> Hash[String, Hash[String, untyped]?]
|
|
38
|
+
|
|
39
|
+
# Read directly, with a column `skip:` left out of history as its default,
|
|
40
|
+
# so a report checks its ceiling before reifying anything. A version the
|
|
41
|
+
# direct reader declines, such as one naming a column since removed, is
|
|
42
|
+
# reified. Only the record's own attributes are read, so the association
|
|
43
|
+
# options, which only spare queries, cannot change an answer.
|
|
44
|
+
# mutant:disable
|
|
45
|
+
# : (untyped) -> Hash[String, untyped]?
|
|
46
|
+
def reified_attributes: (untyped) -> Hash[String, untyped]?
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Generated from lib/paper_trail_diff/deprecation.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module PaperTrailDiff
|
|
4
|
+
# Warns about options that do nothing any more and will be removed. In a
|
|
5
|
+
# Rails app, register the deprecator to configure it with the app's own:
|
|
6
|
+
#
|
|
7
|
+
# Rails.application.deprecators[:paper_trail_diff] = PaperTrailDiff.deprecator
|
|
8
|
+
module Deprecation
|
|
9
|
+
CLOSE_ON: ::String
|
|
10
|
+
|
|
11
|
+
ALLOW_EXPENSIVE: ::String
|
|
12
|
+
|
|
13
|
+
RELOAD_LIVE_ENDPOINTS: ::String
|
|
14
|
+
|
|
15
|
+
# Required when first used, as the library loads nothing it does not need.
|
|
16
|
+
# : () -> untyped
|
|
17
|
+
def self.deprecator: () -> untyped
|
|
18
|
+
|
|
19
|
+
# The one value close_on: took. It named what ends a wall-clock window,
|
|
20
|
+
# so it was meaningless without one, and until it is removed it is still
|
|
21
|
+
# checked as it was.
|
|
22
|
+
CLOSE_ON_CURRENT: ::Symbol
|
|
23
|
+
|
|
24
|
+
# : (Symbol?, untyped) -> void
|
|
25
|
+
def self.close_on!: (Symbol?, untyped) -> void
|
|
26
|
+
|
|
27
|
+
# : (untyped) -> void
|
|
28
|
+
def self.allow_expensive!: (untyped) -> void
|
|
29
|
+
|
|
30
|
+
# The value to use: the option still works until it is removed, and nil
|
|
31
|
+
# stands for its old default.
|
|
32
|
+
# : (untyped) -> untyped
|
|
33
|
+
def self.reload_live_endpoints!: (untyped) -> untyped
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -41,28 +41,49 @@ module PaperTrailDiff
|
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
# Performs read-only checks for known PaperTrail/PT-AT reconstruction hazards.
|
|
44
|
+
#
|
|
45
|
+
# The endpoints are a comparison's: two versions, or a version and the
|
|
46
|
+
# current record, in either order. Two current records read no history, so
|
|
47
|
+
# nothing is checked.
|
|
44
48
|
class HistoryDiagnostics
|
|
45
|
-
|
|
46
|
-
|
|
49
|
+
STOPPING_CODES: untyped
|
|
50
|
+
|
|
51
|
+
# : (untyped, untyped, associations: association_option, ?allow_approximate: bool) -> void
|
|
52
|
+
def initialize: (untyped, untyped, associations: association_option, ?allow_approximate: bool) -> void
|
|
47
53
|
|
|
48
54
|
# : () -> DiagnosticReport
|
|
49
55
|
def call: () -> DiagnosticReport
|
|
50
56
|
|
|
51
57
|
private
|
|
52
58
|
|
|
53
|
-
|
|
59
|
+
# Each refusal a comparison would raise is an error, so `ok?` is false
|
|
60
|
+
# exactly when one would. The checks after them read reflections that must
|
|
61
|
+
# resolve and PT-AT records that must be there.
|
|
62
|
+
# : (untyped) -> void
|
|
63
|
+
def inspect_associations: (untyped) -> void
|
|
64
|
+
|
|
65
|
+
@allow_approximate: bool
|
|
54
66
|
|
|
55
67
|
@issues: Array[DiagnosticIssue]
|
|
56
68
|
|
|
57
|
-
@
|
|
69
|
+
@preparer: TraversalPreparer
|
|
70
|
+
|
|
71
|
+
@traversal: AssociationTraversal
|
|
58
72
|
|
|
59
73
|
@tree: AssociationTree
|
|
60
74
|
|
|
61
|
-
|
|
62
|
-
|
|
75
|
+
@versions: Array[untyped]
|
|
76
|
+
|
|
77
|
+
# The endpoints that are versions, in the order given.
|
|
78
|
+
# : (untyped, untyped) -> Array[untyped]
|
|
79
|
+
def validated_versions: (untyped, untyped) -> Array[untyped]
|
|
63
80
|
|
|
64
81
|
# : (Array[Array[untyped]]) -> void
|
|
65
|
-
def
|
|
82
|
+
def inspect_polymorphic_targets: (Array[Array[untyped]]) -> void
|
|
83
|
+
|
|
84
|
+
# Accepted, but read the way `allow_approximate:` describes.
|
|
85
|
+
# : (untyped) -> void
|
|
86
|
+
def inspect_approximate_paths: (untyped) -> void
|
|
66
87
|
|
|
67
88
|
# : (untyped, Array[Array[untyped]]) -> void
|
|
68
89
|
def inspect_habtm: (untyped, Array[Array[untyped]]) -> void
|
|
@@ -72,8 +93,15 @@ module PaperTrailDiff
|
|
|
72
93
|
# : (untyped, Array[Array[untyped]]) -> void
|
|
73
94
|
def inspect_shared_transactions: (untyped, Array[Array[untyped]]) -> void
|
|
74
95
|
|
|
75
|
-
#
|
|
76
|
-
|
|
96
|
+
# With the current record as one endpoint there is one version, and the
|
|
97
|
+
# sequence runs on from it.
|
|
98
|
+
# : (untyped) -> void
|
|
99
|
+
def inspect_version_sequence: (untyped) -> void
|
|
100
|
+
|
|
101
|
+
# Only a path PT-AT reads can lose a change between tied versions, and a
|
|
102
|
+
# traversal that does not resolve reads nothing at all.
|
|
103
|
+
# : (untyped) -> bool
|
|
104
|
+
def fallback_selected?: (untyped) -> bool
|
|
77
105
|
|
|
78
106
|
# : (untyped) -> void
|
|
79
107
|
def inspect_checkpoint_timestamp: (untyped) -> void
|
|
@@ -81,30 +109,9 @@ module PaperTrailDiff
|
|
|
81
109
|
# : (Array[String]) -> void
|
|
82
110
|
def inspect_transaction_metadata: (Array[String]) -> void
|
|
83
111
|
|
|
84
|
-
# : (untyped, String) -> void
|
|
85
|
-
def inspect_versioned_model: (untyped, String) -> void
|
|
86
|
-
|
|
87
|
-
# : (untyped, String) -> void
|
|
88
|
-
def inspect_through_model: (untyped, String) -> void
|
|
89
|
-
|
|
90
|
-
# : (untyped) -> bool
|
|
91
|
-
def model_versioned?: (untyped) -> bool
|
|
92
|
-
|
|
93
112
|
# : (untyped) -> bool
|
|
94
113
|
def synchronized_timestamp_disabled?: (untyped) -> bool
|
|
95
114
|
|
|
96
|
-
# : () -> bool
|
|
97
|
-
def association_tracking_available?: () -> bool
|
|
98
|
-
|
|
99
|
-
# : (untyped) -> Array[String]
|
|
100
|
-
def version_identity: (untyped) -> Array[String]
|
|
101
|
-
|
|
102
|
-
# : (untyped) -> String
|
|
103
|
-
def version_type: (untyped) -> String
|
|
104
|
-
|
|
105
|
-
# : () -> String
|
|
106
|
-
def tracking_unavailable_message: () -> String
|
|
107
|
-
|
|
108
115
|
# : (Symbol, String, ?String?, ?untyped) -> void
|
|
109
116
|
def add_error: (Symbol, String, ?String?, ?untyped) -> void
|
|
110
117
|
|
|
@@ -21,6 +21,15 @@ module PaperTrailDiff
|
|
|
21
21
|
# : (untyped) -> void
|
|
22
22
|
def self?.validate!: (untyped) -> void
|
|
23
23
|
|
|
24
|
+
# The record a method reads the history of: a record, saved or destroyed,
|
|
25
|
+
# of a model PaperTrail versions.
|
|
26
|
+
# : (untyped) -> void
|
|
27
|
+
def self?.validate_root!: (untyped) -> void
|
|
28
|
+
|
|
29
|
+
# A relation is named rather than inspected, which would load it.
|
|
30
|
+
# : (untyped) -> String
|
|
31
|
+
def self?.described: (untyped) -> String
|
|
32
|
+
|
|
24
33
|
# : (untyped) -> bool
|
|
25
34
|
def self?.version?: (untyped) -> bool
|
|
26
35
|
|
|
@@ -33,8 +42,8 @@ module PaperTrailDiff
|
|
|
33
42
|
# : (untyped) -> untyped
|
|
34
43
|
def self?.model_class: (untyped) -> untyped
|
|
35
44
|
|
|
36
|
-
#
|
|
37
|
-
#
|
|
45
|
+
# Every PaperTrail version of the record, whatever scope its versions
|
|
46
|
+
# association has.
|
|
38
47
|
# : (untyped) -> untyped
|
|
39
48
|
def self?.versions: (untyped) -> untyped
|
|
40
49
|
|
|
@@ -54,7 +54,9 @@ module PaperTrailDiff
|
|
|
54
54
|
class FilterLimitExceededError < Error
|
|
55
55
|
end
|
|
56
56
|
|
|
57
|
-
#
|
|
57
|
+
# No longer raised: `allow_approximate: true` alone accepts the paths it
|
|
58
|
+
# refused. Kept until `allow_expensive:` is removed, so code that rescues it
|
|
59
|
+
# still loads.
|
|
58
60
|
class UnsupportedBatchPathError < ConfigurationError
|
|
59
61
|
end
|
|
60
62
|
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Generated from lib/paper_trail_diff/fallback_paths.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module PaperTrailDiff
|
|
4
|
+
# Whether a request may read a path with PT-AT's reifier rather than from
|
|
5
|
+
# loaded history. PT-AT places association changes by timestamp alone, so
|
|
6
|
+
# only such a path can miss a change between two versions that share one;
|
|
7
|
+
# loaded history orders those versions by id.
|
|
8
|
+
module FallbackPaths
|
|
9
|
+
# A path history reads only approximately counts when it is allowed: a
|
|
10
|
+
# request that does not allow it is refused before anything is read.
|
|
11
|
+
# : (AssociationTraversal, untyped, approximate: bool) -> bool
|
|
12
|
+
def self.any?: (AssociationTraversal, untyped, approximate: bool) -> bool
|
|
13
|
+
|
|
14
|
+
# Loaded history cannot index a composite foreign key, and a polymorphic
|
|
15
|
+
# belongs_to's target, with its default scope, is known only per record.
|
|
16
|
+
# : (untyped) -> bool
|
|
17
|
+
def self.per_record?: (untyped) -> bool
|
|
18
|
+
|
|
19
|
+
# : (untyped) -> bool
|
|
20
|
+
def self.composite_key?: (untyped) -> bool
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -16,8 +16,10 @@ module PaperTrailDiff
|
|
|
16
16
|
# : (untyped, predicate: untyped, time_range: TimeRange?, limit: Integer, max_versions: Integer) -> void
|
|
17
17
|
def initialize: (untyped, predicate: untyped, time_range: TimeRange?, limit: Integer, max_versions: Integer) -> void
|
|
18
18
|
|
|
19
|
-
#
|
|
20
|
-
|
|
19
|
+
# More matches than the limit raise, unless the caller does not count
|
|
20
|
+
# them: candidates it only lists, such as deleted roots.
|
|
21
|
+
# : (Array[String], ?counted: bool) -> Array[String]
|
|
22
|
+
def call: (Array[String], ?counted: bool) -> Array[String]
|
|
21
23
|
|
|
22
24
|
private
|
|
23
25
|
|
|
@@ -38,6 +40,14 @@ module PaperTrailDiff
|
|
|
38
40
|
# : (untyped) -> untyped
|
|
39
41
|
def validate_predicate: (untyped) -> untyped
|
|
40
42
|
|
|
43
|
+
# Expected and recorded values compare as the attribute casts them, as
|
|
44
|
+
# `where` reads its values: an enum's symbol names its stored value, and a
|
|
45
|
+
# `json` or `jsonb` version column, which PaperTrail does not cast, holds
|
|
46
|
+
# dates and times as strings. Casting also copies a mutable value, so the
|
|
47
|
+
# filter keeps its own.
|
|
48
|
+
# : (String, untyped) -> untyped
|
|
49
|
+
def cast: (String, untyped) -> untyped
|
|
50
|
+
|
|
41
51
|
# : (untyped) -> Integer
|
|
42
52
|
def validate_budget: (untyped) -> Integer
|
|
43
53
|
|