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

documents[R]

@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.

path[R]

@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.

root[R]

@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.

selector[R]

@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

new(criteria) click to toggle source

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

==(other) click to toggle source

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() click to toggle source

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
Also aliased as: delete_all
delete_all()
Alias for: delete
destroy() click to toggle source

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
Also aliased as: destroy_all
destroy_all()
Alias for: destroy
distinct(field) click to toggle source

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
each() { |doc| ... } click to toggle source

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
exists?(id_or_conditions = :none) click to toggle source

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
fifth() click to toggle source

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
fifth!() click to toggle source

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
find_first(limit = nil)
Alias for: first
first(limit = nil) click to toggle source

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
Also aliased as: one, find_first
first!() click to toggle source

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
fourth() click to toggle source

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
fourth!() click to toggle source

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
inc(incs) click to toggle source

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
last(limit = nil) click to toggle source

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
last!() click to toggle source

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
length() click to toggle source

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
Also aliased as: size
limit(value) click to toggle source

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
one(limit = nil)
Alias for: first
pick(*fields) click to toggle source

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(*fields) click to toggle source

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
second() click to toggle source

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
second!() click to toggle source

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
second_to_last() click to toggle source

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
second_to_last!() click to toggle source

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
size()
Alias for: length
skip(value) click to toggle source

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
sort(values) click to toggle source

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(limit = nil) click to toggle source

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!() click to toggle source

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(field) click to toggle source

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
third() click to toggle source

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
third!() click to toggle source

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
third_to_last() click to toggle source

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
third_to_last!() click to toggle source

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(attributes = nil) click to toggle source

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(attributes = nil) click to toggle source

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

_session() click to toggle source
# File lib/mongoid/contextual/memory.rb, line 682
def _session
  @criteria.send(:_session)
end
apply_options() click to toggle source

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
apply_sorting() click to toggle source

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(a, b) click to toggle source

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
compare_operand(value) click to toggle source

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
documents_for_iteration() click to toggle source

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
in_place_sort(values) click to toggle source

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
limiting() click to toggle source

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
limiting=(value) click to toggle source

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_from_doc(doc, *fields) click to toggle source

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_remove(doc) click to toggle source

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
raise_document_not_found_error() click to toggle source
# File lib/mongoid/contextual/memory.rb, line 776
def raise_document_not_found_error
  raise Errors::DocumentNotFound.new(klass, nil, nil)
end
retrieve_value_at_path(document, field_path) click to toggle source

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
skipping() click to toggle source

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
skipping=(value) click to toggle source

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_documents(attributes, docs) click to toggle source

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