module Mongoid::Criteria::Queryable::Mergeable

Contains behavior for merging existing selection with new selection.

Attributes

strategy[RW]

@attribute [rw] strategy The name of the current strategy.

Public Instance Methods

intersect() click to toggle source

Instruct the next mergeable call to use intersection.

@example Use intersection on the next call.

mergeable.intersect.in(field: [ 1, 2, 3 ])

@return [ Mergeable ] The intersect flagged mergeable.

@since 1.0.0

# File lib/mongoid/criteria/queryable/mergeable.rb, line 22
def intersect
  use(:__intersect__)
end
override() click to toggle source

Instruct the next mergeable call to use override.

@example Use override on the next call.

mergeable.override.in(field: [ 1, 2, 3 ])

@return [ Mergeable ] The override flagged mergeable.

@since 1.0.0

# File lib/mongoid/criteria/queryable/mergeable.rb, line 34
def override
  use(:__override__)
end
reset_strategies!() click to toggle source

Clear the current strategy and negating flag, used after cloning.

@example Reset the strategies.

mergeable.reset_strategies!

@return [ Criteria ] self.

@since 1.0.0

# File lib/mongoid/criteria/queryable/mergeable.rb, line 58
def reset_strategies!
  self.strategy = nil
  self.negating = nil
  self
end
union() click to toggle source

Instruct the next mergeable call to use union.

@example Use union on the next call.

mergeable.union.in(field: [ 1, 2, 3 ])

@return [ Mergeable ] The union flagged mergeable.

@since 1.0.0

# File lib/mongoid/criteria/queryable/mergeable.rb, line 46
def union
  use(:__union__)
end

Private Instance Methods

__add__(criterion, operator) click to toggle source

Adds the criterion to the existing selection.

@api private

@example Add the criterion.

mergeable.__add__({ name: 1 }, "$in")

@param [ Hash ] criterion The criteria. @param [ String ] operator The MongoDB operator.

@return [ Mergeable ] The new mergeable.

@since 1.0.0

# File lib/mongoid/criteria/queryable/mergeable.rb, line 79
def __add__(criterion, operator)
  with_strategy(:__add__, criterion, operator)
end
__expanded__(criterion, outer, inner) click to toggle source

Adds the criterion to the existing selection.

@api private

@example Add the criterion.

mergeable.__expanded__([ 1, 10 ], "$within", "$center")

@param [ Hash ] criterion The criteria. @param [ String ] outer The outer MongoDB operator. @param [ String ] inner The inner MongoDB operator.

@return [ Mergeable ] The new mergeable.

@since 1.0.0

# File lib/mongoid/criteria/queryable/mergeable.rb, line 97
def __expanded__(criterion, outer, inner)
  selection(criterion) do |selector, field, value|
    selector.store(field, { outer => { inner => value }})
  end
end
__intersect__(criterion, operator) click to toggle source

Adds the criterion to the existing selection.

@api private

@example Add the criterion.

mergeable.__intersect__([ 1, 2 ], "$in")

@param [ Hash ] criterion The criteria. @param [ String ] operator The MongoDB operator.

@return [ Mergeable ] The new mergeable.

@since 1.0.0

# File lib/mongoid/criteria/queryable/mergeable.rb, line 135
def __intersect__(criterion, operator)
  with_strategy(:__intersect__, criterion, operator)
end
__merge__(criterion) click to toggle source

Perform a straight merge of the criterion into the selection and let the symbol overrides do all the work.

@api private

@example Straight merge the expanded criterion.

mergeable.__merge__(location: [ 1, 10 ])

@param [ Hash ] criterion The criteria.

@return [ Mergeable ] The cloned object.

@since 2.0.0

# File lib/mongoid/criteria/queryable/mergeable.rb, line 116
def __merge__(criterion)
  selection(criterion) do |selector, field, value|
    selector.merge!(field.__expr_part__(value))
  end
end
__multi__(criteria, operator) click to toggle source

Adds $and/$or/$nor criteria to a copy of this selection.

Each of the criteria can be a Hash of key/value pairs or MongoDB operators (keys beginning with $), or a Selectable object (which typically will be a Criteria instance).

