Class: PaperTrailDiff::SnapshotNormalizer
- Inherits:
-
Object
- Object
- PaperTrailDiff::SnapshotNormalizer
- Defined in:
- lib/paper_trail_diff/snapshot_normalizer.rb,
sig/generated/paper_trail_diff/snapshot_normalizer.rbs
Overview
Converts an ActiveRecord graph into immutable, persistence-independent snapshots.
Instance Method Summary collapse
-
#association_snapshot(record, reflection, subtree, path, reifier) ⇒ AssociationSnapshot
: (untyped, untyped, AssociationTree, String, untyped) -> AssociationSnapshot.
-
#attributes_for(record, tree:, path:) ⇒ snapshot_attributes
Normalizes scalar attributes without traversing or loading associations.
- #build_excluded_attributes(model_class, reflections, path) ⇒ Array[String]
-
#call(record, reifier:) ⇒ RecordSnapshot?
: (untyped, reifier: untyped) -> RecordSnapshot?.
-
#call_child(record, tree:, path:, incoming:, reifier:) ⇒ RecordSnapshot?
Normalizes one member of an already selected association branch.
-
#excluded_attributes(record, reflections, path) ⇒ Array[String]
Every input is fixed for one model class at one selected path, and the path's structural columns are recorded before its records normalize, so this is resolved once instead of per record per boundary.
-
#initialize(tree:, ignore_policy:, traversal:, pool: SnapshotPool.new) ⇒ SnapshotNormalizer
constructor
: (tree: AssociationTree, ignore_policy: IgnorePolicy, traversal: AssociationTraversal, ?pool: SnapshotPool) -> void.
-
#normalize_associations(record, tree, path, reifier, reflections) ⇒ Hash[String, AssociationSnapshot]
: (untyped, AssociationTree, String, untyped, Array) -> Hash[String, AssociationSnapshot].
- #normalize_children(records, tree, path, reifier) ⇒ Array[RecordSnapshot]
-
#normalize_record(record, tree, path, reifier, reflections) ⇒ RecordSnapshot?
: (untyped, AssociationTree, String, untyped, Array) -> RecordSnapshot?.
-
#normalized_attributes(record, reflections, path) ⇒ snapshot_attributes
: (untyped, Array, String) -> snapshot_attributes.
-
#reflections_for(record, tree, path) ⇒ Array[untyped]
: (untyped, AssociationTree, String) -> Array.
- #relationship_columns(reflections) ⇒ Array[String]
Constructor Details
#initialize(tree:, ignore_policy:, traversal:, pool: SnapshotPool.new) ⇒ SnapshotNormalizer
: (tree: AssociationTree, ignore_policy: IgnorePolicy, traversal: AssociationTraversal, ?pool: SnapshotPool) -> void
50 51 52 53 54 55 56 57 |
# File 'lib/paper_trail_diff/snapshot_normalizer.rb', line 50 def initialize(tree:, ignore_policy:, traversal:, pool: SnapshotPool.new) @tree = tree @ignore_policy = ignore_policy @traversal = traversal @pool = pool @structural_columns = {} #: Hash[String, Array[String]] @excluded_attributes = {} #: Hash[Array[untyped], Array[String]] end |
Instance Method Details
#association_snapshot(record, reflection, subtree, path, reifier) ⇒ AssociationSnapshot
: (untyped, untyped, AssociationTree, String, untyped) -> AssociationSnapshot
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 |
# File 'lib/paper_trail_diff/snapshot_normalizer.rb', line 154 def association_snapshot( # rubocop:disable Metrics/AbcSize record, reflection, subtree, path, reifier ) associated = record.public_send(reflection.name) records = if AssociationSnapshot.collection_kind?(reflection.macro) associated.to_a else Array(associated).compact end child_path = Support.association_path(path, reflection.name.to_s) @structural_columns[child_path] ||= @traversal.incoming_relationship_columns(reflection) snapshot = AssociationSnapshot.new( kind: reflection.macro, records: normalize_children(records, subtree, child_path, reifier) ) @pool.association(child_path, snapshot) end |
#attributes_for(record, tree:, path:) ⇒ snapshot_attributes
Normalizes scalar attributes without traversing or loading associations. : (untyped, tree: AssociationTree, path: String) -> snapshot_attributes
78 79 80 81 |
# File 'lib/paper_trail_diff/snapshot_normalizer.rb', line 78 def attributes_for(record, tree:, path:) reflections = reflections_for(record, tree, path) normalized_attributes(record, reflections, path) end |
#build_excluded_attributes(model_class, reflections, path) ⇒ Array[String]
123 124 125 126 127 128 129 130 131 |
# File 'lib/paper_trail_diff/snapshot_normalizer.rb', line 123 def build_excluded_attributes(model_class, reflections, path) primary_key = model_class.primary_key #: untyped primary_keys = primary_key.is_a?(Array) ? primary_key : [primary_key] # @type var primary_keys: Array[untyped] primary_keys = primary_keys.map { |key| key.to_s } # rubocop:disable Style/SymbolProc ignored = @ignore_policy.attributes_for(path) structural = @structural_columns.fetch(path, []) (primary_keys + ignored + relationship_columns(reflections) + structural).uniq.freeze end |
#call(record, reifier:) ⇒ RecordSnapshot?
: (untyped, reifier: untyped) -> RecordSnapshot?
60 61 62 63 |
# File 'lib/paper_trail_diff/snapshot_normalizer.rb', line 60 def call(record, reifier:) reflections = reflections_for(record, @tree, '') normalize_record(record, @tree, '', reifier, reflections) end |
#call_child(record, tree:, path:, incoming:, reifier:) ⇒ RecordSnapshot?
Normalizes one member of an already selected association branch. : (untyped, tree: AssociationTree, path: String, incoming: untyped, reifier: untyped) -> RecordSnapshot?
67 68 69 70 71 72 73 74 |
# File 'lib/paper_trail_diff/snapshot_normalizer.rb', line 67 def call_child(record, tree:, path:, incoming:, reifier:) return unless record @structural_columns[path] ||= @traversal.incoming_relationship_columns(incoming) reflections = reflections_for(record, tree, path) reifier.reify(record, reflections) unless reflections.empty? normalize_record(record, tree, path, reifier, reflections) end |
#excluded_attributes(record, reflections, path) ⇒ Array[String]
116 117 118 119 120 |
# File 'lib/paper_trail_diff/snapshot_normalizer.rb', line 116 def excluded_attributes(record, reflections, path) model_class = record.class @excluded_attributes[[model_class, path]] ||= build_excluded_attributes(model_class, reflections, path) end |
#normalize_associations(record, tree, path, reifier, reflections) ⇒ Hash[String, AssociationSnapshot]
: (untyped, AssociationTree, String, untyped, Array) -> Hash[String, AssociationSnapshot]
143 144 145 146 147 148 149 150 151 |
# File 'lib/paper_trail_diff/snapshot_normalizer.rb', line 143 def normalize_associations(record, tree, path, reifier, reflections) reflections.to_h do |reflection| name = reflection.name.to_s subtree = tree.child(name) raise ConfigurationError, "missing traversal subtree: #{name}" unless subtree [name, association_snapshot(record, reflection, subtree, path, reifier)] end end |
#normalize_children(records, tree, path, reifier) ⇒ Array[RecordSnapshot]
177 178 179 180 181 182 183 184 185 |
# File 'lib/paper_trail_diff/snapshot_normalizer.rb', line 177 def normalize_children(records, tree, path, reifier) reflections_by_class = {} #: Hash[String, Array[untyped]] records.filter_map do |child| type = child.class.name reflections = reflections_by_class[type] ||= reflections_for(child, tree, path) reifier.reify(child, reflections) unless reflections.empty? normalize_record(child, tree, path, reifier, reflections) end end |
#normalize_record(record, tree, path, reifier, reflections) ⇒ RecordSnapshot?
: (untyped, AssociationTree, String, untyped, Array) -> RecordSnapshot?
93 94 95 96 97 98 99 100 101 102 103 |
# File 'lib/paper_trail_diff/snapshot_normalizer.rb', line 93 def normalize_record(record, tree, path, reifier, reflections) return unless record snapshot = RecordSnapshot.new( type: record.class.name, id: record.id, attributes: normalized_attributes(record, reflections, path), associations: normalize_associations(record, tree, path, reifier, reflections) ) path.empty? ? snapshot : @pool.record(path, snapshot) end |
#normalized_attributes(record, reflections, path) ⇒ snapshot_attributes
: (untyped, Array, String) -> snapshot_attributes
106 107 108 109 110 |
# File 'lib/paper_trail_diff/snapshot_normalizer.rb', line 106 def normalized_attributes(record, reflections, path) attributes = record.attributes.dup excluded_attributes(record, reflections, path).each { |name| attributes.delete(name) } attributes end |
#reflections_for(record, tree, path) ⇒ Array[untyped]
: (untyped, AssociationTree, String) -> Array
188 189 190 191 192 |
# File 'lib/paper_trail_diff/snapshot_normalizer.rb', line 188 def reflections_for(record, tree, path) return [] unless record @traversal.reflections_for(record.class, tree, path: path) end |
#relationship_columns(reflections) ⇒ Array[String]
134 135 136 137 138 139 140 |
# File 'lib/paper_trail_diff/snapshot_normalizer.rb', line 134 def relationship_columns(reflections) reflections.select { |reflection| reflection.macro == :belongs_to }.flat_map do |reflection| columns = [reflection.foreign_key.to_s] columns << reflection.foreign_type.to_s if reflection.polymorphic? columns end end |