Class: PaperTrailDiff::TimeActivityTimelineBuilder
- Inherits:
-
Object
- Object
- PaperTrailDiff::TimeActivityTimelineBuilder
- Includes:
- ActivityGrouping
- Defined in:
- lib/paper_trail_diff/time_activity_timeline_builder.rb,
sig/generated/paper_trail_diff/time_activity_timeline_builder.rbs
Overview
Builds activity views for mutations selected by a wall-clock range.
Instance Method Summary collapse
-
#activity_steps(history, closing) ⇒ Array[ActivityStep]
: (ActivityHistory, ActivityStep?) -> Array.
-
#analyze ⇒ Analysis
: () -> Analysis.
-
#build ⇒ Array[ActivityStep]
: () -> Array.
- #build_history(root_versions, events) ⇒ ActivityHistory
-
#closing_step(history, event, plan, captured_at, snapshot) ⇒ ActivityStep?
A window either closes on the absence its final destruction leaves, or on current state when it reaches past everything recorded.
- #collect_events(root_versions, range_end: nil) ⇒ Array[ActivityEvent]
-
#current_snapshot(plan) ⇒ RecordSnapshot?
One live read serves both the closing activity step and the endpoint the analysis reports, which would otherwise reconstruct the graph twice.
-
#current_step(history, record, captured_at, snapshot) ⇒ ActivityStep?
The last boundary reached is where current state is compared from, which is the final event rather than the final root version once descendants have moved since it.
-
#destroyed_step(history, event) ⇒ ActivityStep
: (ActivityHistory, ActivityEvent) -> ActivityStep.
- #events_through(root_versions, captured_at) ⇒ Array[ActivityEvent]
-
#history_and_versions ⇒ [ ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped ]
: () -> [ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped].
-
#history_for(plan, captured_at) ⇒ [ ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped ]
: (RootVersionPlan, untyped) -> [ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped].
-
#initialize(record, range:, tree:, snapshotter:, snapshots: false, group: nil) ⇒ TimeActivityTimelineBuilder
constructor
: (untyped, range: TimelineRange, tree: AssociationTree, snapshotter: untyped, ?snapshots: bool, ?group: Symbol?) -> void.
-
#later_event?(selected, events) ⇒ Boolean
: (ActivityEvent, Array) -> bool.
-
#prepare_history(root_versions, end_at: nil) ⇒ void
: (Array, ?end_at: untyped) -> void.
-
#selected_events(events, plan) ⇒ Array[ActivityEvent]
Root versions inside the window that the plan does not report are context rather than mutations: the version appended to reveal the last selected change, and anything a filter excluded but the replay still walks.
-
#terminal_destroy?(event) ⇒ Boolean
: (ActivityEvent?) -> bool.
- #time_events?(selected, events, plan) ⇒ Boolean
Methods included from ActivityGrouping
Constructor Details
#initialize(record, range:, tree:, snapshotter:, snapshots: false, group: nil) ⇒ TimeActivityTimelineBuilder
: (untyped, range: TimelineRange, tree: AssociationTree, snapshotter: untyped, ?snapshots: bool, ?group: Symbol?) -> void
10 11 12 13 14 15 16 17 18 19 20 |
# File 'lib/paper_trail_diff/time_activity_timeline_builder.rb', line 10 def initialize(record, range:, tree:, snapshotter:, snapshots: false, group: nil) # rubocop:disable Metrics/ParameterLists @snapshots = snapshots @group = group # Merging a group compares its outer states, so the snapshots must survive # the build even when the caller did not ask to keep them. @retain = snapshots || grouping? @record = record @range = range @tree = tree @snapshotter = snapshotter end |
Instance Method Details
#activity_steps(history, closing) ⇒ Array[ActivityStep]
: (ActivityHistory, ActivityStep?) -> Array
106 107 108 109 |
# File 'lib/paper_trail_diff/time_activity_timeline_builder.rb', line 106 def activity_steps(history, closing) steps = closing ? history.steps + [closing] : history.steps group_steps(steps).freeze end |
#analyze ⇒ Analysis
: () -> Analysis
29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
# File 'lib/paper_trail_diff/time_activity_timeline_builder.rb', line 29 def analyze history, plan, closing, closing_snapshot, captured_at = history_and_versions final = closing_snapshot || history.last_snapshot Analysis.new( diff: Engine.compare(history.first_snapshot, final), timeline: ActivityRootSteps.call( plan, history.root_snapshots, closing_snapshot: closing_snapshot, captured_at: captured_at ), activity_timeline: activity_steps(history, closing), from_snapshot: history.first_snapshot, to_snapshot: final ) end |
#build ⇒ Array[ActivityStep]
: () -> Array
23 24 25 26 |
# File 'lib/paper_trail_diff/time_activity_timeline_builder.rb', line 23 def build history, _plan, closing, = history_and_versions activity_steps(history, closing) end |
#build_history(root_versions, events) ⇒ ActivityHistory
95 96 97 98 99 100 101 102 103 |
# File 'lib/paper_trail_diff/time_activity_timeline_builder.rb', line 95 def build_history(root_versions, events) ActivityHistoryBuilder.new( root_versions, events, @snapshotter, include_step: ->(event) { @range.include?(event.version) }, snapshots: @retain ).call end |
#closing_step(history, event, plan, captured_at, snapshot) ⇒ ActivityStep?
A window either closes on the absence its final destruction leaves, or on current state when it reaches past everything recorded. Both are boundaries no version can supply. : (ActivityHistory, ActivityEvent?, RootVersionPlan, untyped, RecordSnapshot?) -> ActivityStep?
129 130 131 132 133 134 135 |
# File 'lib/paper_trail_diff/time_activity_timeline_builder.rb', line 129 def closing_step(history, event, plan, captured_at, snapshot) return unless event return destroyed_step(history, event) if terminal_destroy?(event) return unless plan.closing_record current_step(history, plan.closing_record, captured_at, snapshot) end |
#collect_events(root_versions, range_end: nil) ⇒ Array[ActivityEvent]
181 182 183 184 185 186 187 188 189 190 |
# File 'lib/paper_trail_diff/time_activity_timeline_builder.rb', line 181 def collect_events(root_versions, range_end: nil) ActivityVersionCollector.new( @record, root_versions: root_versions, tree: @tree, traversal: AssociationTraversal.new(@tree), range_start: @range.begin_time, range_end: range_end || root_versions.last ).call end |
#current_snapshot(plan) ⇒ RecordSnapshot?
One live read serves both the closing activity step and the endpoint the analysis reports, which would otherwise reconstruct the graph twice. : (RootVersionPlan) -> RecordSnapshot?
81 82 83 84 85 86 |
# File 'lib/paper_trail_diff/time_activity_timeline_builder.rb', line 81 def current_snapshot(plan) record = plan.closing_record return unless record @snapshotter.call(record, record) end |
#current_step(history, record, captured_at, snapshot) ⇒ ActivityStep?
The last boundary reached is where current state is compared from, which is the final event rather than the final root version once descendants have moved since it. : (ActivityHistory, untyped, untyped, RecordSnapshot?) -> ActivityStep?
152 153 154 155 156 157 158 159 160 161 |
# File 'lib/paper_trail_diff/time_activity_timeline_builder.rb', line 152 def current_step(history, record, captured_at, snapshot) previous = history.steps.last&.to_boundary return unless previous ActivityStep.between( from_boundary: previous, to_boundary: ActivityBoundary.current(record, captured_at: captured_at), from_snapshot: history.last_snapshot, to_snapshot: snapshot, retain: @retain ) end |
#destroyed_step(history, event) ⇒ ActivityStep
: (ActivityHistory, ActivityEvent) -> ActivityStep
138 139 140 141 142 143 144 145 146 |
# File 'lib/paper_trail_diff/time_activity_timeline_builder.rb', line 138 def destroyed_step(history, event) version = event.version ActivityStep.between( from_boundary: ActivityBoundary.from_version(version), to_boundary: ActivityBoundary.destroyed(version), from_snapshot: history.root_snapshots[ActivityRootSteps.version_key(version)], to_snapshot: nil, retain: @retain ) end |
#events_through(root_versions, captured_at) ⇒ Array[ActivityEvent]
89 90 91 92 |
# File 'lib/paper_trail_diff/time_activity_timeline_builder.rb', line 89 def events_through(root_versions, captured_at) prepare_history(root_versions, end_at: captured_at) collect_events(root_versions, range_end: captured_at) end |
#history_and_versions ⇒ [ ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped ]
: () -> [ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped]
53 54 55 56 57 58 59 60 61 |
# File 'lib/paper_trail_diff/time_activity_timeline_builder.rb', line 53 def history_and_versions plan = @range.select_plan(context_required: !@tree.empty?) return [ActivityHistory.empty, plan, nil, nil, nil] if plan.reconstruction_versions.empty? # A window closing on current state runs to the instant it is captured, # not to the last root version, or descendants that moved after that # version would be missing from a report that claims to reach now. history_for(plan, plan.closing_record ? Time.now.utc : nil) end |
#history_for(plan, captured_at) ⇒ [ ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped ]
: (RootVersionPlan, untyped) -> [ActivityHistory, RootVersionPlan, ActivityStep?, RecordSnapshot?, untyped]
64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/paper_trail_diff/time_activity_timeline_builder.rb', line 64 def history_for(plan, captured_at) root_versions = plan.reconstruction_versions events = events_through(root_versions, captured_at) selected = selected_events(events, plan) unless time_events?(selected, events, plan) return [ActivityHistory.empty, plan, nil, nil, nil] end history = build_history(root_versions, events) snapshot = current_snapshot(plan) closing = closing_step(history, selected.last, plan, captured_at, snapshot) [history, plan, closing, snapshot, captured_at] end |
#later_event?(selected, events) ⇒ Boolean
: (ActivityEvent, Array) -> bool
206 207 208 209 210 |
# File 'lib/paper_trail_diff/time_activity_timeline_builder.rb', line 206 def later_event?(selected, events) events.any? do |event| Support.compare_versions(selected.version, event.version).negative? end end |
#prepare_history(root_versions, end_at: nil) ⇒ void
This method returns an undefined value.
: (Array, ?end_at: untyped) -> void
171 172 173 174 175 176 177 178 |
# File 'lib/paper_trail_diff/time_activity_timeline_builder.rb', line 171 def prepare_history(root_versions, end_at: nil) return unless @snapshotter.respond_to?(:prepare) start_at = @range.begin_time return @snapshotter.prepare(@record, root_versions, start_at: start_at) unless end_at @snapshotter.prepare(@record, root_versions, start_at: start_at, end_at: end_at) end |
#selected_events(events, plan) ⇒ Array[ActivityEvent]
Root versions inside the window that the plan does not report are context rather than mutations: the version appended to reveal the last selected change, and anything a filter excluded but the replay still walks. Neither may be counted as a selected mutation. Descendant events are not filtered, so window membership is the whole test for them. : (Array, RootVersionPlan) -> Array
117 118 119 120 121 122 123 |
# File 'lib/paper_trail_diff/time_activity_timeline_builder.rb', line 117 def selected_events(events, plan) events.select do |event| next false unless @range.include?(event.version) !event.root? || plan.mutation?(event.version) end end |
#terminal_destroy?(event) ⇒ Boolean
: (ActivityEvent?) -> bool
164 165 166 167 168 |
# File 'lib/paper_trail_diff/time_activity_timeline_builder.rb', line 164 def terminal_destroy?(event) return false unless event event.root? && event.version.event.to_s == 'destroy' end |
#time_events?(selected, events, plan) ⇒ Boolean
193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/paper_trail_diff/time_activity_timeline_builder.rb', line 193 def time_events?(selected, events, plan) return false if selected.empty? return true if later_event?(selected.last, events) return true if terminal_destroy?(selected.last) return true if plan.closing_record = 'time range requires a later activity boundary to reconstruct its ' \ 'final change: pass close_on: :current to end at current state, ' \ 'or narrow the window to end before the last recorded boundary' raise IncompleteTimeRangeError, end |