@api private

@example Add the criterion.

mergeable.__multi__([ 1, 2 ], "$in")

@param [ Array<Hash | Criteria> ] criteria Multiple key/value pair

matches or Criteria objects.

@param [ String ] operator The MongoDB operator.

@return [ Mergeable ] The new mergeable.

@since 1.0.0

# File lib/mongoid/criteria/queryable/mergeable.rb, line 157
def __multi__(criteria, operator)
  clone.tap do |query|
    sel = query.selector
    criteria.flatten.each do |expr|
      next unless expr
      result_criteria = sel[operator] || []
      if expr.is_a?(Selectable)
        expr = expr.selector
      end
      normalized = _mongoid_expand_keys(expr)
      sel.store(operator, result_criteria.push(normalized))
    end
  end
end
__override__(criterion, operator) click to toggle source

Adds the criterion to the existing selection.

@api private

@example Add the criterion.

mergeable.__override__([ 1, 2 ], "$in")

@param [ Hash | Criteria ] criterion The criteria. @param [ String ] operator The MongoDB operator.

@return [ Mergeable ] The new mergeable.

@since 1.0.0

# File lib/mongoid/criteria/queryable/mergeable.rb, line 316
def __override__(criterion, operator)
  if criterion.is_a?(Selectable)
    criterion = criterion.selector
  end
  selection(criterion) do |selector, field, value|
    expression = prepare(field, operator, value)
    existing = selector[field]
    if existing.respond_to?(:merge!)
      selector.store(field, existing.merge!(expression))
    else
      selector.store(field, expression)
    end
  end
end
__union__(criterion, operator) click to toggle source

Adds the criterion to the existing selection.

@api private

@example Add the criterion.

mergeable.__union__([ 1, 2 ], "$in")

@param [ Hash ] criterion The criteria. @param [ String ] operator The MongoDB operator.

@return [ Mergeable ] The new mergeable.

@since 1.0.0

# File lib/mongoid/criteria/queryable/mergeable.rb, line 344
def __union__(criterion, operator)
  with_strategy(:__union__, criterion, operator)
end
_mongoid_add_top_level_operation(operator, criteria) click to toggle source

Combines criteria into a MongoDB selector.

Criteria is an array of criterions which will be flattened.

Each criterion can be:

  • A hash

  • A Criteria instance

  • nil, in which case it is ignored

@api private

# File lib/mongoid/criteria/queryable/mergeable.rb, line 182
        def _mongoid_add_top_level_operation(operator, criteria)
  # Flatten the criteria. The idea is that predicates in MongoDB
  # are always hashes and are never arrays. This method additionally
  # allows Criteria instances as predicates.
  # The flattening is existing Mongoid behavior but we could possibly
  # get rid of it as applications can splat their predicates, or
  # flatten if needed.
  clone.tap do |query|
    sel = query.selector
    _mongoid_flatten_arrays(criteria).each do |criterion|
      if criterion.is_a?(Selectable)
        expr = _mongoid_expand_keys(criterion.selector)
      else
        expr = _mongoid_expand_keys(criterion)
      end
      if sel.empty?
        sel.store(operator, [expr])
      elsif sel.keys == [operator]
        sel.store(operator, sel[operator] + [expr])
      else
        operands = [sel.dup] + [expr]
        sel.clear
        sel.store(operator, operands)
      end
    end
  end
end
_mongoid_expand_keys(expr) click to toggle source

Takes a criteria hash and expands Key objects into hashes containing MQL corresponding to said key objects.

Ruby does not permit multiple symbol operators. For example, {:foo.gt => 1, :foo.gt => 2} is collapsed to {:foo.gt => 2} by the language. Therefore this method never has to deal with multiple identical operators.

Similarly, this method should never need to expand a literal value and an operator at the same time.

@param [ Hash ] Criteria including Key instances.

@return [ Hash ] Expanded criteria.

