Skip to content

Commit 2fbc9a8

Browse files
committed
Mark eager-load internals @api private and restore Eager's public API
The new eager-load classes are implementation detail, so mark them @api private, and add YARD to the public methods that lacked it. Removing parameters from Eager#initialize changed its public API. Keep the use_lookup/pipeline parameters and the $lookup branch in #run, and have preload_for_lookup delegate to a new Eager.run class method that wraps new(...).run. Update the eager_spec warning expectation for the new cluster/database message.
1 parent abb8f2d commit 2fbc9a8

9 files changed

Lines changed: 109 additions & 4 deletions

File tree

lib/mongoid/association/eager.rb

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,15 @@ module Mongoid
44
module Association
55
# Base class for eager load preload functions.
66
class Eager
7+
# Build a preloader for the given arguments and run it.
8+
#
9+
# @param (see #initialize)
10+
#
11+
# @return [ Array ] The list of documents given.
12+
def self.run(associations, docs, use_lookup = false, pipeline = [])
13+
new(associations, docs, use_lookup, pipeline).run
14+
end
15+
716
# Instantiate the eager load class.
817
#
918
# @example Create the new belongs to eager load preloader.
@@ -12,12 +21,18 @@ class Eager
1221
# @param [ Array<Mongoid::Association::Relatable> ] associations
1322
# Associations to eager load
1423
# @param [ Array<Document> ] docs Documents to preload the associations
24+
# @param [ Boolean ] use_lookup Whether to use $lookup aggregation
25+
# for eager loading. This is used in Criteria#eager_load.
26+
# @param [ Array<Hash> ] pipeline The aggregation pipeline to use
27+
# when using $lookup for eager loading.
1528
#
1629
# @return [ Base ] The eager load preloader
17-
def initialize(associations, docs)
30+
def initialize(associations, docs, use_lookup = false, pipeline = [])
1831
@associations = associations
1932
@docs = docs
2033
@grouped_docs = {}
34+
@use_lookup = use_lookup
35+
@pipeline = pipeline
2136
end
2237

2338
# Run the preloader.
@@ -29,6 +44,12 @@ def initialize(associations, docs)
2944
def run
3045
@loaded = []
3146

47+
if @use_lookup
48+
preload_with_lookup
49+
@loaded = @docs
50+
return @loaded.flatten
51+
end
52+
3253
while shift_association
3354
preload
3455
@loaded << @docs.collect { |d| d.send(@association.name) if d.respond_to?(@association.name) }
@@ -48,6 +69,21 @@ def preload
4869
raise NotImplementedError
4970
end
5071

72+
# Preload the current association using $lookup aggregation.
73+
# This method executes the aggregation pipeline
74+
# and instantiates the documents.
75+
# @example Preload the current association using $lookup.
76+
# loader.preload_with_lookup
77+
def preload_with_lookup
78+
# For $lookup aggregation, execute pipeline and instantiate documents
79+
owner_class = @associations.first.owner_class
80+
aggregated_docs = owner_class.collection.aggregate(@pipeline)
81+
aggregated_docs.each do |doc|
82+
parsed_doc = Factory.from_db(owner_class, doc)
83+
@docs << parsed_doc
84+
end
85+
end
86+
5187
# Retrieves the documents referenced by the association, and
5288
# yields each one sequentially to the provided block. If the
5389
# association is not polymorphic, all documents are retrieved in

lib/mongoid/association/eager_load/discriminated_inclusion.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ module EagerLoad
2424
# ], 'default' => [] } }
2525
# } },
2626
# { '$unset' => [ '__eager_load_widgets_Lathe', '__eager_load_widgets_Press' ] }
27+
#
28+
# @api private
2729
class DiscriminatedInclusion < Inclusion
2830
def initialize(nodes)
2931
super()

lib/mongoid/association/eager_load/embedded_distributor.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ module EagerLoad
3131
# ] }
3232
# } },
3333
# { '$unset' => '__eager_load_port_device' } # drop the temp field
34+
#
35+
# @api private
3436
class EmbeddedDistributor
3537
# @param [ Mongoid::Association::Relatable ] association The referenced
3638
# inclusion being eager-loaded from within an embedded document.

