Class: PaperTrailDiff::CollectionComparator
- Inherits:
-
Object
- Object
- PaperTrailDiff::CollectionComparator
- Defined in:
- lib/paper_trail_diff/collection_comparator.rb,
sig/generated/paper_trail_diff/collection_comparator.rbs
Overview
Compares collection snapshots with a low-allocation path for stable membership.
Instance Method Summary collapse
-
#aligned_changed_pairs ⇒ Array[[ RecordSnapshot, RecordSnapshot ]]?
: () -> Array[[RecordSnapshot, RecordSnapshot]]?.
-
#aligned_changes ⇒ Array[RecordChange]?
: () -> Array?.
-
#call ⇒ CollectionAssociationDiff
: () -> CollectionAssociationDiff.
-
#changed_records(from_records, to_records) ⇒ Array[RecordChange]
: (Hash[identity, RecordSnapshot], Hash[identity, RecordSnapshot]) -> Array.
- #collect_aligned_pairs(from_records, to_records) ⇒ Array[[ RecordSnapshot, RecordSnapshot ]]?
-
#compare_transition_records(before, after) ⇒ RecordChange?
: (RecordSnapshot?, RecordSnapshot?) -> RecordChange?.
- #difference(added:, removed:, changed:) ⇒ CollectionAssociationDiff
-
#index_records(records) ⇒ Hash[identity, RecordSnapshot]
: (Array) -> Hash[identity, RecordSnapshot].
-
#indexed_difference ⇒ CollectionAssociationDiff
: () -> CollectionAssociationDiff.
-
#initialize(from_association, to_association, record_comparer:) ⇒ CollectionComparator
constructor
: (AssociationSnapshot, AssociationSnapshot, record_comparer: untyped) -> void.
-
#raise_duplicate_identity!(identity) ⇒ void
: (identity) -> void.
-
#records_missing_from(records, other_records) ⇒ Array[RecordSnapshot]
: (Hash[identity, RecordSnapshot], Hash[identity, RecordSnapshot]) -> Array.
-
#sortable_id(id) ⇒ Array[untyped]
: (untyped) -> Array.
-
#sortable_identity(identity) ⇒ Array[untyped]
Identities sort by type first so that mixed id types stay comparable, then naturally within one type.
- #sorted_identities(identities) ⇒ Array[identity]
-
#transition_difference(transition) ⇒ CollectionAssociationDiff
: (CollectionTransition) -> CollectionAssociationDiff.
Constructor Details
#initialize(from_association, to_association, record_comparer:) ⇒ CollectionComparator
: (AssociationSnapshot, AssociationSnapshot, record_comparer: untyped) -> void
8 9 10 11 12 |
# File 'lib/paper_trail_diff/collection_comparator.rb', line 8 def initialize(from_association, to_association, record_comparer:) @from = from_association @to = to_association @record_comparer = record_comparer end |
Instance Method Details
#aligned_changed_pairs ⇒ Array[[ RecordSnapshot, RecordSnapshot ]]?
: () -> Array[[RecordSnapshot, RecordSnapshot]]?
63 64 65 66 67 68 69 |
# File 'lib/paper_trail_diff/collection_comparator.rb', line 63 def aligned_changed_pairs from_records = @from.records to_records = @to.records return unless from_records.length == to_records.length collect_aligned_pairs(from_records, to_records) end |
#aligned_changes ⇒ Array[RecordChange]?
: () -> Array?
53 54 55 56 57 58 59 60 |
# File 'lib/paper_trail_diff/collection_comparator.rb', line 53 def aligned_changes pairs = aligned_changed_pairs return unless pairs pairs.sort_by { |_from, to| sortable_identity(to.identity) }.filter_map do |pair| @record_comparer.call(pair.fetch(0), pair.fetch(1)) end end |
#call ⇒ CollectionAssociationDiff
: () -> CollectionAssociationDiff
15 16 17 18 19 20 21 22 23 |
# File 'lib/paper_trail_diff/collection_comparator.rb', line 15 def call transition = @to.transition_from(@from) return transition_difference(transition) if transition aligned = aligned_changes return difference(added: [], removed: [], changed: aligned) if aligned indexed_difference end |
#changed_records(from_records, to_records) ⇒ Array[RecordChange]
: (Hash[identity, RecordSnapshot], Hash[identity, RecordSnapshot]) -> Array
118 119 120 121 122 123 124 125 126 |
# File 'lib/paper_trail_diff/collection_comparator.rb', line 118 def changed_records(from_records, to_records) sorted_identities(from_records.keys & to_records.keys).filter_map do |identity| from_record = from_records.fetch(identity) to_record = to_records.fetch(identity) next if from_record.equal?(to_record) @record_comparer.call(from_record, to_record) end end |
#collect_aligned_pairs(from_records, to_records) ⇒ Array[[ RecordSnapshot, RecordSnapshot ]]?
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/paper_trail_diff/collection_comparator.rb', line 72 def collect_aligned_pairs(from_records, to_records) seen = {} #: Hash[identity, bool] changed = [] #: Array[[RecordSnapshot, RecordSnapshot]] index = 0 while index < from_records.length from_record = from_records.fetch(index) to_record = to_records.fetch(index) identity = from_record.identity raise_duplicate_identity!(identity) if seen.key?(identity) return unless identity == to_record.identity seen[identity] = true changed << [from_record, to_record] unless from_record.equal?(to_record) index += 1 end changed end |
#compare_transition_records(before, after) ⇒ RecordChange?
: (RecordSnapshot?, RecordSnapshot?) -> RecordChange?
46 47 48 49 50 |
# File 'lib/paper_trail_diff/collection_comparator.rb', line 46 def compare_transition_records(before, after) return unless before && after && before.identity == after.identity @record_comparer.call(before, after) end |
#difference(added:, removed:, changed:) ⇒ CollectionAssociationDiff
102 103 104 105 106 107 108 109 |
# File 'lib/paper_trail_diff/collection_comparator.rb', line 102 def difference(added:, removed:, changed:) CollectionAssociationDiff.new( kind: @from.kind, added: added, removed: removed, changed: changed ) end |
#index_records(records) ⇒ Hash[identity, RecordSnapshot]
: (Array) -> Hash[identity, RecordSnapshot]
129 130 131 132 133 134 135 136 137 138 |
# File 'lib/paper_trail_diff/collection_comparator.rb', line 129 def index_records(records) index = {} #: Hash[identity, RecordSnapshot] records.each do |record| identity = record.identity raise_duplicate_identity!(identity) if index.key?(identity) index[identity] = record end index end |
#indexed_difference ⇒ CollectionAssociationDiff
: () -> CollectionAssociationDiff
91 92 93 94 95 96 97 98 99 |
# File 'lib/paper_trail_diff/collection_comparator.rb', line 91 def indexed_difference from_records = index_records(@from.records) to_records = index_records(@to.records) difference( added: records_missing_from(to_records, from_records), removed: records_missing_from(from_records, to_records), changed: changed_records(from_records, to_records) ) end |
#raise_duplicate_identity!(identity) ⇒ void
This method returns an undefined value.
: (identity) -> void
164 165 166 |
# File 'lib/paper_trail_diff/collection_comparator.rb', line 164 def raise_duplicate_identity!(identity) raise ArgumentError, "duplicate record identity: #{identity.inspect}" end |
#records_missing_from(records, other_records) ⇒ Array[RecordSnapshot]
: (Hash[identity, RecordSnapshot], Hash[identity, RecordSnapshot]) -> Array
112 113 114 115 |
# File 'lib/paper_trail_diff/collection_comparator.rb', line 112 def records_missing_from(records, other_records) identities = sorted_identities(records.keys - other_records.keys) identities.map { |identity| records.fetch(identity) } end |
#sortable_id(id) ⇒ Array[untyped]
: (untyped) -> Array
155 156 157 158 159 160 161 |
# File 'lib/paper_trail_diff/collection_comparator.rb', line 155 def sortable_id(id) case id when Numeric then [0, id, ''] when String, Symbol then [1, 0, id.to_s] else [2, 0, id.inspect] end end |
#sortable_identity(identity) ⇒ Array[untyped]
Identities sort by type first so that mixed id types stay comparable, then naturally within one type. Ordering by the printed form instead would put id 10 before id 2, which is deterministic but reads as unsorted wherever a result is rendered. : (identity) -> Array
150 151 152 |
# File 'lib/paper_trail_diff/collection_comparator.rb', line 150 def sortable_identity(identity) [identity.fetch(0), *sortable_id(identity.fetch(1))] end |
#sorted_identities(identities) ⇒ Array[identity]
141 142 143 |
# File 'lib/paper_trail_diff/collection_comparator.rb', line 141 def sorted_identities(identities) identities.sort_by { |identity| sortable_identity(identity) } end |
#transition_difference(transition) ⇒ CollectionAssociationDiff
: (CollectionTransition) -> CollectionAssociationDiff
32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/paper_trail_diff/collection_comparator.rb', line 32 def transition_difference(transition) @from.validate_unique_identities! @to.validate_unique_identities! before = transition.before after = transition.after changed = compare_transition_records(before, after) difference( added: before.nil? && after ? [after] : [], removed: after.nil? && before ? [before] : [], changed: changed ? [changed] : [] ) end |