Module: Mongoid::Association::EagerLoadable
| Relationships & Source Files | |
| Extension / Inclusion / Inheritance Descendants | |
|
Included In:
| |
| Defined in: | lib/mongoid/association/eager_loadable.rb |
Overview
This module defines the eager loading behavior for criteria.
Instance Attribute Summary
-
#eager_loadable? ⇒ true | false
readonly
Indicates whether the criteria has association inclusions which should be eager loaded.
Instance Method Summary
- #create_pipeline(current_assoc, mapping)
-
#eager_load(docs) ⇒ Array<Mongoid::Document>
Load the associations for the given documents.
-
#eager_load_with_lookup ⇒ Array<Mongoid::Document>
Load the associations for the given documents using $lookup.
-
#preload(associations, docs)
Load the associations for the given documents.
-
#preload_for_lookup(criteria)
Load the associations for the given documents.
- #switch_local_and_foreign_fields?(association) ⇒ Boolean
Instance Attribute Details
#eager_loadable? ⇒ true | false (readonly)
Indicates whether the criteria has association inclusions which should be eager loaded.
# File 'lib/mongoid/association/eager_loadable.rb', line 16
def eager_loadable? !criteria.inclusions.empty? end
Instance Method Details
#create_pipeline(current_assoc, mapping)
[ GitHub ]# File 'lib/mongoid/association/eager_loadable.rb', line 117
def create_pipeline(current_assoc, mapping) # Build nested pipeline for children and ordering pipeline_stages = [] # For belongs_to and has_and_belongs_to_many, the foreign key is on the current document # For has_many/has_one, the foreign key is on the related document if switch_local_and_foreign_fields?(current_assoc) local_field = current_assoc.foreign_key foreign_field = current_assoc.primary_key else local_field = current_assoc.primary_key foreign_field = current_assoc.foreign_key end # Build the 'as' field with embedded path prefix if needed as_field = current_assoc.name.to_s stage = { "$lookup" => { "from" => current_assoc.klass.collection.name, "localField" => local_field, "foreignField" => foreign_field, "as" => as_field } } # Add ordering if defined on the association, or default to _id for consistent order if current_assoc.order sort_spec = current_assoc.order.is_a?(Hash) ? current_assoc.order : { current_assoc.order => 1 } pipeline_stages << { "$sort" => sort_spec } else # Default to sorting by _id to maintain insertion order consistency pipeline_stages << { "$sort" => { "_id" => 1 } } end # Add nested lookups for child associations # Child associations don't need the embedded_path prefix since they're referenced from the looked-up document # Remove this class from the mapping to prevent infinite loops with circular references class_name = current_assoc.klass.to_s if child_assocs = mapping.delete(class_name) child_assocs.each do |child| pipeline_stages << create_pipeline(child, mapping) end end # Always add pipeline since we always have at least $sort stage["$lookup"]["pipeline"] = pipeline_stages stage end
#eager_load(docs) ⇒ Array<Mongoid::Document>
Load the associations for the given documents.
# File 'lib/mongoid/association/eager_loadable.rb', line 25
def eager_load(docs) docs.tap do |d| if eager_loadable? preload(criteria.inclusions, d) end end end
#eager_load_with_lookup ⇒ Array<Mongoid::Document>
Load the associations for the given documents using $lookup.
# File 'lib/mongoid/association/eager_loadable.rb', line 36
def eager_load_with_lookup preload_for_lookup(criteria) end
#preload(associations, docs)
Load the associations for the given documents. This will be done recursively to load the associations of the given documents’ associated documents.
# File 'lib/mongoid/association/eager_loadable.rb', line 47
def preload(associations, docs) assoc_map = associations.group_by(&:inverse_class_name) docs_map = {} queue = [ klass.to_s ] # account for single-collection inheritance queue.push(klass.root_class.to_s) if klass != klass.root_class while klass = queue.shift if as = assoc_map.delete(klass) as.each do |assoc| queue << assoc.class_name # If this class is nested in the inclusion tree, only load documents # for the association above it. If there is no parent association, # we will include documents from the documents passed to this method. ds = docs if assoc.parent_inclusions.length > 0 ds = assoc.parent_inclusions.map{ |p| docs_map[p].to_a }.flatten end res = assoc.relation.eager_loader([assoc], ds).run docs_map[assoc.name] ||= [].to_set docs_map[assoc.name].merge(res) end end end end
#preload_for_lookup(criteria)
Load the associations for the given documents. This will be done recursively to load the associations of the given documents’ associated documents.
# File 'lib/mongoid/association/eager_loadable.rb', line 84
def preload_for_lookup(criteria) assoc_map = criteria.inclusions.group_by(&:inverse_class_name) # match first pipeline = criteria.selector.to_pipeline # then sort, skip, limit pipeline.concat(criteria..to_pipeline_for_lookup) # account for single-collection inheritance root_class = klass.root_class if assoc_map[klass.to_s] assoc_map[klass.to_s].each do |assoc| # Create a copy of the mapping for each top-level association to avoid mutation issues pipeline << create_pipeline(assoc, assoc_map.dup) end end if klass != root_class && assoc_map[root_class.to_s] assoc_map[root_class.to_s].each do |assoc| # Create a copy of the mapping for each top-level association to avoid mutation issues pipeline << create_pipeline(assoc, assoc_map.dup) end end Eager.new(criteria.inclusions, [], true, pipeline).run end
#switch_local_and_foreign_fields?(association) ⇒ Boolean
# File 'lib/mongoid/association/eager_loadable.rb', line 112
def switch_local_and_foreign_fields?(association) association.is_a?(Mongoid::Association::Referenced::BelongsTo) || association.is_a?(Mongoid::Association::Referenced::HasAndBelongsToMany) end