Class: Mongoid::Association::EagerLoad::EmbeddedDistributor Private
| Relationships & Source Files | |
| Inherits: | Object |
| Defined in: | lib/mongoid/association/eager_load/embedded_distributor.rb |
Overview
Distributes the results of a $lookup onto the embedded documents they belong to.
A $lookup overwrites the single field it writes to, and it can't distribute its matches across the elements of an embedded array. So when the reference being eager-loaded lives on an embedded document, the matches are first collected in a temporary top-level field and then distributed down the embedded path onto each embedded document, merging into it (so the rest of the document is kept) and correlating by key. The temporary field is then dropped.
For Computer.eager_load(port: :device) (Port belongs_to :device) it emits:
{ '$lookup' => { # devices can't be written into
'from' => 'devices', # the embedded port, so they are
'localField' => 'port.device_id', # collected in a temp top-level
'foreignField' => '_id', # field instead
'as' => '__eager_load_port_device'
} },
{ '$set' => {
'port' => { '$mergeObjects' => [ # merge a 'device' key onto the port
'$port',
{ 'device' => { '$filter' => { ... } } } # this port's matches
] }
} },
{ '$unset' => '__eager_load_port_device' } # drop the temp field
Class Method Summary
- .for(association:, chain:, lookup_stage:) Internal use only
- .new(association, chain, lookup_stage, local_field, foreign_field) ⇒ EmbeddedDistributor constructor Internal use only
Instance Method Summary
-
#stages ⇒ Array<Hash>
Internal use only
The stages that run the $lookup into a temporary field and then distribute its matches onto the embedded documents along the path.
-
#correlated_matches(element)
private
Internal use only
The matches that belong to a single embedded element.
-
#distributed_value(chain, node)
private
Internal use only
An embedded collection (embeds_many) is rebuilt with $map so each element keeps its own matches instead of collapsing onto the first; a single embedded document (embeds_one) receives its matches in place.
-
#match_operator
private
Internal use only
A has_and_belongs_to_many holds an array of foreign keys, so a match belongs when its key is among them ($in); every other association points at a single key ($eq).
-
#merge_into_present(node, merged)
private
Internal use only
Merge the matches into a single embedded document only when it exists, so an absent embeds_one stays absent instead of being synthesized from its matches alone.
- #path private Internal use only
-
#redirect_lookup_to_temporary_field
private
Internal use only
The $lookup runs at the top level, so it reads the local field by its full embedded path and writes the matches into the temporary field.
- #root private Internal use only
- #temporary_field private Internal use only
Constructor Details
.new(association, chain, lookup_stage, local_field, foreign_field) ⇒ EmbeddedDistributor
# File 'lib/mongoid/association/eager_load/embedded_distributor.rb', line 53
def initialize(association, chain, lookup_stage, local_field, foreign_field) @association = association @chain = chain @lookup_stage = lookup_stage @local_field = local_field @foreign_field = foreign_field end
Class Method Details
.for(association:, chain:, lookup_stage:)
[ GitHub ]# File 'lib/mongoid/association/eager_load/embedded_distributor.rb', line 45
def for(association:, chain:, lookup_stage:) lookup = lookup_stage['$lookup'] new(association, chain, lookup_stage, lookup['localField'], lookup['foreignField']) end
Instance Method Details
#distributed_value(chain, node) (private)
An embedded collection (embeds_many) is rebuilt with $map so each element keeps its own matches instead of collapsing onto the first; a single embedded document (embeds_one) receives its matches in place.
# File 'lib/mongoid/association/eager_load/embedded_distributor.rb', line 101
def distributed_value(chain, node) head, *rest = chain many = head.many? element = many ? "$$#{head.store_as}" : node child = if rest.empty? { @association.name.to_s => (element) } else segment = rest.first.store_as { segment => distributed_value(rest, "#{element}.#{segment}") } end merged = { '$mergeObjects' => [ element, child ] } return merge_into_present(node, merged) unless many { '$map' => { 'input' => node, 'as' => head.store_as, 'in' => merged } } end
#match_operator (private)
A has_and_belongs_to_many holds an array of foreign keys, so a match belongs when its key is among them ($in); every other association points at a single key ($eq).
# File 'lib/mongoid/association/eager_load/embedded_distributor.rb', line 145
def match_operator @association.many_to_many? ? '$in' : '$eq' end
#merge_into_present(node, merged) (private)
Merge the matches into a single embedded document only when it exists, so an absent embeds_one stays absent instead of being synthesized from its matches alone.
# File 'lib/mongoid/association/eager_load/embedded_distributor.rb', line 125
def merge_into_present(node, merged) { '$cond' => { 'if' => { '$ifNull' => [ node, false ] }, 'then' => merged, 'else' => node } } end
#path (private)
[ GitHub ]# File 'lib/mongoid/association/eager_load/embedded_distributor.rb', line 86
def path @chain.map(&:store_as).join('.') end
#redirect_lookup_to_temporary_field (private)
The $lookup runs at the top level, so it reads the local field by its full embedded path and writes the matches into the temporary field.
# File 'lib/mongoid/association/eager_load/embedded_distributor.rb', line 80
def redirect_lookup_to_temporary_field lookup = @lookup_stage['$lookup'] lookup['localField'] = "#{path}.#{@local_field}" lookup['as'] = temporary_field end
#root (private)
[ GitHub ]# File 'lib/mongoid/association/eager_load/embedded_distributor.rb', line 90
def root @chain.first.store_as end
#stages ⇒ Array<Hash>
The stages that run the $lookup into a temporary field and then distribute its matches onto the embedded documents along the path.
# File 'lib/mongoid/association/eager_load/embedded_distributor.rb', line 65
def stages redirect_lookup_to_temporary_field [ @lookup_stage, { '$set' => { root => distributed_value(@chain, "$#{root}") } }, { '$unset' => temporary_field } ] end
#temporary_field (private)
[ GitHub ]# File 'lib/mongoid/association/eager_load/embedded_distributor.rb', line 94
def temporary_field "__eager_load_#{path.tr('.', '_')}_#{@association.name}" end