lib/mongoid/matcher.rb
module Mongoid
# @api private
module Matcher
# 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.
module_function 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
end
end
require 'mongoid/matcher/all'
require 'mongoid/matcher/and'
require 'mongoid/matcher/bits'
require 'mongoid/matcher/bits_all_clear'
require 'mongoid/matcher/bits_all_set'
require 'mongoid/matcher/bits_any_clear'
require 'mongoid/matcher/bits_any_set'
require 'mongoid/matcher/elem_match'
require 'mongoid/matcher/elem_match_expression'
require 'mongoid/matcher/eq'
require 'mongoid/matcher/eq_impl'
require 'mongoid/matcher/eq_impl_with_regexp'
require 'mongoid/matcher/exists'
require 'mongoid/matcher/expression'
require 'mongoid/matcher/field_expression'
require 'mongoid/matcher/gt'
require 'mongoid/matcher/gte'
require 'mongoid/matcher/in'
require 'mongoid/matcher/lt'
require 'mongoid/matcher/lte'
require 'mongoid/matcher/mod'
require 'mongoid/matcher/ne'
require 'mongoid/matcher/nin'
require 'mongoid/matcher/nor'
require 'mongoid/matcher/not'
require 'mongoid/matcher/or'
require 'mongoid/matcher/regex'
require 'mongoid/matcher/size'
require 'mongoid/matcher/type'
require 'mongoid/matcher/expression_operator'
require 'mongoid/matcher/field_operator'