module Mongoid::Matcher

@api private

Public Instance Methods

extract_attribute(document, key) click to toggle source

Extracts field values in the document at the specified key.

The document can be a Hash or a model instance.

The key is a valid MongoDB dot notation key. The following use cases are supported:

  • Simple field traversal (‘foo`) - retrieves the field `foo` in the current document.

  • Hash/embedded document field traversal (‘foo.bar`) - retrieves the field `foo` in the current document, then retrieves the field `bar` from the value of `foo`. Each path segment could descend into an embedded document or a hash field.

  • Array element retrieval (‘foo.N`) - retrieves the Nth array element from the field `foo` which must be an array. N must be a non-negative integer.

  • Array traversal (‘foo.bar`) - if `foo` is an array field, and the elements of `foo` are hashes or embedded documents, this returns an array of values of the `bar` field in each of the hashes in the `foo` array.

The return value is a two-element array. The first element is the value retrieved, or an array of values. The second element is a boolean flag indicating whether an array was expanded at any point during the key traversal (because the respective document field was an array).

@param [ Document | Hash ] document The document to extract from. @param [ String ] key The key path to extract.

@return [ Array<true | false, Object | Array, true | false> ]

Whether the value existed in the document, the extracted value
and the array expansion flag.
# File lib/mongoid/matcher.rb, line 38
                def extract_attribute(document, key)
  if document.respond_to?(:as_attributes, true)
    # If a document has hash fields, as_attributes would keep those fields
    # as Hash instances which do not offer indifferent access.
    # Convert to BSON::Document to get indifferent access on hash fields.
    document = BSON::Document.new(document.send(:as_attributes))
  end

  current = [document]

  key.to_s.split('.').each do |field|
    new = []
    current.each do |doc|
      case doc
      when Hash
        if doc.key?(field)
          new << doc[field]
        end
      when Array
        if (index = field.to_i).to_s == field
          if doc.length > index
            new << doc[index]
          end
        end
        doc.each do |subdoc|
          if Hash === subdoc
            if subdoc.key?(field)
              new << subdoc[field]
            end
          end
        end
      end
    end
    current = new
    break if current.empty?
  end

  current
end