lib/mongoid/association/eager_load/inclusion.rb

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ module EagerLoad
88
# tree follows by recursion. AssociationInclusion stands for a single
99
# association; DiscriminatedInclusion stands for a name several subclasses
1010
# share.
11+
#
12+
# @api private
1113
class Inclusion
1214
# Add this inclusion's stages to the destination.
1315
#
@@ -23,6 +25,8 @@ def contribute(destination, chain)
2325
# An inclusion that stands for a single association. The LookupPipeline holds
2426
# the stage-building helpers the kinds lean on, and a node carries its own
2527
# children, so the pipeline is built by recursion from the roots downward.
28+
#
29+
# @api private
2630
class AssociationInclusion < Inclusion
2731
class << self
2832
# Builds the right kind of inclusion for the association. Each subclass
@@ -39,6 +43,8 @@ def for(association, pipeline, children)
3943

4044
# Whether this kind handles the given association.
4145
#
46+
# @param [ Mongoid::Association::Relatable ] association The inclusion.
47+
#
4248
# @return [ true | false ] Whether it handles it.
4349
def for?(association)
4450
raise NotImplementedError
@@ -75,15 +81,28 @@ def initialize(association, pipeline, children)
7581
# <children>
7682
# ]
7783
# } }
84+
#
85+
# @api private
7886
class JoinedInclusion < AssociationInclusion
7987
class << self
8088
# The default kind: a referenced, non-polymorphic association, i.e. the
8189
# one no sibling kind claims.
90+
#
91+
# @param [ Mongoid::Association::Relatable ] association The inclusion.
92+
#
93+
# @return [ true | false ] Whether it handles it.
8294
def for?(association)
8395
(superclass.subclasses - [ self ]).none? { |kind| kind.for?(association) }
8496
end
8597
end
8698

99+
# Append the $lookup, with the children in its sub-pipeline, to the
100+
# destination; or distribute it onto the embedded path when nested in one.
101+
#
102+
# @param [ Array<Hash> ] destination The pipeline (or sub-pipeline) the
103+
# stages are appended to.
104+
# @param [ Array<Mongoid::Association::Relatable> ] chain The embedded path
105+
# accumulated from the ancestors above this inclusion (empty at the top).
87106
def contribute(destination, chain)
88107
stage = @pipeline.lookup_stage_for(@association)
89108
@children.each { |child| child.contribute(stage['$lookup']['pipeline'], []) }
@@ -103,13 +122,25 @@ def contribute(destination, chain)
103122
# For Computer.eager_load(port: :device) the :port inclusion emits nothing;
104123
# it hands the path [ :port ] to :device, which EmbeddedDistributor then
105124
# turns into stages.
125+
#
126+
# @api private
106127
class EmbeddedInclusion < AssociationInclusion
107128
class << self
129+
# @param [ Mongoid::Association::Relatable ] association The inclusion.
130+
#
131+
# @return [ true | false ] Whether the association is embedded.
108132
def for?(association)
109133
association.embedded?
110134
end
111135
end
112136

137+
# Add no stage of its own; hand this document down the embedded path so the
138+
# children distribute onto it.
139+
#
140+
# @param [ Array<Hash> ] destination The pipeline (or sub-pipeline) the
141+
# stages are appended to.
142+
# @param [ Array<Mongoid::Association::Relatable> ] chain The embedded path
143+
# accumulated from the ancestors above this inclusion (empty at the top).
113144
def contribute(destination, chain)
114145
@children.each { |child| child.contribute(destination, chain + [ @association ]) }
115146
end
@@ -118,13 +149,23 @@ def contribute(destination, chain)
118149
# A polymorphic inclusion: its target collection varies per document, so it
119150
# can't be a $lookup. It adds nothing here; PolymorphicPreloader resolves it
120151
# after the roots are materialized.
152+
#
153+
# @api private
121154
class DeferredInclusion < AssociationInclusion
122155
class << self
156+
# @param [ Mongoid::Association::Relatable ] association The inclusion.
157+
#
158+
# @return [ true | false ] Whether the association is polymorphic.
123159
def for?(association)
124160
association.polymorphic?
125161
end
126162
end
127163

164+
# Add nothing; PolymorphicPreloader resolves the association after the
165+
# roots are materialized.
166+
#
167+
# @param [ Array<Hash> ] destination The pipeline (unused).
168+
# @param [ Array<Mongoid::Association::Relatable> ] chain The embedded path (unused).
128169
def contribute(destination, chain); end
129170
end
130171
end

