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

  src = document
  expanded = false
  exists = true

  key.to_s.split('.').each do |field|
    if (index = field.to_i).to_s == field
      # Array indexing
      if Array === src
        exists = index < src.length
        src = src[index]
      else
        # Trying to index something that is not an array
        exists = false
        src = nil
      end
    else
      case src
      when nil
        exists = false
      when Hash
        exists = src.key?(field)
        src = src[field]
      when Array
        expanded = true
        exists = false
        new = []
        src.each do |doc|
          case doc
          when Hash
            if doc.key?(field)
              v = doc[field]
              case v
              when Array
                new += v
              else
                new += [v]
              end
              exists = true
            end
          else
            # Trying to hash index into a value that is not a hash
          end
        end
        src = new
      else
        # Trying to descend into a field that is not a hash using
        # dot notation.
        exists = false
        src = nil
      end
    end
  end

  [exists, src, expanded]
end