Class: PaperTrailDiff::PaperTrailAdapter
- Inherits:
-
Object
- Object
- PaperTrailDiff::PaperTrailAdapter
- Defined in:
- lib/paper_trail_diff/paper_trail_adapter.rb,
sig/generated/paper_trail_diff/paper_trail_adapter.rbs
Overview
PaperTrail/ActiveRecord boundary that produces plain record snapshots. It is deliberately the widest class here: it fronts every public operation and is the only place allowed to know about both PaperTrail and the pure engine. Reconstruction logic lives in the collaborators it wires together.
Constant Summary collapse
- CLOSE_ON_CURRENT =
The only thing a wall-clock window can close on besides a later version.
:current
Instance Method Summary collapse
-
#activity_builder(record, from:, to:, within:, version_scope: nil, live_endpoint: nil, snapshots: false) ⇒ ActivityTimelineBuilder
: (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?live_endpoint: untyped, ?snapshots: bool) -> ActivityTimelineBuilder.
-
#activity_timeline(record, from:, to:, within:, version_scope: nil, close_on: nil, snapshots: false) ⇒ Array[ActivityStep]
: (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool) -> Array.
-
#analyze(record, from:, to:, within:, activity: false, version_scope: nil, close_on: nil, snapshots: false) ⇒ Analysis
: (untyped, from: untyped, to: untyped, within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool) -> Analysis.
-
#analyze_activity(record, from:, to:, within:, version_scope:, live_endpoint:, snapshots: false) ⇒ Analysis
: (untyped, from: untyped, to: untyped, within: untyped, version_scope: untyped, live_endpoint: untyped, ?snapshots: bool) -> Analysis.
-
#analyze_many(records, within:, activity: false, version_scope: nil, close_on: nil) ⇒ Hash[identity, Analysis]
Analyzes many roots over one shared range, preparing their history once.
-
#batched_root_analyzer(activity) ⇒ BatchedRootAnalyzer
: (bool) -> BatchedRootAnalyzer.
-
#build_activity_snapshotter ⇒ ActivitySnapshotProvider
: () -> ActivitySnapshotProvider.
-
#build_historical_store ⇒ HistoricalSnapshotStore
: () -> HistoricalSnapshotStore.
-
#build_snapshotters ⇒ void
: () -> void.
-
#close_on_current?(close_on, within) ⇒ Boolean
close_on:names what ends a wall-clock window, so it is meaningless for a range whose endpoints the caller already gave explicitly. -
#compare(from_endpoint, to_endpoint) ⇒ Diff
: (untyped, untyped) -> Diff.
-
#compare_many(comparisons) ⇒ comparison_results
Compares independent pairs and returns immutable results keyed by item identity.
-
#historical_snapshot(version) ⇒ RecordSnapshot?
: (untyped) -> RecordSnapshot?.
-
#initialize(associations:, ignore:, reload_live_endpoints: true) ⇒ PaperTrailAdapter
constructor
: (associations: Array[String | Symbol], ignore: ignore_option, ?reload_live_endpoints: bool) -> void.
-
#live_endpoint_for(record, close_on, within) ⇒ Object
A destroyed root has no current state to close on, and its own destroy version already terminates the history.
-
#live_snapshot(record) ⇒ RecordSnapshot
: (untyped) -> RecordSnapshot.
-
#normalize_live_snapshot(current) ⇒ RecordSnapshot
: (untyped) -> RecordSnapshot.
-
#notify_ambiguous_association_boundary(from_endpoint, to_endpoint) ⇒ void
Association membership is resolved by timestamp, so endpoints sharing one cannot be told apart and any association change between them is invisible.
-
#reject_live_habtm_activity!(model_class) ⇒ void
: (untyped) -> void.
-
#snapshot_at(root_endpoint, context_endpoint) ⇒ RecordSnapshot?
: (untyped, untyped) -> RecordSnapshot?.
-
#snapshot_for_endpoint(endpoint) ⇒ RecordSnapshot?
: (untyped) -> RecordSnapshot?.
-
#timeline(record, from:, to:, within:, version_scope: nil, close_on: nil) ⇒ Array[Step]
: (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?close_on: Symbol?) -> Array.
-
#uncached_historical_snapshot(version) ⇒ RecordSnapshot?
: (untyped) -> RecordSnapshot?.
Constructor Details
#initialize(associations:, ignore:, reload_live_endpoints: true) ⇒ PaperTrailAdapter
: (associations: Array[String | Symbol], ignore: ignore_option, ?reload_live_endpoints: bool) -> void
14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 14 def initialize(associations:, ignore:, reload_live_endpoints: true) @association_tree = AssociationTree.build(associations) @ignore_policy = IgnorePolicy.build(ignore, association_paths: @association_tree.paths) @traversal = AssociationTraversal.new(@association_tree) @traversal_preparer = TraversalPreparer.new(tree: @association_tree, traversal: @traversal) @live_endpoints = LiveEndpointProvider.new( tree: @association_tree, traversal: @traversal, reload: reload_live_endpoints ) @instrumentation_payload = @live_endpoints.comparison_payload @snapshot_pool = SnapshotPool.new @normalizer = SnapshotNormalizer.new( tree: @association_tree, ignore_policy: @ignore_policy, traversal: @traversal, pool: @snapshot_pool ) build_snapshotters end |
Instance Method Details
#activity_builder(record, from:, to:, within:, version_scope: nil, live_endpoint: nil, snapshots: false) ⇒ ActivityTimelineBuilder
: (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?live_endpoint: untyped, ?snapshots: bool) -> ActivityTimelineBuilder
238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 238 def activity_builder(record, from:, to:, within:, version_scope: nil, live_endpoint: nil, snapshots: false) # rubocop:disable Metrics/ParameterLists, Layout/LineLength ActivityTimelineBuilder.new( record, range: TimelineRange.new( record, from: from, to: to, within: within, version_scope: version_scope, live_endpoint: live_endpoint ), tree: @association_tree, snapshotter: @activity_snapshotter, snapshots: snapshots ) end |
#activity_timeline(record, from:, to:, within:, version_scope: nil, close_on: nil, snapshots: false) ⇒ Array[ActivityStep]
: (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool) -> Array
72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 72 def activity_timeline(record, from:, to:, within:, version_scope: nil, close_on: nil, snapshots: false) # rubocop:disable Metrics/ParameterLists, Layout/LineLength payload = @instrumentation_payload.merge(model_type: record.class.base_class.name.to_s) Instrumentation.instrument('activity_timeline', payload) do @traversal_preparer.call(record.class, historical: true) live = live_endpoint_for(record, close_on, within) reject_live_habtm_activity!(record.class) if Endpoint.record?(to) || live steps = activity_builder( record, from: from, to: to, within: within, version_scope: version_scope, live_endpoint: live, snapshots: snapshots ).build payload[:step_count] = steps.length steps end end |
#analyze(record, from:, to:, within:, activity: false, version_scope: nil, close_on: nil, snapshots: false) ⇒ Analysis
: (untyped, from: untyped, to: untyped, within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?, ?snapshots: bool) -> Analysis
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 88 def analyze(record, from:, to:, within:, activity: false, version_scope: nil, close_on: nil, snapshots: false) # rubocop:disable Metrics/ParameterLists, Layout/LineLength @traversal_preparer.call(record.class, historical: true) live = live_endpoint_for(record, close_on, within) if activity return analyze_activity( record, from: from, to: to, within: within, version_scope: version_scope, live_endpoint: live, snapshots: snapshots ) end TimelineBuilder.new( record, from: from, to: to, within: within, version_scope: version_scope, live_endpoint: live, snapshotter: @timeline_snapshotter ).analyze end |
#analyze_activity(record, from:, to:, within:, version_scope:, live_endpoint:, snapshots: false) ⇒ Analysis
: (untyped, from: untyped, to: untyped, within: untyped, version_scope: untyped, live_endpoint: untyped, ?snapshots: bool) -> Analysis
137 138 139 140 141 142 143 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 137 def analyze_activity(record, from:, to:, within:, version_scope:, live_endpoint:, snapshots: false) # rubocop:disable Metrics/ParameterLists, Layout/LineLength reject_live_habtm_activity!(record.class) if live_endpoint activity_builder( record, from: from, to: to, within: within, version_scope: version_scope, live_endpoint: live_endpoint, snapshots: snapshots ).analyze end |
#analyze_many(records, within:, activity: false, version_scope: nil, close_on: nil) ⇒ Hash[identity, Analysis]
Analyzes many roots over one shared range, preparing their history once. : (Array, within: untyped, ?activity: bool, ?version_scope: untyped, ?close_on: Symbol?) -> Hash[identity, Analysis]
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 106 def analyze_many(records, within:, activity: false, version_scope: nil, close_on: nil) count = records.is_a?(Array) ? records.length : 0 payload = @instrumentation_payload.merge(comparison_count: count) Instrumentation.instrument('analyze_many', payload) do AnalysisBatch.new( records, time_range: within.nil? ? nil : TimeRange.new(within), version_scope: version_scope, close_on_current: close_on_current?(close_on, within), live_loader: @live_endpoints.method(:call), history_preparer: @historical_store.method(:prepare_batch), analyzer: batched_root_analyzer(activity) ).call end end |
#batched_root_analyzer(activity) ⇒ BatchedRootAnalyzer
: (bool) -> BatchedRootAnalyzer
199 200 201 202 203 204 205 206 207 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 199 def batched_root_analyzer(activity) BatchedRootAnalyzer.new( tree: @association_tree, timeline_snapshotter: @timeline_snapshotter, activity_snapshotter: @activity_snapshotter, preparer: @traversal_preparer.method(:call), activity: activity ) end |
#build_activity_snapshotter ⇒ ActivitySnapshotProvider
: () -> ActivitySnapshotProvider
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 220 def build_activity_snapshotter refresher = BranchSnapshotRefresher.new( tree: @association_tree, ignore_policy: @ignore_policy, traversal: @traversal, pool: @snapshot_pool, normalizer: @normalizer, full_snapshotter: method(:snapshot_at), partial_snapshotter: @historical_store.method(:custom), association_reader: @historical_store.method(:association_reader), record_transition: @historical_store.method(:record_transition) ) ActivitySnapshotProvider.new( snapshotter: method(:snapshot_at), refresher: refresher, preparer: @historical_store.method(:prepare) ) end |
#build_historical_store ⇒ HistoricalSnapshotStore
: () -> HistoricalSnapshotStore
210 211 212 213 214 215 216 217 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 210 def build_historical_store HistoricalSnapshotStore.new( tree: @association_tree, traversal: @traversal, normalizer: @normalizer, preparer: @traversal_preparer.method(:call) ) end |
#build_snapshotters ⇒ void
This method returns an undefined value.
: () -> void
190 191 192 193 194 195 196 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 190 def build_snapshotters @historical_store = build_historical_store @timeline_snapshotter = TimelineSnapshotProvider.new( @historical_store, live_snapshotter: method(:live_snapshot) ) @activity_snapshotter = build_activity_snapshotter end |
#close_on_current?(close_on, within) ⇒ Boolean
close_on: names what ends a wall-clock window, so it is meaningless for a
range whose endpoints the caller already gave explicitly.
: (Symbol?, untyped) -> bool
169 170 171 172 173 174 175 176 177 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 169 def close_on_current?(close_on, within) return false if close_on.nil? unless close_on == CLOSE_ON_CURRENT raise ConfigurationError, "close_on: must be #{CLOSE_ON_CURRENT.inspect} or nil" end raise ConfigurationError, 'close_on: requires `within`' if within.nil? true end |
#compare(from_endpoint, to_endpoint) ⇒ Diff
: (untyped, untyped) -> Diff
32 33 34 35 36 37 38 39 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 32 def compare(from_endpoint, to_endpoint) payload = @instrumentation_payload.merge(comparison_count: 1) Instrumentation.instrument('compare', payload) do Endpoint.validate_pair!(from_endpoint, to_endpoint) notify_ambiguous_association_boundary(from_endpoint, to_endpoint) Engine.compare(snapshot_for_endpoint(from_endpoint), snapshot_for_endpoint(to_endpoint)) end end |
#compare_many(comparisons) ⇒ comparison_results
Compares independent pairs and returns immutable results keyed by item identity. : (Array) -> comparison_results
43 44 45 46 47 48 49 50 51 52 53 54 55 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 43 def compare_many(comparisons) count = comparisons.is_a?(Array) ? comparisons.length : 0 payload = @instrumentation_payload.merge(comparison_count: count) Instrumentation.instrument('compare_many', payload) do ComparisonBatch.new( comparisons, live_loader: @live_endpoints.method(:call), preparer: @traversal_preparer.method(:call), history_preparer: @historical_store.method(:prepare_batch), historical_snapshotter: method(:historical_snapshot), live_normalizer: method(:normalize_live_snapshot) ).call end end |
#historical_snapshot(version) ⇒ RecordSnapshot?
: (untyped) -> RecordSnapshot?
266 267 268 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 266 def historical_snapshot(version) snapshot_at(version, version) end |
#live_endpoint_for(record, close_on, within) ⇒ Object
A destroyed root has no current state to close on, and its own destroy version already terminates the history. : (untyped, Symbol?, untyped) -> untyped
182 183 184 185 186 187 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 182 def live_endpoint_for(record, close_on, within) return unless close_on_current?(close_on, within) return unless Endpoint.record?(record) && !record.destroyed? record end |
#live_snapshot(record) ⇒ RecordSnapshot
: (untyped) -> RecordSnapshot
283 284 285 286 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 283 def live_snapshot(record) current = @live_endpoints.call([record]).fetch(Endpoint.identity(record)) normalize_live_snapshot(current) end |
#normalize_live_snapshot(current) ⇒ RecordSnapshot
: (untyped) -> RecordSnapshot
289 290 291 292 293 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 289 def normalize_live_snapshot(current) @traversal_preparer.call(current.class, historical: false) @normalizer.call(current, reifier: LiveAssociationReader.new) || raise(InvalidEndpointError, 'current record endpoint could not be normalized') end |
#notify_ambiguous_association_boundary(from_endpoint, to_endpoint) ⇒ void
This method returns an undefined value.
Association membership is resolved by timestamp, so endpoints sharing one cannot be told apart and any association change between them is invisible. The result may still be correct -- nothing associated may have changed -- and the gem cannot tell which, since not seeing the change is the symptom. So it reports the condition and leaves the judgement to the application. : (untyped, untyped) -> void
151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 151 def notify_ambiguous_association_boundary(from_endpoint, to_endpoint) return if @association_tree.empty? return unless Endpoint.version?(from_endpoint) && Endpoint.version?(to_endpoint) return unless from_endpoint.created_at == to_endpoint.created_at Instrumentation.notify( 'ambiguous_association_boundary', @instrumentation_payload.merge( item_type: from_endpoint.item_type.to_s, version_ids: [from_endpoint.id, to_endpoint.id].freeze, recorded_at: from_endpoint.created_at ) ) end |
#reject_live_habtm_activity!(model_class) ⇒ void
This method returns an undefined value.
: (untyped) -> void
252 253 254 255 256 257 258 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 252 def reject_live_habtm_activity!(model_class) paths = @traversal.habtm_paths(model_class) return if paths.empty? = "live-ended activity cannot reconstruct HABTM event boundaries: #{paths.join(', ')}" raise UnsupportedLiveActivityError, end |
#snapshot_at(root_endpoint, context_endpoint) ⇒ RecordSnapshot?
: (untyped, untyped) -> RecordSnapshot?
276 277 278 279 280 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 276 def snapshot_at(root_endpoint, context_endpoint) return live_snapshot(root_endpoint) if Endpoint.record?(context_endpoint) @historical_store.call(root_endpoint, context_endpoint) end |
#snapshot_for_endpoint(endpoint) ⇒ RecordSnapshot?
: (untyped) -> RecordSnapshot?
261 262 263 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 261 def snapshot_for_endpoint(endpoint) Endpoint.version?(endpoint) ? historical_snapshot(endpoint) : live_snapshot(endpoint) end |
#timeline(record, from:, to:, within:, version_scope: nil, close_on: nil) ⇒ Array[Step]
: (untyped, from: untyped, to: untyped, within: untyped, ?version_scope: untyped, ?close_on: Symbol?) -> Array
58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 58 def timeline(record, from:, to:, within:, version_scope: nil, close_on: nil) # rubocop:disable Metrics/ParameterLists @traversal_preparer.call(record.class, historical: true) TimelineBuilder.new( record, from: from, to: to, within: within, version_scope: version_scope, live_endpoint: live_endpoint_for(record, close_on, within), snapshotter: @timeline_snapshotter ).build end |
#uncached_historical_snapshot(version) ⇒ RecordSnapshot?
: (untyped) -> RecordSnapshot?
271 272 273 |
# File 'lib/paper_trail_diff/paper_trail_adapter.rb', line 271 def uncached_historical_snapshot(version) @historical_store.uncached(version, version) end |