Class: PaperTrailDiff::PreparedRecordSeries

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

Overview

Versions and live fallback for one model identity.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(versions:, live:, state_loader: PreparedVersionStateLoader.new) ⇒ PreparedRecordSeries

: (versions: Array, live: PreparedRecordState?, ?state_loader: PreparedVersionStateLoader) -> void

Parameters:



111
112
113
114
115
116
117
118
119
120
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 111

def initialize(versions:, live:, state_loader: PreparedVersionStateLoader.new)
  @versions = Support.chronological_sort(versions).freeze
  @version_positions = @versions.each_with_index.to_h do |version, index|
    [version.id.to_s, index]
  end.freeze
  @transaction_positions = build_transaction_positions
  @live = live
  @state_loader = state_loader
  @states = {} #: Hash[untyped, PreparedRecordState?]
end

Instance Attribute Details

#versionsArray[untyped] (readonly)

Signature:

  • Array[untyped]

Returns:

  • (Array[untyped])


108
109
110
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 108

def versions
  @versions
end

Instance Method Details

#boundary_index(boundary) ⇒ Integer?

A version is eligible when it is at or after the boundary, or shares the boundary's transaction. The first is monotonic over the chronological list and the second is indexed, so neither rescans a long history once per boundary. : (untyped) -> Integer?

Parameters:

  • (Object)

Returns:

  • (Integer, nil)


168
169
170
171
172
173
174
175
176
177
178
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 168

def boundary_index(boundary)
  created_at = boundary.created_at
  chronological = versions.bsearch_index do |candidate|
    candidate.created_at >= created_at
  end
  transactional = transaction_index(boundary)
  return chronological unless transactional
  return transactional unless chronological

  [chronological, transactional].min
end

#build_transaction_positionsHash[untyped, Integer]

: () -> Hash[untyped, Integer]

Returns:

  • (Hash[untyped, Integer])


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

def build_transaction_positions
  positions = {} #: Hash[untyped, Integer]
  @versions.each_with_index do |version, index|
    next unless version.respond_to?(:transaction_id)

    transaction_id = version.transaction_id
    positions[transaction_id] ||= index if transaction_id
  end
  positions.freeze
end

#record_before(boundary) ⇒ Object

: (untyped) -> untyped

Parameters:

  • (Object)

Returns:

  • (Object)


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

def record_before(boundary)
  index = boundary_index(boundary)
  return state_for(versions.fetch(index))&.instantiate if index

  @live&.instantiate
end

#recordsArray[untyped]

: () -> Array

Returns:

  • (Array[untyped])


131
132
133
134
135
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 131

def records
  version_records = versions.filter_map { |version| state_for(version)&.instantiate }
  live = @live
  live ? [*version_records, live.instantiate] : version_records
end

#state_after(index) ⇒ PreparedRecordState?

: (Integer) -> PreparedRecordState?

Parameters:

  • (Integer)

Returns:



158
159
160
161
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 158

def state_after(index)
  successor = versions[index + 1]
  successor ? state_for(successor) : @live
end

#state_for(version) ⇒ PreparedRecordState?

: (untyped) -> PreparedRecordState?

Parameters:

  • (Object)

Returns:



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 201

def state_for(version)
  return @states[version.id] if @states.key?(version.id)

  prepared = @state_loader.call(version)
  return @states[version.id] = prepared if prepared

  record = version.reify(
    dup: true,
    has_many: false,
    has_one: false,
    belongs_to: false,
    has_and_belongs_to_many: false
  )
  @states[version.id] = record && PreparedRecordState.new(record)
end

#transaction_index(boundary) ⇒ Integer?

: (untyped) -> Integer?

Parameters:

  • (Object)

Returns:

  • (Integer, nil)


181
182
183
184
185
186
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 181

def transaction_index(boundary)
  transaction_id = boundary.transaction_id if boundary.respond_to?(:transaction_id)
  return unless transaction_id

  @transaction_positions[transaction_id]
end

#transition(version) ⇒ [ Hash[untyped, untyped], Hash[untyped, untyped] ]?

: (untyped) -> [Hash[untyped, untyped], Hash[untyped, untyped]]?

Parameters:

  • (Object)

Returns:

  • ([ Hash[untyped, untyped], Hash[untyped, untyped] ], nil)


138
139
140
141
142
143
144
145
146
147
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 138

def transition(version)
  index = @version_positions[version.id.to_s]
  return unless index

  before = state_for(versions.fetch(index))
  after = state_after(index)
  return unless before && after && before.model_class == after.model_class

  [before.attributes, after.attributes]
end