Class: PaperTrailDiff::PreparedRecordIndex
- Inherits:
-
Object
- Object
- PaperTrailDiff::PreparedRecordIndex
- 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
-
#add_series(model_class, id, versions, live) ⇒ void
: (untyped, untyped, Hash[String, Array[untyped]], Hash[String, untyped]) -> void.
- #available_live_records(model_class, ids) ⇒ Array[untyped]
-
#boundary_time(value) ⇒ Object
Callers bound the range with either a version or a bare timestamp.
-
#first_after_range(table) ⇒ Object
: (untyped) -> untyped.
-
#identity(model_class, id) ⇒ Array[String]
: (untyped, untyped) -> Array.
-
#initialize(start_at, end_at: nil, live_records: []) ⇒ PreparedRecordIndex
constructor
: (untyped, ?end_at: untyped, ?live_records: Array) -> void.
- #live_records_for(model_class, ids) ⇒ Array[untyped]
-
#load(model_class, ids) ⇒ void
: (untyped, Array) -> void.
-
#loaded?(model_class, id) ⇒ Boolean
: (untyped, untyped) -> bool.
- #missing_ids(model_class, ids) ⇒ Array[untyped]
-
#preceding_trailing_version(table, later) ⇒ Object
: (untyped, untyped) -> untyped.
-
#record_before(model_class, id, boundary) ⇒ Object
: (untyped, untyped, untyped) -> untyped.
- #records_for(model_class, ids) ⇒ Array[untyped]
-
#transition(model_class, id, version) ⇒ [ Hash[untyped, untyped], Hash[untyped, untyped] ]?
: (untyped, untyped, untyped) -> [Hash[untyped, untyped], Hash[untyped, untyped]]?.
-
#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.
-
#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.
Constructor Details
#initialize(start_at, end_at: nil, live_records: []) ⇒ PreparedRecordIndex
: (untyped, ?end_at: untyped, ?live_records: Array) -> void
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
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]
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
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
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
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]
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
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
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]
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
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
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]
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]]?
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]
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
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 |