lib/mongoid/contextual/mongo.rb in mongoid-3.0.0.rc vs lib/mongoid/contextual/mongo.rb in mongoid-3.0.0

- old
+ new

@@ -31,10 +31,22 @@ def blank? count == 0 end alias :empty? :blank? + # Is the context cached? + # + # @example Is the context cached? + # context.cached? + # + # @return [ true, false ] If the context is cached. + # + # @since 3.0.0 + def cached? + !!@cache + end + # Get the number of documents matching the query. # # @example Get the number of matching documents. # context.count # @@ -112,29 +124,19 @@ # end # # @return [ Enumerator ] The enumerator. # # @since 3.0.0 - def each + def each(&block) if block_given? reset_length selecting do - if eager_loadable? - docs = query.map{ |doc| Factory.from_db(klass, doc) } - eager_load(docs) - docs.each do |doc| - yield doc - increment_length - end - docs - else - query.each do |doc| - yield Factory.from_db(klass, doc) - increment_length - end - self + documents_for_iteration.each do |doc| + yield_and_increment(doc, &block) end + @cache_loaded = true + eager_loadable? ? docs : self end else to_enum end end @@ -172,10 +174,11 @@ # @param [ Hash ] update The updates. # @param [ Hash ] options The command options. # # @option options [ true, false ] :new Return the updated document. # @option options [ true, false ] :remove Delete the first document. + # @option options [ true, false ] :upsert Create the document if it doesn't exist. # # @return [ Document ] The result of the command. # # @since 3.0.0 def find_and_modify(update, options = {}) @@ -205,11 +208,11 @@ # # @param [ Criteria ] criteria The criteria. # # @since 3.0.0 def initialize(criteria) - @criteria, @klass = criteria, criteria.klass + @criteria, @klass, @cache = criteria, criteria.klass, criteria.options[:cache] add_type_selection @query = klass.collection.find(criteria.selector) apply_options end @@ -302,20 +305,20 @@ end # Update all the matching documents atomically. # # @example Update all the matching documents. - # context.update(name: "Smiths") + # context.update({ "$set" => { name: "Smiths" }}) # # @param [ Hash ] attributes The new attributes for each document. # # @return [ nil, false ] False if no attributes were provided. # # @since 3.0.0 def update(attributes = nil) return false unless attributes - query.update_all({ "$set" => attributes }) + query.update_all(attributes.__consolidate__) end alias :update_all :update private @@ -386,10 +389,22 @@ if spec = criteria.options[:sort] query.sort(spec) end end + # Apply the hint option + # + # @example Apply the hint params. + # context.apply_hint + # + # @since 3.0.0 + def apply_hint + if spec = criteria.options[:hint] + query.hint(spec) + end + end + # Map the inverse sort symbols to the correct MongoDB values. # # @example Apply the inverse sorting params. # context.apply_inverse_sorting # @@ -400,10 +415,81 @@ else query.sort({_id: -1}) end end + # Is the cache able to be added to? + # + # @api private + # + # @example Is the context cacheable? + # context.cacheable? + # + # @return [ true, false ] If caching, and the cache isn't loaded. + # + # @since 3.0.0 + def cacheable? + cached? && !cache_loaded? + end + + # Is the cache fully loaded? Will be true if caching after one full + # iteration. + # + # @api private + # + # @example Is the cache loaded? + # context.cache_loaded? + # + # @return [ true, false ] If the cache is loaded. + # + # @since 3.0.0 + def cache_loaded? + !!@cache_loaded + end + + # Get the documents for cached queries. + # + # @api private + # + # @example Get the cached documents. + # context.documents + # + # @return [ Array<Document> ] The documents. + # + # @since 3.0.0 + def documents + @documents ||= [] + end + + # Get the documents the context should iterate. This follows 3 rules: + # + # 1. If the query is cached, and we already have documents loaded, use + # them. + # 2. If we are eager loading, then eager load the documents and use + # those. + # 3. Use the query. + # + # @api private + # + # @example Get the documents for iteration. + # context.documents_for_iteration + # + # @return [ Array<Document>, Moped::Query ] The docs to iterate. + # + # @since 3.0.0 + def documents_for_iteration + if cached? && !documents.empty? + documents + elsif eager_loadable? + docs = query.map{ |doc| Factory.from_db(klass, doc) } + eager_load(docs) + docs + else + query + end + end + # Eager load the inclusions for the provided documents. # # @example Eager load the inclusions. # context.eager_load(docs) # @@ -412,33 +498,46 @@ # @return [ true ] Always true. # # @since 3.0.0 def eager_load(docs) criteria.inclusions.reject! do |metadata| - unless docs.empty? - if metadata.stores_foreign_key? - child_ids = load_ids(metadata.foreign_key).flatten - metadata.eager_load(child_ids) - else - parent_ids = docs.map(&:id) - metadata.eager_load(parent_ids) - end - end + metadata.eager_load(eager_loaded_ids(docs, metadata)) if !docs.empty? end self.eager_loaded = true end + # Get the ids that to be used to eager load documents. + # + # @api private + # + # @example Get the ids. + # context.eager_load(docs, metadata) + # + # @param [ Array<Document> ] docs The pre-loaded documents. + # @param [ Metadata ] metadata The relation metadata. + # + # @return [ Array<Object> ] The ids. + # + # @since 3.0.0 + def eager_loaded_ids(docs, metadata) + if metadata.stores_foreign_key? + load_ids(metadata.foreign_key).flatten + else + docs.map(&:id) + end + end + # Is this context able to be eager loaded? # # @example Is the context eager loadable? # context.eager_loadable? # # @return [ true, false ] If the context is able to be eager loaded. # # @since 3.0.0 def eager_loadable? - !eager_loaded && criteria.inclusions.any? + !eager_loaded && !criteria.inclusions.empty? end # Increment the length of the results. # # @api private @@ -473,11 +572,11 @@ # @example Load the related ids. # criteria.load_ids("person_id") # # @param [ String ] key The id or foriegn key string. # - # @return [ Array<String, BSON::ObjectId> ] The ids to load. + # @return [ Array<String, Moped::BSON::ObjectId> ] The ids to load. # # @since 3.0.0 def load_ids(key) query.select(key => 1).map do |doc| doc[key] @@ -493,10 +592,11 @@ def apply_options apply_fields apply_limit apply_skip apply_sorting + apply_hint end # If we are limiting results, we need to set the field limitations on a # thread local to avoid overriding the default values. # @@ -535,9 +635,28 @@ return nil unless document doc = Factory.from_db(klass, document) eager_load([ doc ]) if eager_loadable? doc end + end + + # Yield to the document and increment the length. + # + # @api private + # + # @example Yield and increment. + # context.yield_and_increment(doc) do |doc| + # ... + # end + # + # @param [ Document ] document The document to yield to. + # + # @since 3.0.0 + def yield_and_increment(document, &block) + doc = document.respond_to?(:_id) ? document : Factory.from_db(klass, document) + yield(doc) + increment_length + documents.push(doc) if cacheable? end end end end