Class: PaperTrailDiff::BatchBoundaryResolver
- Inherits:
-
Object
- Object
- PaperTrailDiff::BatchBoundaryResolver
- Defined in:
- lib/paper_trail_diff/batch_boundary_resolver.rb,
sig/generated/paper_trail_diff/batch_boundary_resolver.rbs
Overview
Resolves :first and :last endpoints for a whole batch in two queries per
model class. Left to the caller this is a per-root lookup, which reintroduces
exactly the queries a batched comparison exists to remove.
Constant Summary collapse
- BOUNDARIES =
%i[first last].freeze
Class Method Summary collapse
-
.symbolic?(endpoint) ⇒ Boolean
: (untyped) -> bool.
Instance Method Summary collapse
-
#add_model_boundaries(index, model_class, ids) ⇒ void
: (Hash[Array[String], Hash[Symbol, untyped]], untyped, Array) -> void.
-
#anchor(endpoint) ⇒ [ untyped, untyped ]?
: (untyped) -> [untyped, untyped]?.
-
#anchor_identity(endpoint) ⇒ Array[String]?
A symbol carries no identity of its own, so the pair's other endpoint has to say which record is meant.
-
#anchors ⇒ Array[[ untyped, untyped ]]
: () -> Array[[untyped, untyped]].
-
#boundary_index ⇒ Hash[Array[String], Hash[Symbol, untyped]]
: () -> Hash[Array[String], Hash[Symbol, untyped]].
-
#boundary_key(grouped, id) ⇒ Object
Grouped keys come back with whatever type the column uses, so match on the string form rather than assuming integers.
-
#boundary_versions(model_class, ids) ⇒ [ Hash[untyped, untyped], Hash[untyped, untyped], Hash[untyped, untyped] ]
Two grouped queries name each root's outermost versions, and one more loads them, whatever the size of the batch.
-
#call ⇒ Array[[ untyped, untyped ]]
Returns the pairs with every resolvable symbol replaced.
-
#identity_key(model_class, id) ⇒ Array[String]
: (untyped, untyped) -> Array.
-
#initialize(pairs) ⇒ BatchBoundaryResolver
constructor
: (Array[[untyped, untyped]]) -> void.
-
#resolve(endpoint, other, index) ⇒ Object
: (untyped, untyped, Hash[Array[String], Hash[Symbol, untyped]]) -> untyped.
-
#symbolic?(endpoint) ⇒ Boolean
: (untyped) -> bool.
Constructor Details
#initialize(pairs) ⇒ BatchBoundaryResolver
: (Array[[untyped, untyped]]) -> void
19 20 21 |
# File 'lib/paper_trail_diff/batch_boundary_resolver.rb', line 19 def initialize(pairs) @pairs = pairs end |
Class Method Details
.symbolic?(endpoint) ⇒ Boolean
: (untyped) -> bool
13 14 15 |
# File 'lib/paper_trail_diff/batch_boundary_resolver.rb', line 13 def symbolic?(endpoint) endpoint.is_a?(Symbol) end |
Instance Method Details
#add_model_boundaries(index, model_class, ids) ⇒ void
This method returns an undefined value.
: (Hash[Array[String], Hash[Symbol, untyped]], untyped, Array) -> void
102 103 104 105 106 107 108 109 110 |
# File 'lib/paper_trail_diff/batch_boundary_resolver.rb', line 102 def add_model_boundaries(index, model_class, ids) first_ids, last_ids, versions = boundary_versions(model_class, ids) ids.each do |id| index[identity_key(model_class, id)] = { first: versions[boundary_key(first_ids, id)], last: versions[boundary_key(last_ids, id)] } end end |
#anchor(endpoint) ⇒ [ untyped, untyped ]?
: (untyped) -> [untyped, untyped]?
89 90 91 92 93 94 95 96 97 98 99 |
# File 'lib/paper_trail_diff/batch_boundary_resolver.rb', line 89 def anchor(endpoint) return if endpoint.nil? || symbolic?(endpoint) if Endpoint.version?(endpoint) [Endpoint.model_class(endpoint), endpoint.item_id] elsif Endpoint.record?(endpoint) [endpoint.class, endpoint.id] end rescue InvalidEndpointError nil end |
#anchor_identity(endpoint) ⇒ Array[String]?
A symbol carries no identity of its own, so the pair's other endpoint has to say which record is meant. : (untyped) -> Array?
66 67 68 69 70 71 72 |
# File 'lib/paper_trail_diff/batch_boundary_resolver.rb', line 66 def anchor_identity(endpoint) return if endpoint.nil? || symbolic?(endpoint) Endpoint.identity(endpoint) rescue InvalidEndpointError nil end |
#anchors ⇒ Array[[ untyped, untyped ]]
: () -> Array[[untyped, untyped]]
84 85 86 |
# File 'lib/paper_trail_diff/batch_boundary_resolver.rb', line 84 def anchors @pairs.flatten(1).filter_map { |endpoint| anchor(endpoint) }.uniq end |
#boundary_index ⇒ Hash[Array[String], Hash[Symbol, untyped]]
: () -> Hash[Array[String], Hash[Symbol, untyped]]
75 76 77 78 79 80 81 |
# File 'lib/paper_trail_diff/batch_boundary_resolver.rb', line 75 def boundary_index index = {} #: Hash[Array[String], Hash[Symbol, untyped]] anchors.group_by { |model_class, _id| model_class }.each do |model_class, entries| add_model_boundaries(index, model_class, entries.map { |_klass, id| id }.uniq) end index end |
#boundary_key(grouped, id) ⇒ Object
Grouped keys come back with whatever type the column uses, so match on the string form rather than assuming integers. : (Hash[untyped, untyped], untyped) -> untyped
127 128 129 130 |
# File 'lib/paper_trail_diff/batch_boundary_resolver.rb', line 127 def boundary_key(grouped, id) key = grouped.keys.find { |candidate| candidate.to_s == id.to_s } grouped[key] end |
#boundary_versions(model_class, ids) ⇒ [ Hash[untyped, untyped], Hash[untyped, untyped], Hash[untyped, untyped] ]
Two grouped queries name each root's outermost versions, and one more loads them, whatever the size of the batch. : (untyped, Array) -> [Hash[untyped, untyped], Hash[untyped, untyped], Hash[untyped, untyped]]
115 116 117 118 119 120 121 122 |
# File 'lib/paper_trail_diff/batch_boundary_resolver.rb', line 115 def boundary_versions(model_class, ids) version_class = model_class.paper_trail.version_class scope = version_class.where(item_type: model_class.base_class.name.to_s, item_id: ids) first_ids = scope.group(:item_id).minimum(:id) last_ids = scope.group(:item_id).maximum(:id) loaded = version_class.where(id: (first_ids.values + last_ids.values).uniq).index_by(&:id) [first_ids, last_ids, loaded] end |
#call ⇒ Array[[ untyped, untyped ]]
Returns the pairs with every resolvable symbol replaced. A symbol that names history the record does not have is left in place, so the caller can decide what an absent history means rather than being handed nil. : () -> Array[[untyped, untyped]]
27 28 29 30 31 32 33 34 |
# File 'lib/paper_trail_diff/batch_boundary_resolver.rb', line 27 def call return @pairs unless @pairs.flatten(1).any? { |endpoint| symbolic?(endpoint) } index = boundary_index @pairs.map do |from, to| [resolve(from, to, index), resolve(to, from, index)] end end |
#identity_key(model_class, id) ⇒ Array[String]
: (untyped, untyped) -> Array
133 134 135 |
# File 'lib/paper_trail_diff/batch_boundary_resolver.rb', line 133 def identity_key(model_class, id) [model_class.base_class.name.to_s, id.to_s] end |
#resolve(endpoint, other, index) ⇒ Object
: (untyped, untyped, Hash[Array[String], Hash[Symbol, untyped]]) -> untyped
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/paper_trail_diff/batch_boundary_resolver.rb', line 46 def resolve(endpoint, other, index) return endpoint unless symbolic?(endpoint) unless BOUNDARIES.include?(endpoint) raise ConfigurationError, "unsupported boundary: #{endpoint.inspect}; use :first, :last, a version, or a record" end identity = anchor_identity(other) unless identity raise ConfigurationError, 'a :first or :last endpoint needs the other endpoint to name a record' end index.dig(identity, endpoint) || endpoint end |
#symbolic?(endpoint) ⇒ Boolean
: (untyped) -> bool
41 42 43 |
# File 'lib/paper_trail_diff/batch_boundary_resolver.rb', line 41 def symbolic?(endpoint) self.class.symbolic?(endpoint) end |