Class: ActiveRecord::Duplicator::AssociationTraversal
- Inherits:
-
Object
- Object
- ActiveRecord::Duplicator::AssociationTraversal
- Includes:
- Enumerable
- Defined in:
- lib/active_record/duplicator/association_traversal.rb,
sig/generated/active_record/duplicator/association_traversal.rbs
Overview
Breadth-first traversal of associations declared with preload-style syntax. Yields ActiveRecord::Relation objects in the order they would be needed to duplicate records level by level.
Example:
traversal = ActiveRecord::Duplicator::AssociationTraversal.new(
owner,
[items: :taggings],
)
traversal.each do |relation|
# 1st: Relation of items belonging to owner
# 2nd: Relation of taggings belonging to those items
end
Instance Method Summary collapse
-
#build_relation(klass, reflection, records) ⇒ ActiveRecord::Relation
Resolves the intermediate through_reflection first, then replaces it with the source_reflection.
-
#collect_ids(records, columns) ⇒ Array[untyped]
Avoid instantiating ActiveRecord objects just to read ids: use pluck for Relations and map for plain arrays.
-
#each {|arg0| ... } ⇒ Object
: () { (ActiveRecord::Relation) -> void } -> self : () -> Enumerator[ActiveRecord::Relation, self].
-
#find_reflection(klass, association) ⇒ Object
: (Class, Symbol | String) -> untyped.
-
#initialize(record, associations) ⇒ AssociationTraversal
constructor
: (untyped record, untyped associations) -> void.
-
#traverse(klass, association, records) {|arg0| ... } ⇒ void
: (Class, untyped, untyped) { (ActiveRecord::Relation) -> void } -> void.
-
#traverse_hash(klass, association, records) {|arg0| ... } ⇒ void
: (Class, Hash[Symbol | String, untyped], untyped) { (ActiveRecord::Relation) -> void } -> void.
-
#traverse_leaf(klass, association, records) {|arg0| ... } ⇒ void
: (Class, Symbol | String, untyped) { (ActiveRecord::Relation) -> void } -> void.
-
#where_key(columns) ⇒ Symbol, ...
Rails accepts either a scalar column name or an Array of column names as the LHS of a WHERE clause.
Constructor Details
#initialize(record, associations) ⇒ AssociationTraversal
: (untyped record, untyped associations) -> void
25 26 27 28 |
# File 'lib/active_record/duplicator/association_traversal.rb', line 25 def initialize(record, associations) @record = record @associations = associations end |
Instance Method Details
#build_relation(klass, reflection, records) ⇒ ActiveRecord::Relation
Resolves the intermediate through_reflection first, then replaces it with the source_reflection. For has_many/has_one it looks up children by matching the parent's primary key against the child's foreign key. For belongs_to it looks up the parent by matching the child's foreign key against the parent's primary key.
For composite primary keys the foreign_key / active_record_primary_key of a
reflection is an Array of column names; the corresponding WHERE clause uses
tuple syntax like where([:a, :b] => [[v1, v2], [v3, v4]]).
: (Class, untyped, untyped) -> ActiveRecord::Relation
83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/active_record/duplicator/association_traversal.rb', line 83 def build_relation(klass, reflection, records) if reflection.through_reflection? records = build_relation(reflection.through_reflection.klass, reflection.through_reflection, records) reflection = reflection.source_reflection end if reflection.collection? || reflection.has_one? lookup_columns = Array.wrap(reflection.active_record_primary_key) filter_columns = Array.wrap(reflection.foreign_key) elsif reflection.belongs_to? lookup_columns = Array.wrap(reflection.foreign_key) filter_columns = Array.wrap(reflection.active_record_primary_key) else raise NotImplementedError, "reflection kind #{reflection.class.name} is not supported" end ids = collect_ids(records, lookup_columns) klass.where(where_key(filter_columns) => ids) end |
#collect_ids(records, columns) ⇒ Array[untyped]
Avoid instantiating ActiveRecord objects just to read ids:
use pluck for Relations and map for plain arrays. For composite pk
returns an Array of Arrays ([[v1, v2], ...]); for single-column pk
returns a flat Array ([v1, v2, ...]).
: (untyped, Array[Symbol | String]) -> Array
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/active_record/duplicator/association_traversal.rb', line 124 def collect_ids(records, columns) if records.is_a?(ActiveRecord::Relation) if columns.length == 1 records.pluck(columns.first) else records.pluck(*columns) end else records.map do |r| if columns.length == 1 r.public_send(columns.first) else columns.map { |c| r.public_send(c) } end end end end |
#each ⇒ self #each ⇒ Enumerator[ActiveRecord::Relation, self]
: () { (ActiveRecord::Relation) -> void } -> self : () -> Enumerator[ActiveRecord::Relation, self]
32 33 34 35 36 37 38 39 |
# File 'lib/active_record/duplicator/association_traversal.rb', line 32 def each(&) return enum_for(:each) unless block_given? Array.wrap(@associations).each do |association| traverse(@record.class, association, Array.wrap(@record), &) end self end |
#find_reflection(klass, association) ⇒ Object
: (Class, Symbol | String) -> untyped
104 105 106 107 108 109 |
# File 'lib/active_record/duplicator/association_traversal.rb', line 104 def find_reflection(klass, association) reflection = klass.reflect_on_association(association) raise ArgumentError, "#{klass.name} has no association #{association}" if reflection.nil? reflection end |
#traverse(klass, association, records) {|arg0| ... } ⇒ void
This method returns an undefined value.
: (Class, untyped, untyped) { (ActiveRecord::Relation) -> void } -> void
44 45 46 47 48 49 50 51 52 53 |
# File 'lib/active_record/duplicator/association_traversal.rb', line 44 def traverse(klass, association, records, &) case association when Hash traverse_hash(klass, association, records, &) when Symbol, String traverse_leaf(klass, association, records, &) else raise ArgumentError, "#{association.inspect} is not a valid association form" end end |
#traverse_hash(klass, association, records) {|arg0| ... } ⇒ void
This method returns an undefined value.
: (Class, Hash[Symbol | String, untyped], untyped) { (ActiveRecord::Relation) -> void } -> void
62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/active_record/duplicator/association_traversal.rb', line 62 def traverse_hash(klass, association, records, &) association.each do |parent, children| reflection = find_reflection(klass, parent) relation = build_relation(reflection.klass, reflection, records) yield relation Array.wrap(children).each do |child| traverse(relation.klass, child, relation, &) end end end |
#traverse_leaf(klass, association, records) {|arg0| ... } ⇒ void
This method returns an undefined value.
: (Class, Symbol | String, untyped) { (ActiveRecord::Relation) -> void } -> void
56 57 58 59 |
# File 'lib/active_record/duplicator/association_traversal.rb', line 56 def traverse_leaf(klass, association, records) reflection = find_reflection(klass, association) yield build_relation(reflection.klass, reflection, records) end |
#where_key(columns) ⇒ Symbol, ...
Rails accepts either a scalar column name or an Array of column names
as the LHS of a WHERE clause. For single-column pk we hand it a scalar
so the generated SQL stays as WHERE col IN (...).
: (Array[Symbol | String]) -> (Symbol | String | Array[Symbol | String])
115 116 117 |
# File 'lib/active_record/duplicator/association_traversal.rb', line 115 def where_key(columns) columns.length == 1 ? columns.first : columns end |