Module: Mongoid::Association::EagerLoadable

Included in:
Contextual::Memory, Contextual::Mongo, Contextual::Mongo::DocumentsLoader
Defined in:
lib/mongoid/association/eager_loadable.rb

Overview

This module defines the eager loading behavior for criteria.

Instance Method Summary collapse

Instance Method Details

#eager_load(docs) ⇒ Array<Mongoid::Document>

Load the associations for the given documents.

Parameters:

Returns:



25
26
27
28
29
# File 'lib/mongoid/association/eager_loadable.rb', line 25

def eager_load(docs)
  docs.tap do |d|
    preload(criteria.inclusions, d) if eager_loadable?
  end
end

#eager_load_with_lookupArray<Mongoid::Document>

Load the associations for the given documents using $lookup.

If any of the associated collections reside in a different cluster or database than the root class, falls back to the #includes behavior and logs a warning.

Returns:



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/mongoid/association/eager_loadable.rb', line 38

def eager_load_with_lookup
  offenders = inclusions_unreachable_by_lookup
  if offenders.any?
    offender_descriptions = offenders.map do |offender|
      "#{offender.name} (client: #{offender.klass.client_name}, database: #{offender.klass.database_name})"
    end
    Mongoid.logger.warn(
      'eager_load cannot use $lookup aggregation because the following associations ' \
      "reside in a different cluster or database than #{klass} " \
      "(client: #{klass.client_name}, database: #{klass.database_name}): " \
      "#{offender_descriptions.join(', ')}. Falling back to #includes behavior."
    )
    return eager_load(docs_for_lookup_fallback)
  end

  through_inclusions = criteria.inclusions.select do |association|
    association.is_a?(Association::Referenced::HasOneThrough) ||
      association.is_a?(Association::Referenced::HasManyThrough)
  end

  if through_inclusions.any?
    through_names = through_inclusions.map { |association| ":#{association.name}" }
    Mongoid.logger.warn(
      "#{through_names.join(', ')} are :through associations and do not support " \
      '$lookup-based eager loading. All inclusions for this query will be preloaded ' \
      'using separate queries.'
    )
    return eager_load(docs_for_lookup_fallback)
  end

  documents = preload_for_lookup(criteria)
  # A polymorphic belongs_to cannot be expressed as a $lookup: its target
  # collection varies per document. It is resolved after the roots are
  # materialized, each inclusion by its own preloader.
  criteria.inclusions.select(&:polymorphic?).each do |association|
    EagerLoad::PolymorphicPreloader.new(association, klass).preload_into(documents)
  end
  documents
end

#eager_loadable?true | false

Indicates whether the criteria has association inclusions which should be eager loaded.

Returns:

  • (true | false)

    Whether to eager load.



16
17
18
# File 'lib/mongoid/association/eager_loadable.rb', line 16

def eager_loadable?
  !criteria.inclusions.empty?
end

#preload(associations, docs) ⇒ Object

Load the associations for the given documents. This will be done recursively to load the associations of the given documents' associated documents.

Parameters:



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/mongoid/association/eager_loadable.rb', line 85

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
    next unless 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
      ds = assoc.parent_inclusions.map { |p| docs_map[p].to_a }.flatten if assoc.parent_inclusions.length > 0

      res = assoc.relation.eager_loader([ assoc ], ds).run

      docs_map[assoc.name] ||= [].to_set
      docs_map[assoc.name].merge(res)
    end
  end
end

#preload_for_lookup(criteria) ⇒ Array<Mongoid::Document>

Materialize the root documents with their inclusions eager-loaded by a single $lookup aggregation. The pipeline is built by LookupPipeline; the polymorphic inclusions it leaves out are resolved by the caller.

Parameters:

Returns:



120
121
122
123
# File 'lib/mongoid/association/eager_loadable.rb', line 120

def preload_for_lookup(criteria)
  pipeline = EagerLoad::LookupPipeline.new(criteria).stages
  Eager.run(criteria.inclusions, [], true, pipeline)
end