Class: PaperTrailDiff::PreparedRecordIndex

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

Request-scoped scalar history loaded once per model identity.

Instance Method Summary collapse

Constructor Details

#initialize(start_at, end_at: nil, live_records: []) ⇒ PreparedRecordIndex

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

Parameters:

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


221
222
223
224
225
226
227
228
229
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 221

def initialize(start_at, end_at: nil, live_records: [])
  @start_time = boundary_time(start_at)
  @end_time = boundary_time(end_at)
  @seeded_live_records = live_records.to_h do |record|
    [identity(record.class, record.id), record]
  end
  @state_loader = PreparedVersionStateLoader.new
  @series = {} #: Hash[Array[String], PreparedRecordSeries]
end

Instance Method Details

#add_series(model_class, id, versions, live) ⇒ void

This method returns an undefined value.

: (untyped, untyped, Hash[String, Array[untyped]], Hash[String, untyped]) -> void

Parameters:

  • (Object)
  • (Object)
  • (Hash[String, Array[untyped]])
  • (Hash[String, untyped])


288
289
290
291
292
293
294
295
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 288

def add_series(model_class, id, versions, live)
  record = live[id.to_s]
  @series[identity(model_class, id)] = PreparedRecordSeries.new(
    versions: versions.fetch(id.to_s, []),
    live: record && PreparedRecordState.new(record),
    state_loader: @state_loader
  )
end

#available_live_records(model_class, ids) ⇒ Array[untyped]

: (untyped, Array) -> Array

Parameters:

  • (Object)
  • (Array[untyped])

Returns:

  • (Array[untyped])


341
342
343
344
345
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 341

def available_live_records(model_class, ids)
  seeded = ids.filter_map { |id| @seeded_live_records[identity(model_class, id)] }
  missing = ids.reject { |id| @seeded_live_records.key?(identity(model_class, id)) }
  seeded.concat(live_records_for(model_class, missing))
end

#boundary_time(value) ⇒ Object

Callers bound the range with either a version or a bare timestamp. : (untyped) -> untyped

Parameters:

  • (Object)

Returns:

  • (Object)


273
274
275
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 273

def boundary_time(value)
  value.respond_to?(:created_at) ? value.created_at : value
end

#first_after_range(table) ⇒ Object

: (untyped) -> untyped

Parameters:

  • (Object)

Returns:

  • (Object)


321
322
323
324
325
326
327
328
329
330
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 321

def first_after_range(table)
  arel = Object.const_get(:Arel) #: untyped
  later = table.alias('paper_trail_diff_later_versions')
  arel.const_get(:SelectManager).new
      .from(later)
      .project(arel.sql('1'))
      .where(preceding_trailing_version(table, later))
      .exists
      .not
end

#identity(model_class, id) ⇒ Array[String]

: (untyped, untyped) -> Array

Parameters:

  • (Object)
  • (Object)

Returns:

  • (Array[String])


278
279
280
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 278

def identity(model_class, id)
  [model_class.base_class.name.to_s, id.to_s]
end

#live_records_for(model_class, ids) ⇒ Array[untyped]

: (untyped, Array) -> Array

Parameters:

  • (Object)
  • (Array[untyped])

Returns:

  • (Array[untyped])


348
349
350
351
352
353
354
355
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 348

def live_records_for(model_class, ids)
  return [] if ids.empty?

  primary_key = model_class.primary_key
  return [] if primary_key.is_a?(Array)

  model_class.base_class.unscoped.where(primary_key => ids).to_a
end

#load(model_class, ids) ⇒ void

This method returns an undefined value.

: (untyped, Array) -> void

Parameters:

  • (Object)
  • (Array[untyped])


232
233
234
235
236
237
238
239
240
241
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 232

def load(model_class, ids)
  missing = missing_ids(model_class, ids)
  return if missing.empty?

  versions = versions_for(model_class, missing).group_by { |version| version.item_id.to_s }
  live = available_live_records(model_class, missing).to_h do |record|
    [record.id.to_s, record]
  end
  missing.each { |id| add_series(model_class, id, versions, live) }
end

#loaded?(model_class, id) ⇒ Boolean

: (untyped, untyped) -> bool

Parameters:

  • (Object)
  • (Object)

Returns:

  • (Boolean)


259
260
261
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 259

def loaded?(model_class, id)
  @series.key?(identity(model_class, id))
end

#missing_ids(model_class, ids) ⇒ Array[untyped]

: (untyped, Array) -> Array

Parameters:

  • (Object)
  • (Array[untyped])

Returns:

  • (Array[untyped])


283
284
285
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 283

def missing_ids(model_class, ids)
  ids.compact.uniq.reject { |id| @series.key?(identity(model_class, id)) }
end

#preceding_trailing_version(table, later) ⇒ Object

: (untyped, untyped) -> untyped

Parameters:

  • (Object)
  • (Object)

Returns:

  • (Object)


333
334
335
336
337
338
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 333

def preceding_trailing_version(table, later)
  later[:item_type].eq(table[:item_type])
                   .and(later[:item_id].eq(table[:item_id]))
                   .and(later[:created_at].gt(@end_time))
                   .and(later[:created_at].lt(table[:created_at]))
end

#record_before(model_class, id, boundary) ⇒ Object

: (untyped, untyped, untyped) -> untyped

Parameters:

  • (Object)
  • (Object)
  • (Object)

Returns:

  • (Object)


244
245
246
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 244

def record_before(model_class, id, boundary)
  @series.fetch(identity(model_class, id)).record_before(boundary)
end

#records_for(model_class, ids) ⇒ Array[untyped]

: (untyped, Array) -> Array

Parameters:

  • (Object)
  • (Array[untyped])

Returns:

  • (Array[untyped])


254
255
256
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 254

def records_for(model_class, ids)
  ids.flat_map { |id| @series.fetch(identity(model_class, id)).records }
end

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

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

Parameters:

  • (Object)
  • (Object)
  • (Object)

Returns:

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


249
250
251
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 249

def transition(model_class, id, version)
  @series[identity(model_class, id)]&.transition(version)
end

#versions_for(model_class, ids) ⇒ Array[untyped]

A PaperTrail version is a pre-change snapshot, so the state at the last selected boundary can only live in the next version after it. One trailing version per identity is retained, rather than every version recorded between the requested range and the present. : (untyped, Array) -> Array

Parameters:

  • (Object)
  • (Array[untyped])

Returns:

  • (Array[untyped])


302
303
304
305
306
307
308
309
310
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 302

def versions_for(model_class, ids)
  version_class = model_class.paper_trail.version_class
  scope = version_class.where(
    item_type: model_class.base_class.name,
    item_id: ids
  ).where(created_at: @start_time..)
  scope = scope.where(window_condition(version_class)) if @end_time
  scope.order(:id).to_a
end

#window_condition(version_class) ⇒ Object

In range, or the earliest version after it, resolved in the same query rather than with a window function or one query per identity. : (untyped) -> untyped

Parameters:

  • (Object)

Returns:

  • (Object)


315
316
317
318
# File 'lib/paper_trail_diff/prepared_record_index.rb', line 315

def window_condition(version_class)
  table = version_class.arel_table
  table[:created_at].lteq(@end_time).or(first_after_range(table))
end