Class: PaperTrailDiff::ActivityTimelineBuilder

Inherits:
Object
  • Object
show all
Includes:
ActivityGrouping
Defined in:
lib/paper_trail_diff/activity_timeline_builder.rb,
sig/generated/paper_trail_diff/activity_timeline_builder.rbs

Overview

Compares adjacent root and selected-descendant activity boundaries.

Instance Method Summary collapse

Methods included from ActivityGrouping

#group_steps, #grouping?

Constructor Details

#initialize(record, range:, tree:, snapshotter:, snapshots: false, group: nil) ⇒ ActivityTimelineBuilder

: (untyped, range: TimelineRange, tree: AssociationTree, snapshotter: untyped, ?snapshots: bool, ?group: Symbol?) -> void

Parameters:

  • (Object)
  • range: (TimelineRange)
  • tree: (AssociationTree)
  • snapshotter: (Object)
  • snapshots: (Boolean) (defaults to: false)
  • group: (Symbol, nil) (defaults to: nil)


10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/paper_trail_diff/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
  @from = range.from
  @to = range.to
  @range = range
  @tree = tree
  @snapshotter = snapshotter
end

Instance Method Details

#activity_steps(history, events, final_boundary, final_snapshot) ⇒ Array[ActivityStep]

Appends the transition into an explicit closing boundary, which is either a requested current record or the absence a final root destroy leaves. : (ActivityHistory, Array, ActivityBoundary?, RecordSnapshot?) -> Array

Parameters:

Returns:



156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 156

def activity_steps(history, events, final_boundary, final_snapshot)
  steps = history.steps.dup
  previous_event = events.last
  previous_boundary = ActivityBoundary.from_version(previous_event.version) if previous_event
  if final_boundary && previous_boundary
    steps << ActivityStep.between(
      from_boundary: previous_boundary, to_boundary: final_boundary,
      from_snapshot: history.last_snapshot, to_snapshot: final_snapshot,
      retain: @retain
    )
  end
  group_steps(steps).freeze
end

#analyzeAnalysis

Builds all three version-bounded views from one activity snapshot pass. : () -> Analysis

Returns:



36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 36

def analyze
  return time_builder.analyze if @range.time?
  return Analysis.empty if @range.unresolved?

  unless Endpoint.version?(@to)
    raise InvalidTimelineRangeError, '`to` must be a root PaperTrail version'
  end

  plan = @range.select_plan
  root_versions = plan.reconstruction_versions
  prepare_history(root_versions)
  events = collect_events(root_versions)
  build_analysis(plan, events, event_history(root_versions, events))
end

#buildArray[ActivityStep]

: () -> Array

Returns:



25
26
27
28
29
30
31
32
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 25

def build
  return time_builder.build if @range.time?
  return no_steps if @range.unresolved?
  return build_to_current if Endpoint.record?(@to)

  Endpoint.validate!(@to)
  build_between_versions
end

#build_analysis(plan, events, history) ⇒ Analysis

Only the activity view gains the closing removal. The endpoint diff and the root timeline keep their compare and timeline semantics, under which the state at a destroy version is the state before the deletion. : (RootVersionPlan, Array, ActivityHistory) -> Analysis

Parameters:

Returns:



195
196
197
198
199
200
201
202
203
204
205
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 195

def build_analysis(plan, events, history)
  Analysis.new(
    diff: Engine.compare(history.first_snapshot, history.last_snapshot),
    timeline: ActivityRootSteps.call(plan, history.root_snapshots),
    activity_timeline: activity_steps(
      history, events, destroyed_boundary(plan.reconstruction_versions), nil
    ),
    from_snapshot: history.first_snapshot,
    to_snapshot: history.last_snapshot
  )
end

#build_between_versionsArray[ActivityStep]

Every boundary in the span, filtered out or not. A filter narrows where the span starts and ends, but the sequence inside it stays complete: dropping boundaries would fold the changes they carried into a neighbouring step and credit them to whoever made that one. : () -> Array

Returns:



72
73
74
75
76
77
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 72

def build_between_versions
  root_versions = @range.select_plan.reconstruction_versions
  prepare_history(root_versions)
  events = collect_events(root_versions)
  build_event_steps(root_versions, events)
end

#build_event_steps(root_versions, events, current: nil, final_boundary: nil, final_snapshot: nil) ⇒ Array[ActivityStep]

: (Array, Array, ?current: untyped, ?final_boundary: ActivityBoundary?, ?final_snapshot: RecordSnapshot?) -> Array

