module Mongoid
module Criterion
module EagerLoading
# EagerLoading criterion are used when eager loading the associations.
#
# Example:
#
# criteria.includes(:user)
#
# criteria.includes(:user, :post)
#
# Returns: self
attr_accessor :eager_loadings, :id_document_map, :id_associations_map
def includes(*options)
@eager_loadings = options
self
end
def preload(documents)
document_class = documents.first.class
@eager_loadings.each do |eager_loading|
setup_associations(documents, association_reflection(document_class, eager_loading))
end
end
private
def ignore_includes
@eager_loadings = nil
end
def association_reflection(document_class, eager_loading)
document_class.reflect_on_association(eager_loading)
end
def setup_associations(documents, reflection)
if reflection.association == Mongoid::Associations::ReferencesOne
setup_associations_with_ids(documents, reflection, true)
elsif reflection.association == Mongoid::Associations::ReferencesMany
setup_associations_with_ids(documents, reflection, false)
elsif reflection.association == Mongoid::Associations::ReferencesManyAsArray
setup_associations_with_foreign_keys(documents, reflection, false)
elsif reflection.association == Mongoid::Associations::ReferencedIn
setup_associations_with_foreign_keys(documents, reflection, true)
end
end
def setup_associations_with_ids(documents, reflection, one=true)
ids = []
documents.each do |document|
id_document_map[document.id] = document
ids << document.id if document.id
end
association_class = reflection.name.singularize.camelize.constantize
ignore_includes
eager_associations = association_class.where(reflection.foreign_key.to_sym.in => ids).to_a
eager_associations.each do |eager_association|
add_id_association(eager_association.send(reflection.foreign_key), eager_association)
end
id_document_map.each do |id, document|
document.send("#{reflection.name}=", one ? id_associations_map[id].first : id_associations_map[id])
end
end
def setup_associations_with_foreign_keys(documents, reflection, one)
ids = []
foreign_key_name = reflection.foreign_key
documents.each do |document|
foreign_key_value = document.send(foreign_key_name)
if one
id_document_map[foreign_key_value] = document
ids << foreign_key_value if foreign_key_value
elsif foreign_key_value
foreign_key_value.each do |fkv|
id_document_map[fkv] = document
ids << fkv if fkv
end
end
end
association_class = reflection.name.singularize.camelize.constantize
ignore_includes
eager_associations = association_class.find(ids).to_a
eager_associations.each do |eager_association|
add_id_association(eager_association.id, eager_association)
end
id_document_map.each do |id, document|
foreign_key_value = document.send(foreign_key_name)
associations = \
if one
id_associations_map[foreign_key_value].first
else
foreign_key_value.collect { |fkv| id_associations_map[fkv] }.flatten.uniq
end
document.send("#{reflection.name}=", associations)
end
end
def id_document_map
@id_doccument_map ||= {}
end
def id_associations_map
@id_associations_map ||= {}
end
def add_id_association(id, association)
id_associations_map[id] ||= []
id_associations_map[id] << association
end
end
end
end