class Mongoid::Contextual::Memory
Context object used for performing bulk query and persistence operations on documents which have been loaded into application memory. The method interface of this class is consistent with Mongoid::Contextual::Mongo
.
Attributes
@attribute [r] root The root document. @attribute [r] path The atomic path. @attribute [r] selector The root document selector. @attribute [r] matching The in memory documents that match the selector.
@attribute [r] root The root document. @attribute [r] path The atomic path. @attribute [r] selector The root document selector. @attribute [r] matching The in memory documents that match the selector.
@attribute [r] root The root document. @attribute [r] path The atomic path. @attribute [r] selector The root document selector. @attribute [r] matching The in memory documents that match the selector.
@attribute [r] root The root document. @attribute [r] path The atomic path. @attribute [r] selector The root document selector. @attribute [r] matching The in memory documents that match the selector.
Public Class Methods
Create the new in memory context.
@example Create the new context.
Memory.new(criteria)
@param [ Criteria
] criteria The criteria.
# File lib/mongoid/contextual/memory.rb, line 173 def initialize(criteria) @criteria, @klass = criteria, criteria.klass @documents = criteria.documents.select do |doc| @root ||= doc._root @collection ||= root.collection doc._matches?(criteria.selector) end apply_sorting apply_options end
Public Instance Methods
Check if the context is equal to the other object.
@example Check equality.
context == []
@param [ Array ] other The other array.
@return [ true | false ] If the objects are equal.
# File lib/mongoid/contextual/memory.rb, line 35 def ==(other) return false unless other.respond_to?(:entries) entries == other.entries end
Delete all documents in the database that match the selector.
@example Delete all the documents.
context.delete
@return [ nil ] Nil.
# File lib/mongoid/contextual/memory.rb, line 46 def delete deleted = count removed = map do |doc| prepare_remove(doc) doc.send(:as_attributes) end unless removed.empty? collection.find(selector).update_one( positionally(selector, "$pullAll" => { path => removed }), session: _session ) end deleted end
Destroy all documents in the database that match the selector.
@example Destroy all the documents.
context.destroy
@return [ nil ] Nil.
# File lib/mongoid/contextual/memory.rb, line 68 def destroy deleted = count each do |doc| documents.delete_one(doc) doc.destroy end deleted end
Get the distinct values in the db for the provided field.
@example Get the distinct values.
context.distinct(:name)
@param [ String | Symbol ] field The name of the field.
@return [ Array<Object> ] The distinct values for the field.
# File lib/mongoid/contextual/memory.rb, line 86 def distinct(field) pluck(field).uniq end
Iterate over the context. If provided a block, yield to a Mongoid
document for each, otherwise return an enum.
@example Iterate over the context.
context.each do |doc| puts doc.name end
@return [ Enumerator ] The enumerator.
# File lib/mongoid/contextual/memory.rb, line 99 def each if block_given? documents_for_iteration.each do |doc| yield(doc) end self else to_enum end end
Do any documents exist for the context.
@example Do any documents exist for the context.
context.exists?
@example Do any documents exist for given _id.
context.exists?(BSON::ObjectId(...))
@example Do any documents exist for given conditions.
context.exists?(name: "...")
@param [ Hash | Object
| false ] id_or_conditions an _id to
search for, a hash of conditions, nil or false.
@return [ true | false ] If the count is more than zero.
Always false if passed nil or false.
# File lib/mongoid/contextual/memory.rb, line 126 def exists?(id_or_conditions = :none) case id_or_conditions when :none then any? when nil, false then false when Hash then Memory.new(criteria.where(id_or_conditions)).exists? else Memory.new(criteria.where(_id: id_or_conditions)).exists? end end
Get the fifth document in the database for the criteria’s selector.
@example Get the fifth document.
context.fifth
@return [ Document
] The fifth document.
# File lib/mongoid/contextual/memory.rb, line 454 def fifth eager_load([documents.fifth]).first end
Get the fifth document in the database for the criteria’s selector or raise an error if none is found.
@example Get the fifth document.
context.fifth!
@return [ Document
] The fifth document.
@raise [ Mongoid::Errors::DocumentNotFound
] raises when there are no
documents to take.
# File lib/mongoid/contextual/memory.rb, line 468 def fifth! fifth || raise_document_not_found_error end
Get the first document in the database for the criteria’s selector.
@example Get the first document.
context.first
@param [ Integer ] limit The number of documents to return.
@return [ Document
] The first document.
# File lib/mongoid/contextual/memory.rb, line 143 def first(limit = nil) if limit eager_load(documents.first(limit)) else eager_load([documents.first]).first end end
Get the first document in the database for the criteria’s selector or raise an error if none is found.
@example Get the first document.
context.first!
@return [ Document
] The first document.
@raise [ Mongoid::Errors::DocumentNotFound
] raises when there are no
documents to take.
# File lib/mongoid/contextual/memory.rb, line 163 def first! first || raise_document_not_found_error end
Get the fourth document in the database for the criteria’s selector.
@example Get the fourth document.
context.fourth
@return [ Document
] The fourth document.
# File lib/mongoid/contextual/memory.rb, line 430 def fourth eager_load([documents.fourth]).first end
Get the fourth document in the database for the criteria’s selector or raise an error if none is found.
@example Get the fourth document.
context.fourth!
@return [ Document
] The fourth document.
@raise [ Mongoid::Errors::DocumentNotFound
] raises when there are no
documents to take.
# File lib/mongoid/contextual/memory.rb, line 444 def fourth! fourth || raise_document_not_found_error end
Increment a value on all documents.
@example Perform the increment.
context.inc(likes: 10)
@param [ Hash ] incs The operations.
@return [ Enumerator ] The enumerator.
# File lib/mongoid/contextual/memory.rb, line 192 def inc(incs) each do |document| document.inc(incs) end end
Get the last document in the database for the criteria’s selector.
@example Get the last document.
context.last
@param [ Integer ] limit The number of documents to return.
@return [ Document
] The last document.
# File lib/mongoid/contextual/memory.rb, line 206 def last(limit = nil) if limit eager_load(documents.last(limit)) else eager_load([documents.last]).first end end
Get the last document in the database for the criteria’s selector or raise an error if none is found.
@example Get the last document.
context.last!
@return [ Document
] The last document.
@raise [ Mongoid::Errors::DocumentNotFound
] raises when there are no
documents to take.
# File lib/mongoid/contextual/memory.rb, line 224 def last! last || raise_document_not_found_error end
Get the length of matching documents in the context.
@example Get the length of matching documents.
context.length
@return [ Integer ] The matching length.
# File lib/mongoid/contextual/memory.rb, line 234 def length documents.length end
Limits the number of documents that are returned.
@example Limit the documents.
context.limit(20)
@param [ Integer ] value The number of documents to return.
@return [ Memory
] The context.
# File lib/mongoid/contextual/memory.rb, line 247 def limit(value) self.limiting = value self end
Pick the field values in memory.
@example Get the values in memory.
context.pick(:name)
@param [ [ String | Symbol ]… ] *fields Field(s) to pick.
@return [ Object
| Array<Object> ] The picked values.
# File lib/mongoid/contextual/memory.rb, line 274 def pick(*fields) if doc = documents.first pluck_from_doc(doc, *fields) end end
Pluck the field values in memory.
@example Get the values in memory.
context.pluck(:name)
@param [ [ String | Symbol ]… ] *fields Field(s) to pluck.
@return [ Array<Object> | Array<Array<Object>> ] The plucked values.
# File lib/mongoid/contextual/memory.rb, line 260 def pluck(*fields) documents.map do |doc| pluck_from_doc(doc, *fields) end end
Get the second document in the database for the criteria’s selector.
@example Get the second document.
context.second
@return [ Document
] The second document.
# File lib/mongoid/contextual/memory.rb, line 382 def second eager_load([documents.second]).first end
Get the second document in the database for the criteria’s selector or raise an error if none is found.
@example Get the second document.
context.second!
@return [ Document
] The second document.
@raise [ Mongoid::Errors::DocumentNotFound
] raises when there are no
documents to take.
# File lib/mongoid/contextual/memory.rb, line 396 def second! second || raise_document_not_found_error end
Get the second to last document in the database for the criteria’s selector.
@example Get the second to last document.
context.second_to_last
@return [ Document
] The second to last document.
# File lib/mongoid/contextual/memory.rb, line 478 def second_to_last eager_load([documents.second_to_last]).first end
Get the second to last document in the database for the criteria’s selector or raise an error if none is found.
@example Get the second to last document.
context.second_to_last!
@return [ Document
] The second to last document.
@raise [ Mongoid::Errors::DocumentNotFound
] raises when there are no
documents to take.
# File lib/mongoid/contextual/memory.rb, line 492 def second_to_last! second_to_last || raise_document_not_found_error end
Skips the provided number of documents.
@example Skip the documents.
context.skip(20)
@param [ Integer ] value The number of documents to skip.
@return [ Memory
] The context.
# File lib/mongoid/contextual/memory.rb, line 334 def skip(value) self.skipping = value self end
Sorts the documents by the provided spec.
@example Sort the documents.
context.sort(name: -1, title: 1)
@param [ Hash ] values The sorting values as field/direction(1/-1)
pairs.
@return [ Memory
] The context.
# File lib/mongoid/contextual/memory.rb, line 348 def sort(values) in_place_sort(values) and self end
Take the given number of documents from the database.
@example Take a document.
context.take
@param [ Integer | nil ] limit The number of documents to take or nil.
@return [ Document
] The document.
# File lib/mongoid/contextual/memory.rb, line 304 def take(limit = nil) if limit eager_load(documents.take(limit)) else eager_load([documents.first]).first end end
Take the given number of documents from the database or raise an error if none are found.
@example Take a document.
context.take
@return [ Document
] The document.
@raise [ Mongoid::Errors::DocumentNotFound
] raises when there are no
documents to take.
# File lib/mongoid/contextual/memory.rb, line 322 def take! take || raise_document_not_found_error end
Tally the field values in memory.
@example Get the counts of values in memory.
context.tally(:name)
@param [ String | Symbol ] field Field to tally.
@return [ Hash ] The hash of counts.
# File lib/mongoid/contextual/memory.rb, line 288 def tally(field) return documents.each_with_object({}) do |d, acc| v = retrieve_value_at_path(d, field) acc[v] ||= 0 acc[v] += 1 end end
Get the third document in the database for the criteria’s selector.
@example Get the third document.
context.third
@return [ Document
] The third document.
# File lib/mongoid/contextual/memory.rb, line 406 def third eager_load([documents.third]).first end
Get the third document in the database for the criteria’s selector or raise an error if none is found.
@example Get the third document.
context.third!
@return [ Document
] The third document.
@raise [ Mongoid::Errors::DocumentNotFound
] raises when there are no
documents to take.
# File lib/mongoid/contextual/memory.rb, line 420 def third! third || raise_document_not_found_error end
Get the third to last document in the database for the criteria’s selector.
@example Get the third to last document.
context.third_to_last
@return [ Document
] The third to last document.
# File lib/mongoid/contextual/memory.rb, line 502 def third_to_last eager_load([documents.third_to_last]).first end
Get the third to last document in the database for the criteria’s selector or raise an error if none is found.
@example Get the third to last document.
context.third_to_last!
@return [ Document
] The third to last document.
@raise [ Mongoid::Errors::DocumentNotFound
] raises when there are no
documents to take.
# File lib/mongoid/contextual/memory.rb, line 516 def third_to_last! third_to_last || raise_document_not_found_error end
Update the first matching document atomically.
@example Update the matching document.
context.update(name: "Smiths")
@param [ Hash ] attributes The new attributes for the document.
@return [ nil | false ] False if no attributes were provided.
# File lib/mongoid/contextual/memory.rb, line 360 def update(attributes = nil) update_documents(attributes, [ first ]) end
Update all the matching documents atomically.
@example Update all the matching documents.
context.update_all(name: "Smiths")
@param [ Hash ] attributes The new attributes for each document.
@return [ nil | false ] False if no attributes were provided.
# File lib/mongoid/contextual/memory.rb, line 372 def update_all(attributes = nil) update_documents(attributes, entries) end
Private Instance Methods
# File lib/mongoid/contextual/memory.rb, line 682 def _session @criteria.send(:_session) end
Apply criteria options.
@api private
@example Apply criteria options.
context.apply_options
@return [ Memory
] self.
# File lib/mongoid/contextual/memory.rb, line 615 def apply_options raise Errors::InMemoryCollationNotSupported.new if criteria.options[:collation] skip(criteria.options[:skip]).limit(criteria.options[:limit]) end
Map the sort symbols to the correct MongoDB values.
@example Apply the sorting params.
context.apply_sorting
# File lib/mongoid/contextual/memory.rb, line 624 def apply_sorting if spec = criteria.options[:sort] in_place_sort(spec) end end
Compare two values, handling the cases when either value is nil.
@api private
@example Compare the two objects.
context.compare(a, b)
@param [ Object
] a The first object. @param [ Object
] b The second object.
@return [ Integer ] The comparison value.
# File lib/mongoid/contextual/memory.rb, line 642 def compare(a, b) return 0 if a.nil? && b.nil? return 1 if a.nil? return -1 if b.nil? compare_operand(a) <=> compare_operand(b) end
Get the operand value to be used in comparison. Adds capability to sort boolean values.
@example Get the comparison operand.
compare_operand(true) #=> 1
@param [ Object
] value The value to be used in comparison.
@return [ Integer | Object
] The comparison operand.
# File lib/mongoid/contextual/memory.rb, line 695 def compare_operand(value) case value when TrueClass then 1 when FalseClass then 0 else value end end
Get the documents the context should iterate. This follows 3 rules:
@api private
@example Get the documents for iteration.
context.documents_for_iteration
@return [ Array<Document> ] The docs to iterate.
# File lib/mongoid/contextual/memory.rb, line 530 def documents_for_iteration docs = documents[skipping || 0, limiting || documents.length] || [] if eager_loadable? eager_load(docs) end docs end
Sort the documents in place.
@example Sort the documents.
context.in_place_sort(name: 1)
@param [ Hash ] values The field/direction sorting pairs.
# File lib/mongoid/contextual/memory.rb, line 656 def in_place_sort(values) documents.sort! do |a, b| values.map do |field, direction| direction * compare(a[field], b[field]) end.detect { |value| !value.zero? } || 0 end end
Get the limiting value.
@api private
@example Get the limiting value.
@return [ Integer ] The limit.
# File lib/mongoid/contextual/memory.rb, line 566 def limiting defined?(@limiting) ? @limiting : nil end
Set the limiting value.
@api private
@example Set the limiting value.
@param [ Integer ] value The limit.
@return [ Integer ] The limit.
# File lib/mongoid/contextual/memory.rb, line 579 def limiting=(value) @limiting = value end
Pluck the field values from the given document.
@param [ Document
] doc The document to pluck from. @param [ [ String | Symbol ]… ] *fields Field(s) to pluck.
@return [ Object
| Array<Object> ] The plucked values.
# File lib/mongoid/contextual/memory.rb, line 766 def pluck_from_doc(doc, *fields) if fields.length == 1 retrieve_value_at_path(doc, fields.first) else fields.map do |field| retrieve_value_at_path(doc, field) end end end
Prepare the document for batch removal.
@api private
@example Prepare for removal.
context.prepare_remove(doc)
@param [ Document
] doc The document.
# File lib/mongoid/contextual/memory.rb, line 672 def prepare_remove(doc) @selector ||= root.atomic_selector @path ||= doc.atomic_path documents.delete_one(doc) doc._parent.remove_child(doc) doc.destroyed = true end
# File lib/mongoid/contextual/memory.rb, line 776 def raise_document_not_found_error raise Errors::DocumentNotFound.new(klass, nil, nil) end
Retrieve the value for the current document at the given field path.
For example, if I have the following models:
User has_many Accounts address is a hash on Account u = User.new(accounts: [ Account.new(address: { street: "W 50th" }) ]) retrieve_value_at_path(u, "user.accounts.address.street") # => [ "W 50th" ]
Note that the result is in an array since accounts is an array. If it was nested in two arrays the result would be in a 2D array.
@param [ Object
] document The object to traverse the field path. @param [ String ] field_path The dotted string that represents the path
to the value.
@return [ Object
| nil ] The value at the given field path or nil if it
doesn't exist.
# File lib/mongoid/contextual/memory.rb, line 723 def retrieve_value_at_path(document, field_path) return if field_path.blank? || !document segment, remaining = field_path.to_s.split('.', 2) curr = if document.is_a?(Document) # Retrieves field for segment to check localization. Only does one # iteration since there's no dots res = if remaining field = document.class.traverse_association_tree(segment) # If this is a localized field, and there are remaining, get the # _translations hash so that we can get the specified translation in # the remaining if field&.localized? document.send("#{segment}_translations") end end meth = klass.aliased_associations[segment] || segment res.nil? ? document.try(meth) : res elsif document.is_a?(Hash) # TODO: Remove the indifferent access when implementing MONGOID-5410. document.key?(segment.to_s) ? document[segment.to_s] : document[segment.to_sym] else nil end return curr unless remaining if curr.is_a?(Array) # compact is used for consistency with server behavior. curr.map { |d| retrieve_value_at_path(d, remaining) }.compact else retrieve_value_at_path(curr, remaining) end end
Get the skipping value.
@api private
@example Get the skipping value.
@return [ Integer ] The skip.
# File lib/mongoid/contextual/memory.rb, line 590 def skipping defined?(@skipping) ? @skipping : nil end
Set the skipping value.
@api private
@example Set the skipping value.
@param [ Integer ] value The skip.
@return [ Integer ] The skip.
# File lib/mongoid/contextual/memory.rb, line 603 def skipping=(value) @skipping = value end
Update the provided documents with the attributes.
@api private
@example Update the documents.
context.update_documents({}, doc)
@param [ Hash ] attributes The attributes. @param [ Array<Document> ] docs The docs to update.
# File lib/mongoid/contextual/memory.rb, line 547 def update_documents(attributes, docs) return false if !attributes || docs.empty? updates = { "$set" => {}} docs.each do |doc| @selector ||= root.atomic_selector doc.write_attributes(attributes) updates["$set"].merge!(doc.atomic_updates["$set"] || {}) doc.move_changes end collection.find(selector).update_one(updates, session: _session) unless updates["$set"].empty? end