Parameters:

Returns:



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 137

def build_event_steps(
  root_versions,
  events,
  current: nil,
  final_boundary: nil,
  final_snapshot: nil
)
  history = event_history(root_versions, events, current: current)
  activity_steps(
    history,
    events,
    final_boundary || destroyed_boundary(root_versions),
    final_snapshot
  )
end

#build_to_currentArray[ActivityStep]

: () -> Array

Returns:



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 80

def build_to_current
  validate_current_range!
  current_snapshot, captured_at = capture_current
  root_versions = VersionRange.new(@record, from: @from, to: @from).select_through_latest
  # Descendants can move after the last root version, so the prepared range
  # ends at the captured instant rather than at that version.
  prepare_history(root_versions, end_at: captured_at)
  events = collect_events(root_versions, range_end: captured_at)
  build_event_steps(
    root_versions,
    events,
    current: @to,
    final_boundary: ActivityBoundary.current(@to, captured_at: captured_at),
    final_snapshot: current_snapshot
  )
end

#capture_current[ RecordSnapshot?, untyped ]

: () -> [RecordSnapshot?, untyped]

Returns:



98
99
100
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 98

def capture_current
  [@snapshotter.call(@to, @to), Time.now.utc]
end

#collect_events(root_versions, range_start: root_versions.first, range_end: root_versions.last) ⇒ Array[ActivityEvent]

: (Array, ?range_start: untyped, ?range_end: untyped) -> Array

Parameters:

  • (Array[untyped])
  • range_start: (Object) (defaults to: root_versions.first)
  • range_end: (Object) (defaults to: root_versions.last)

Returns:



113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 113

def collect_events(
  root_versions,
  range_start: root_versions.first,
  range_end: root_versions.last
)
  ActivityVersionCollector.new(
    @record,
    root_versions: root_versions,
    tree: @tree,
    traversal: AssociationTraversal.new(@tree),
    range_start: range_start,
    range_end: range_end
  ).call
end

#destroyed_boundary(root_versions) ⇒ ActivityBoundary?

A destroyed root has no later version, but its own event states that nothing follows it, so the removal can still close the timeline. : (Array) -> ActivityBoundary?

Parameters:

  • (Array[untyped])

Returns:



173
174
175
176
177
178
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 173

def destroyed_boundary(root_versions)
  version = root_versions.last
  return unless version && version.event.to_s == 'destroy'

  ActivityBoundary.destroyed(version)
end

#event_history(root_versions, events, current: nil) ⇒ ActivityHistory

: (Array, Array, ?current: untyped) -> ActivityHistory

Parameters:

  • (Array[untyped])
  • (Array[ActivityEvent])
  • current: (Object) (defaults to: nil)

Returns:



181
182
183
184
185
186
187
188
189
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 181

def event_history(root_versions, events, current: nil)
  ActivityHistoryBuilder.new(
    root_versions,
    events,
    @snapshotter,
    current: current,
    snapshots: @retain
  ).call
end

#no_stepsArray[ActivityStep]

: () -> Array

Returns:



62
63
64
65
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 62

def no_steps
  steps = [] #: Array[ActivityStep]
  steps.freeze
end

#prepare_history(root_versions, end_at: nil) ⇒ void

This method returns an undefined value.

: (Array, ?end_at: untyped) -> void

Parameters:

  • (Array[untyped])
  • end_at: (Object) (defaults to: nil)


129
130
131
132
133
134
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 129

def prepare_history(root_versions, end_at: nil)
  return unless @snapshotter.respond_to?(:prepare)
  return @snapshotter.prepare(@record, root_versions) unless end_at

  @snapshotter.prepare(@record, root_versions, end_at: end_at)
end

#time_builderTimeActivityTimelineBuilder

: () -> TimeActivityTimelineBuilder



208
209
210
211
212
213
214
215
216
217
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 208

def time_builder
  TimeActivityTimelineBuilder.new(
    @record,
    range: @range,
    tree: @tree,
    snapshotter: @snapshotter,
    snapshots: @snapshots,
    group: @group
  )
end

#validate_current_range!void

This method returns an undefined value.

: () -> void



103
104
105
106
107
108
109
110
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 103

def validate_current_range!
  unless Endpoint.version?(@from)
    raise InvalidTimelineRangeError, '`from` must be a root PaperTrail version'
  end

  Endpoint.validate_pair!(@from, @to)
  Endpoint.validate_pair!(@record, @to)
end