Class: PaperTrailDiff::BatchedRootVersions
- Inherits:
-
Object
- Object
- PaperTrailDiff::BatchedRootVersions
- Defined in:
- lib/paper_trail_diff/batched_root_versions.rb,
sig/generated/paper_trail_diff/batched_root_versions.rbs
Overview
Selects each root's versions for a batched range in a fixed number of queries. Only the range forms that mean the same thing for every root are supported: a shared wall-clock window, or each root's own whole history.
Instance Method Summary collapse
-
#after_window(later, range) ⇒ Object
: (untyped, TimeRange) -> untyped.
-
#base_scope(model_class, ids) ⇒ Object
: (untyped, Array) -> untyped.
-
#call ⇒ Hash[Array[String], RootVersionPlan]
Returns a plan per record identity.
-
#chosen_version_ids(model_class, ids, range) ⇒ Set[untyped]?
One extra query names the selected mutations without discarding the unfiltered versions the successor lookup still needs.
-
#grouped_versions(model_class, ids, range) ⇒ Hash[String, Array[untyped]]
: (untyped, Array, TimeRange?) -> Hash[String, Array[untyped]].
-
#identity(model_class, id) ⇒ Array[String]
: (untyped, untyped) -> Array.
-
#initialize(records, time_range:, version_scope: nil, live_endpoints: nil) ⇒ BatchedRootVersions
constructor
: (Array, time_range: TimeRange?, ?version_scope: untyped, ?live_endpoints: Hash[Array[String], untyped]?) -> void.
-
#live_endpoint(key, record) ⇒ Object
The batch already loaded every root, so closing on current state reuses that rather than reading each record again.
-
#model_versions(model_class, ids) ⇒ [ Hash[String, Array[untyped]], Set[untyped]?, Hash[String, untyped] ]
: (untyped, Array) -> [Hash[String, Array[untyped]], Set[untyped]?, Hash[String, untyped]].
-
#no_earlier_trailing(table, range) ⇒ Object
: (untyped, TimeRange) -> untyped.
-
#ordered(relation) ⇒ Array[untyped]
: (untyped) -> Array.
-
#preceding(table, later, range) ⇒ Object
Mirrors the window's own end handling, so a version sitting exactly on an inclusive boundary stays inside the window instead of masking the trailing version that follows it.
-
#range_scope(model_class, ids, range) ⇒ Object
: (untyped, Array, TimeRange?) -> untyped.
-
#select_model(model_class, records, selected) ⇒ void
: (untyped, Array, Hash[Array[String], RootVersionPlan]) -> void.
-
#trailing_versions(model_class, ids, range) ⇒ Hash[String, untyped]
One row per root: the earliest version after the window, found without a window function so the query stays portable.
- #versions_for(in_range, chosen, after_range, live_endpoint) ⇒ RootVersionPlan
Constructor Details
#initialize(records, time_range:, version_scope: nil, live_endpoints: nil) ⇒ BatchedRootVersions
: (Array, time_range: TimeRange?, ?version_scope: untyped, ?live_endpoints: Hash[Array[String], untyped]?) -> void
10 11 12 13 14 15 |
# File 'lib/paper_trail_diff/batched_root_versions.rb', line 10 def initialize(records, time_range:, version_scope: nil, live_endpoints: nil) @records = records @time_range = time_range @version_scope = version_scope @live_endpoints = live_endpoints end |
Instance Method Details
#after_window(later, range) ⇒ Object
: (untyped, TimeRange) -> untyped
140 141 142 143 144 |
# File 'lib/paper_trail_diff/batched_root_versions.rb', line 140 def after_window(later, range) return later[:created_at].gteq(range.end_time) if range.exclude_end? later[:created_at].gt(range.end_time) end |
#base_scope(model_class, ids) ⇒ Object
: (untyped, Array) -> untyped
147 148 149 150 151 152 |
# File 'lib/paper_trail_diff/batched_root_versions.rb', line 147 def base_scope(model_class, ids) model_class.paper_trail.version_class.where( item_type: model_class.base_class.name.to_s, item_id: ids ) end |
#call ⇒ Hash[Array[String], RootVersionPlan]
Returns a plan per record identity. : () -> Hash[Array[String], RootVersionPlan]
19 20 21 22 23 24 25 26 27 |
# File 'lib/paper_trail_diff/batched_root_versions.rb', line 19 def call return {} if @records.empty? selected = {} #: Hash[Array[String], RootVersionPlan] @records.group_by(&:class).each do |model_class, records| select_model(model_class, records, selected) end selected end |
#chosen_version_ids(model_class, ids, range) ⇒ Set[untyped]?
86 87 88 89 90 91 92 93 94 |
# File 'lib/paper_trail_diff/batched_root_versions.rb', line 86 def chosen_version_ids(model_class, ids, range) scope = @version_scope return unless scope # A narrowed relation is the expected return. Active Support also gives # `pluck` to plain enumerables, so an array of versions works too. filtered = scope.call(range_scope(model_class, ids, range)) Set.new(filtered.pluck(:id)) end |
#grouped_versions(model_class, ids, range) ⇒ Hash[String, Array[untyped]]
: (untyped, Array, TimeRange?) -> Hash[String, Array[untyped]]
97 98 99 |
# File 'lib/paper_trail_diff/batched_root_versions.rb', line 97 def grouped_versions(model_class, ids, range) ordered(range_scope(model_class, ids, range)).group_by { |version| version.item_id.to_s } end |
#identity(model_class, id) ⇒ Array[String]
: (untyped, untyped) -> Array
160 161 162 |
# File 'lib/paper_trail_diff/batched_root_versions.rb', line 160 def identity(model_class, id) [model_class.base_class.name.to_s, id.to_s] end |
#live_endpoint(key, record) ⇒ Object
The batch already loaded every root, so closing on current state reuses that rather than reading each record again. : (Array, untyped) -> untyped
75 76 77 78 79 80 81 |
# File 'lib/paper_trail_diff/batched_root_versions.rb', line 75 def live_endpoint(key, record) loaded = @live_endpoints return unless loaded current = loaded.fetch(key, record) current unless current.destroyed? end |
#model_versions(model_class, ids) ⇒ [ Hash[String, Array[untyped]], Set[untyped]?, Hash[String, untyped] ]
: (untyped, Array) -> [Hash[String, Array[untyped]], Set[untyped]?, Hash[String, untyped]]
50 51 52 53 54 55 56 57 58 |
# File 'lib/paper_trail_diff/batched_root_versions.rb', line 50 def model_versions(model_class, ids) range = @time_range empty = {} #: Hash[String, untyped] [ grouped_versions(model_class, ids, range), chosen_version_ids(model_class, ids, range), range ? trailing_versions(model_class, ids, range) : empty ] end |
#no_earlier_trailing(table, range) ⇒ Object
: (untyped, TimeRange) -> untyped
118 119 120 121 122 123 124 125 126 127 |
# File 'lib/paper_trail_diff/batched_root_versions.rb', line 118 def no_earlier_trailing(table, range) arel = Object.const_get(:Arel) #: untyped later = table.alias('paper_trail_diff_later_roots') arel.const_get(:SelectManager).new .from(later) .project(arel.sql('1')) .where(preceding(table, later, range)) .exists .not end |
#ordered(relation) ⇒ Array[untyped]
: (untyped) -> Array
155 156 157 |
# File 'lib/paper_trail_diff/batched_root_versions.rb', line 155 def ordered(relation) relation.reorder(created_at: :asc, id: :asc).to_a end |
#preceding(table, later, range) ⇒ Object
Mirrors the window's own end handling, so a version sitting exactly on an inclusive boundary stays inside the window instead of masking the trailing version that follows it. : (untyped, untyped, TimeRange) -> untyped
133 134 135 136 137 |
# File 'lib/paper_trail_diff/batched_root_versions.rb', line 133 def preceding(table, later, range) same_item = later[:item_type].eq(table[:item_type]) .and(later[:item_id].eq(table[:item_id])) same_item.and(after_window(later, range)).and(later[:created_at].lt(table[:created_at])) end |
#range_scope(model_class, ids, range) ⇒ Object
: (untyped, Array, TimeRange?) -> untyped
102 103 104 105 |
# File 'lib/paper_trail_diff/batched_root_versions.rb', line 102 def range_scope(model_class, ids, range) scope = base_scope(model_class, ids) range ? range.scope(scope) : scope end |
#select_model(model_class, records, selected) ⇒ void
This method returns an undefined value.
: (untyped, Array, Hash[Array[String], RootVersionPlan]) -> void
37 38 39 40 41 42 43 44 45 46 47 |
# File 'lib/paper_trail_diff/batched_root_versions.rb', line 37 def select_model(model_class, records, selected) ids = records.map(&:id) in_range, chosen, trailing = model_versions(model_class, ids) records.each do |record| key = identity(model_class, record.id) selected[key] = versions_for( in_range.fetch(record.id.to_s, []), chosen, trailing[record.id.to_s], live_endpoint(key, record) ) end end |
#trailing_versions(model_class, ids, range) ⇒ Hash[String, untyped]
One row per root: the earliest version after the window, found without a window function so the query stays portable. : (untyped, Array, TimeRange) -> Hash[String, untyped]
110 111 112 113 114 115 |
# File 'lib/paper_trail_diff/batched_root_versions.rb', line 110 def trailing_versions(model_class, ids, range) table = model_class.paper_trail.version_class.arel_table relation = range.trailing_scope(base_scope(model_class, ids)) ordered(relation.where(no_earlier_trailing(table, range))) .to_h { |version| [version.item_id.to_s, version] } end |
#versions_for(in_range, chosen, after_range, live_endpoint) ⇒ RootVersionPlan
61 62 63 64 65 66 67 68 69 70 |
# File 'lib/paper_trail_diff/batched_root_versions.rb', line 61 def versions_for(in_range, chosen, after_range, live_endpoint) RootVersionSelection.new( in_range: in_range, selected: chosen ? in_range.select { |version| chosen.include?(version.id) } : in_range, after_range: after_range, windowed: !@time_range.nil?, filtered: !@version_scope.nil?, live_endpoint: live_endpoint ).call end |