# File lib/mongoid/criteria/queryable/mergeable.rb, line 243
        def _mongoid_expand_keys(expr)
  unless expr.is_a?(Hash)
    raise ArgumentError, 'Argument must be a Hash'
  end

  result = {}
  expr.each do |field, value|
    field.__expr_part__(value.__expand_complex__).each do |k, v|
      if result[k]
        if result[k].is_a?(Hash)
          # Existing value is an operator.
          # If new value is also an operator, ensure there are no
          # conflicts and add
          if v.is_a?(Hash)
            # The new value is also an operator.
            # If there are no conflicts, combine the hashes, otherwise
            # add new conditions to top level with $and.
            if (v.keys & result[k].keys).empty?
              result[k].update(v)
            else
              raise NotImplementedError, 'Ruby does not allow same symbol operator with different values'
              result['$and'] ||= []
              result['$and'] << {k => v}
            end
          else
            # The new value is a simple value.
            # If there isn't an $eq operator already in the query,
            # transform the new value into an $eq operator and add it
            # to the existing hash. Otherwise add the new condition
            # with $and to the top level.
            if result[k].key?('$eq')
              raise NotImplementedError, 'Ruby does not allow same symbol operator with different values'
              result['$and'] ||= []
              result['$and'] << {k => v}
            else
              result[k].update('$eq' => v)
            end
          end
        else
          # Existing value is a simple value.
          # If we are adding an operator, and the operator is not $eq,
          # convert existing value into $eq and add the new operator
          # to the same hash. Otherwise add the new condition with $and
          # to the top level.
          if v.is_a?(Hash) && !v.key?('$eq')
            result[k] = {'$eq' => result[k]}.update(v)
          else
            raise NotImplementedError, 'Ruby does not allow same symbol operator with different values'
            result['$and'] ||= []
            result['$and'] << {k => v}
          end
        end
      else
        result[k] = v
      end
    end
  end
  result
end
_mongoid_flatten_arrays(array) click to toggle source

Calling .flatten on an array which includes a Criteria instance evaluates the criteria, which we do not want. Hence this method explicitly only expands Array objects and Array subclasses.

# File lib/mongoid/criteria/queryable/mergeable.rb, line 213
        def _mongoid_flatten_arrays(array)
  out = []
  pending = array.dup
  until pending.empty?
    item = pending.shift
    if item.nil?
      # skip
    elsif item.is_a?(Array)
      pending += item
    else
      out << item
    end
  end
  out
end
prepare(field, operator, value) click to toggle source

Prepare the value for merging.

@api private

@example Prepare the value.

mergeable.prepare("field", "$gt", 10)

@param [ String ] field The name of the field. @param [ Object ] value The value.

@return [ Object ] The serialized value.

@since 1.0.0

# File lib/mongoid/criteria/queryable/mergeable.rb, line 402
def prepare(field, operator, value)
  unless operator =~ /exists|type|size/
    value = value.__expand_complex__
    field = field.to_s
    name = aliases[field] || field
    serializer = serializers[name]
    value = serializer ? serializer.evolve(value) : value
  end
  selection = { operator => value }
  negating? ? { "$not" => selection } : selection
end
use(strategy) click to toggle source

Use the named strategy for the next operation.

@api private

@example Use intersection.

mergeable.use(:__intersect__)

@param [ Symbol ] strategy The strategy to use.

@return [ Mergeable ] The existing mergeable.

@since 1.0.0

# File lib/mongoid/criteria/queryable/mergeable.rb, line 360
def use(strategy)
  tap do |mergeable|
    mergeable.strategy = strategy
  end
end
with_strategy(strategy, criterion, operator) click to toggle source

Add criterion to the selection with the named strategy.

@api private

@example Add criterion with a strategy.

mergeable.with_strategy(:__union__, {field_name: [ 1, 2, 3 ]}, "$in")

@param [ Symbol ] strategy The name of the strategy method. @param [ Object ] criterion The criterion to add. @param [ String ] operator The MongoDB operator.

@return [ Mergeable ] The cloned query.

@since 1.0.0

# File lib/mongoid/criteria/queryable/mergeable.rb, line 380
def with_strategy(strategy, criterion, operator)
  selection(criterion) do |selector, field, value|
    selector.store(
      field,
      selector[field].send(strategy, prepare(field, operator, value))
    )
  end
end