lib/mongoid/relations/targets/enumerable.rb in mongoid-2.8.1 vs lib/mongoid/relations/targets/enumerable.rb in mongoid-3.0.0.rc

- old
+ new

@@ -1,24 +1,24 @@ # encoding: utf-8 -module Mongoid #:nodoc: - module Relations #:nodoc: - module Targets #:nodoc: +module Mongoid + module Relations + module Targets # This class is the wrapper for all relational associations that have a - # target that can be a criteria or array of loaded documents. This + # target that can be a criteria or array of _loaded documents. This # handles both cases or a combination of the two. class Enumerable include ::Enumerable # The three main instance variables are collections of documents. # - # @attribute [rw] added Documents that have been appended. - # @attribute [rw] loaded Persisted documents that have been loaded. - # @attribute [rw] unloaded A criteria representing persisted docs. - attr_accessor :added, :loaded, :unloaded + # @attribute [rw] _added Documents that have been appended. + # @attribute [rw] _loaded Persisted documents that have been _loaded. + # @attribute [rw] _unloaded A criteria representing persisted docs. + attr_accessor :_added, :_loaded, :_unloaded - delegate :===, :is_a?, :kind_of?, :to => :added + delegate :===, :is_a?, :kind_of?, to: [] # Check if the enumerable is equal to the other object. # # @example Check equality. # enumerable == [] @@ -42,11 +42,12 @@ # # @return [ Document ] The document. # # @since 2.1.0 def <<(document) - added.push(document) + _added[document.id] = document + self end alias :push :<< # Clears out all the documents in this enumerable. If passed a block it # will yield to each document that is in memory. @@ -57,18 +58,18 @@ # @example Clear out the enumerable with a block. # enumerable.clear do |doc| # doc.unbind # end # - # @return [ Array<Document> ] The cleared out added docs. + # @return [ Array<Document> ] The cleared out _added docs. # # @since 2.1.0 def clear if block_given? in_memory { |doc| yield(doc) } end - loaded.clear and added.clear + _loaded.clear and _added.clear end # Clones each document in the enumerable. # # @note This loads all documents into memory. @@ -92,19 +93,19 @@ # # @return [ Document ] The deleted document. # # @since 2.1.0 def delete(document) - (loaded.delete(document) || added.delete(document)).tap do |doc| - unless doc - if unloaded && unloaded.where(:_id => document.id).exists? - yield(document) if block_given? - return document - end + doc = (_loaded.delete(document.id) || _added.delete(document.id)) + unless doc + if _unloaded && _unloaded.where(_id: document.id).exists? + yield(document) if block_given? + return document end - yield(doc) if block_given? end + yield(doc) if block_given? + doc end # Deletes every document in the enumerable for where the block returns # true. # @@ -118,66 +119,68 @@ # @return [ Array<Document> ] The remaining docs. # # @since 2.1.0 def delete_if(&block) load_all! - tap do - loaded.delete_if(&block) - added.delete_if(&block) + deleted = in_memory.select(&block) + deleted.each do |doc| + _loaded.delete(doc.id) + _added.delete(doc.id) end + self end # Iterating over this enumerable has to handle a few different # scenarios. # - # If the enumerable has its criteria loaded into memory then it yields - # to all the loaded docs and all the added docs. + # If the enumerable has its criteria _loaded into memory then it yields + # to all the _loaded docs and all the _added docs. # - # If the enumerable has not loaded the criteria then it iterates over + # If the enumerable has not _loaded the criteria then it iterates over # the cursor while loading the documents and then iterates over the - # added docs. + # _added docs. # # @example Iterate over the enumerable. # enumerable.each do |doc| # puts doc # end # - # @return [ true ] That the enumerable is now loaded. + # @return [ true ] That the enumerable is now _loaded. # # @since 2.1.0 def each - if loaded? - loaded.each do |doc| + if _loaded? + _loaded.each_pair do |id, doc| yield(doc) end else - unloaded.each do |doc| - document = added.delete_one(doc) || loaded.delete_one(doc) || doc + _unloaded.each do |doc| + document = _added.delete(doc.id) || _loaded.delete(doc.id) || doc yield(document) - loaded.push(document) + _loaded[document.id] = document end end - added.each do |doc| + _added.each_pair do |id, doc| yield(doc) end @executed = true end # Is the enumerable empty? Will determine if the count is zero based on - # whether or not it is loaded. + # whether or not it is _loaded. # # @example Is the enumerable empty? # enumerable.empty? # # @return [ true, false ] If the enumerable is empty. # # @since 2.1.0 def empty? - if loaded? + if _loaded? in_memory.count == 0 else - unloaded.count + added.count == 0 + _unloaded.count + _added.count == 0 end end # Get the first document in the enumerable. Will check the persisted # documents first. Does not load the entire enumerable. @@ -187,11 +190,11 @@ # # @return [ Document ] The first document found. # # @since 2.1.0 def first - added.first || (loaded? ? loaded.first : unloaded.first) + matching_document(:first) end # Initialize the new enumerable either with a criteria or an array. # # @example Initialize the enumerable with a criteria. @@ -203,16 +206,35 @@ # @param [ Criteria, Array<Document> ] target The wrapped object. # # @since 2.1.0 def initialize(target) if target.is_a?(Criteria) - @added, @loaded, @unloaded = [], [], target + @_added, @executed, @_loaded, @_unloaded = {}, false, {}, target else - @added, @executed, @loaded = [], true, target + @_added, @executed = {}, true + @_loaded = target.inject({}) do |_target, doc| + _target[doc.id] = doc + _target + end end end + # Does the target include the provided document? + # + # @example Does the target include the document? + # enumerable.include?(document) + # + # @param [ Document ] doc The document to check. + # + # @return [ true, false ] If the document is in the target. + # + # @since 3.0.0 + def include?(doc) + return super unless _unloaded + _unloaded.where(_id: doc.id).exists? || _added.has_key?(doc.id) + end + # Inspection will just inspect the entries for nice array-style # printing. # # @example Inspect the enumerable. # enumerable.inspect @@ -222,25 +244,25 @@ # @since 2.1.0 def inspect entries.inspect end - # Return all the documents in the enumerable that have been loaded or - # added. + # Return all the documents in the enumerable that have been _loaded or + # _added. # # @note When passed a block it yields to each document. # # @example Get the in memory docs. # enumerable.in_memory # # @return [ Array<Document> ] The in memory docs. # # @since 2.1.0 def in_memory - (loaded + added).tap do |docs| - docs.each { |doc| yield(doc) } if block_given? - end + docs = (_loaded.values + _added.values) + docs.each { |doc| yield(doc) } if block_given? + docs end # Get the last document in the enumerable. Will check the new # documents first. Does not load the entire enumerable. # @@ -249,33 +271,33 @@ # # @return [ Document ] The last document found. # # @since 2.1.0 def last - added.last || (loaded? ? loaded.last : unloaded.last) + matching_document(:last) end # Loads all the documents in the enumerable from the database. # # @example Load all the documents. # enumerable.load_all! # - # @return [ true ] That the enumerable is loaded. + # @return [ true ] That the enumerable is _loaded. # # @since 2.1.0 alias :load_all! :entries - # Has the enumerable been loaded? This will be true if the criteria has + # Has the enumerable been _loaded? This will be true if the criteria has # been executed or we manually load the entire thing. # - # @example Is the enumerable loaded? - # enumerable.loaded? + # @example Is the enumerable _loaded? + # enumerable._loaded? # - # @return [ true, false ] If the enumerable has been loaded. + # @return [ true, false ] If the enumerable has been _loaded. # # @since 2.1.0 - def loaded? + def _loaded? !!@executed end # Reset the enumerable back to it's persisted state. # @@ -284,11 +306,11 @@ # # @return [ false ] Always false. # # @since 2.1.0 def reset - loaded.clear and added.clear + _loaded.clear and _added.clear @executed = false end # Does this enumerable respond to the provided method? # @@ -314,15 +336,15 @@ # # @return [ Integer ] The size of the enumerable. # # @since 2.1.0 def size - count = (unloaded ? unloaded.count : loaded.count) + count = (_unloaded ? _unloaded.count : _loaded.count) if count.zero? - count + added.count + count + _added.count else - count + added.count{ |d| d.new_record? } + count + _added.values.count{ |d| d.new_record? } end end alias :length :size # Send #to_json to the entries. @@ -330,11 +352,11 @@ # @example Get the enumerable as json. # enumerable.to_json # # @param [ Hash ] options Optional parameters. # - # @return [ String ] The entries all loaded as a string. + # @return [ String ] The entries all _loaded as a string. # # @since 2.2.0 def to_json(options = {}) entries.to_json(options) end @@ -344,11 +366,11 @@ # @example Get the enumerable as json. # enumerable.as_json # # @param [ Hash ] options Optional parameters. # - # @return [ Hash ] The entries all loaded as a hash. + # @return [ Hash ] The entries all _loaded as a hash. # # @since 2.2.0 def as_json(options = {}) entries.as_json(options) end @@ -369,9 +391,16 @@ private def method_missing(name, *args, &block) entries.send(name, *args, &block) + end + + def matching_document(location) + _loaded.try(:values).try(location) || + _added[_unloaded.try(location).try(:id)] || + _unloaded.try(location) || + _added.values.try(location) end end end end end