lib/mongoid/association/eager_load/inclusion_tree.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,16 @@ module EagerLoad
1414
# inclusion is removed as it is placed, so it lands once per branch even if
1515
# more than one parent in that branch points at it, and a circular chain of
1616
# inclusions can't loop forever.
17+
#
18+
# @api private
1719
class InclusionTree
1820
class << self
21+
# Builds the tree for the criteria's inclusions.
22+
#
23+
# @param [ Array<Mongoid::Association::Relatable> ] inclusions The inclusions.
24+
# @param [ LookupPipeline ] pipeline The pipeline being built.
25+
#
26+
# @return [ InclusionTree ] The tree.
1927
def from(inclusions, pipeline)
2028
new(inclusions, pipeline, inclusions.to_h { |association| [ association.name, association ] })
2129
end

lib/mongoid/association/eager_load/lookup_pipeline.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ module EagerLoad
3636
# } }
3737
# ]
3838
# } } ]
39+
#
40+
# @api private
3941
class LookupPipeline
4042
def initialize(criteria)
4143
@criteria = criteria

lib/mongoid/association/eager_load/polymorphic_preloader.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ module EagerLoad
1111
# collection varies per document. So once the roots are materialized, the
1212
# foreign keys are grouped by type, PolymorphicTargets resolves the documents
1313
# for those keys, and the result is set on each document.
14+
#
15+
# @api private
1416
class PolymorphicPreloader
1517
def initialize(association, root_class)
1618
@association = association

lib/mongoid/association/eager_load/polymorphic_targets.rb

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,19 @@ module EagerLoad
77
# { type => { primary_key => document } }. Each subclass reaches the types that
88
# live in one place (the root's database or elsewhere); .for resolves the whole
99
# set, routing each type to the subclass that can reach it.
10+
#
11+
# @api private
1012
class PolymorphicTargets
1113
class << self
1214
# Resolve every polymorphic target for the foreign keys grouped by type.
1315
# The types whose documents share the root's database are fetched together
1416
# in one $facet; those living elsewhere are read through their own models.
15-
# Returns them indexed as { type => { primary_key => document } }.
17+
#
18+
# @param [ Mongoid::Association::Relatable ] association The polymorphic inclusion.
19+
# @param [ Hash ] keys_by_type The foreign keys grouped by type.
20+
# @param [ Class ] root_class The class being queried.
21+
#
22+
# @return [ Hash ] The targets, as { type => { primary_key => document } }.
1623
def for(association, keys_by_type, root_class)
1724
here, elsewhere = keys_by_type.partition do |type, _keys|
1825
in_root_database?(association, type, root_class)
@@ -73,12 +80,15 @@ def indexed(documents, model)
7380
# 'Scanner' => [ { '$lookup' => { 'from' => 'scanners', ... } }, ... ]
7481
# } }
7582
# ]
83+
#
84+
# @api private
7685
class SameDatabaseTargets < PolymorphicTargets
7786
def initialize(association, keys_by_type, root_class)
7887
super(association, keys_by_type)
7988
@root_class = root_class
8089
end
8190

91+
# @return [ Hash ] The targets, as { type => { primary_key => document } }.
8292
def fetch
8393
return {} if @keys_by_type.empty?
8494

@@ -124,7 +134,10 @@ def branch_for(collection_name, keys)
124134
#
125135
# For { 'Scanner' => [ id2 ] } it runs, on the Scanner model's own client:
126136
# scanners.find('_id' => { '$in' => [ id2 ] })
137+
#
138+
# @api private
127139
class OtherDatabaseTargets < PolymorphicTargets
140+
# @return [ Hash ] The targets, as { type => { primary_key => document } }.
128141
def fetch
129142
@keys_by_type.to_h do |type, keys|
130143
model = model_for(type)

lib/mongoid/association/eager_loadable.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,7 @@ def preload(associations, docs)
119119
# @return [ Array<Mongoid::Document> ] The materialized root documents.
120120
def preload_for_lookup(criteria)
121121
pipeline = EagerLoad::LookupPipeline.new(criteria).stages
122-
owner = criteria.inclusions.first.owner_class
123-
owner.collection.aggregate(pipeline).map { |document| Factory.from_db(owner, document) }
122+
Eager.run(criteria.inclusions, [], true, pipeline)
124123
end
125124

126125
private

0 commit comments

Comments
 (0)