Class: PaperTrailDiff::ActivityTimelineBuilder

Inherits:
Object
  • Object
show all
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

Constructor Details

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

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

Parameters:



8
9
10
11
12
13
14
15
16
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 8

def initialize(record, range:, tree:, snapshotter:, snapshots: false)
  @snapshots = snapshots
  @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:



150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 150

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: @snapshots
    )
  end
  steps.freeze
end

#analyzeAnalysis

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

Returns:



30
31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 30

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:



19
20
21
22
23
24
25
26
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 19

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:



189
190
191
192
193
194
195
196
197
198
199
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 189

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:



66
67
68
69
70
71
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 66

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:



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 131

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:



74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 74

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:



92
93
94
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 92

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:



107
108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 107

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:



167
168
169
170
171
172
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 167

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:



175
176
177
178
179
180
181
182
183
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 175

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

#no_stepsArray[ActivityStep]

: () -> Array

Returns:



56
57
58
59
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 56

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)


123
124
125
126
127
128
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 123

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



202
203
204
205
206
207
208
209
210
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 202

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

#validate_current_range!void

This method returns an undefined value.

: () -> void



97
98
99
100
101
102
103
104
# File 'lib/paper_trail_diff/activity_timeline_builder.rb', line 97

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