123456789_123456789_123456789_123456789_123456789_

Module: Mongoid::Criteria::Queryable::Mergeable

Relationships & Source Files
Extension / Inclusion / Inheritance Descendants
Included In:
Defined in: lib/mongoid/criteria/queryable/mergeable.rb

Overview

Contains behavior for merging existing selection with new selection.

Instance Attribute Summary

Instance Method Summary

Instance Attribute Details

#strategy (rw)

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/mergeable.rb', line 12

attr_accessor :strategy

#strategy The name of the current strategy.(The name of the current strategy.) (rw)

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/mergeable.rb', line 12

attr_accessor :strategy

Instance Method Details

#__add__(criterion, operator) ⇒ Mergeable (private)

This method is for internal use only.

Adds the criterion to the existing selection.

Examples:

Add the criterion.

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

Parameters:

  • criterion (Hash)

    The criteria.

  • operator (String)

    The MongoDB operator.

Returns:

  • (Mergeable)

    The new mergeable.

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/mergeable.rb', line 90

def __add__(criterion, operator)
  with_strategy(:__add__, criterion, operator)
end

#__expanded__(criterion, outer, inner) ⇒ Mergeable (private)

This method is for internal use only.

Adds the criterion to the existing selection.

Examples:

Add the criterion.

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

Parameters:

  • criterion (Hash)

    The criteria.

  • outer (String)

    The outer MongoDB operator.

  • inner (String)

    The inner MongoDB operator.

Returns:

  • (Mergeable)

    The new mergeable.

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/mergeable.rb', line 106

def __expanded__(criterion, outer, inner)
  selection(criterion) do |selector, field, value|
    selector.store(field, { outer => { inner => value }})
  end
end

#__intersect__(criterion, operator) ⇒ Mergeable (private)

This method is for internal use only.

Adds the criterion to the existing selection.

Examples:

Add the criterion.

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

Parameters:

  • criterion (Hash)

    The criteria.

  • operator (String)

    The MongoDB operator.

Returns:

  • (Mergeable)

    The new mergeable.

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/mergeable.rb', line 140

def __intersect__(criterion, operator)
  with_strategy(:__intersect__, criterion, operator)
end

#__merge__(criterion) ⇒ Mergeable (private)

This method is for internal use only.

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

Examples:

Straight merge the expanded criterion.

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

Parameters:

  • criterion (Hash)

    The criteria.

Returns:

  • (Mergeable)

    The cloned object.

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/mergeable.rb', line 123

def __merge__(criterion)
  selection(criterion) do |selector, field, value|
    selector.merge!(field.__expr_part__(value))
  end
end

#__multi__(criteria, operator) ⇒ Mergeable (private)

This method is for internal use only.

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 ::Mongoid::Criteria instance).

Examples:

Add the criterion.

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

Parameters:

Returns:

  • (Mergeable)

    The new mergeable.

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/mergeable.rb', line 160

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) ⇒ Mergeable (private)

This method is for internal use only.

Adds the criterion to the existing selection.

Examples:

Add the criterion.

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

Parameters:

  • criterion (Hash | Criteria)

    The criteria.

  • operator (String)

    The MongoDB operator.

Returns:

  • (Mergeable)

    The new mergeable.

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/mergeable.rb', line 343

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) ⇒ Mergeable (private)

This method is for internal use only.

Adds the criterion to the existing selection.

Examples:

Add the criterion.

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

Parameters:

  • criterion (Hash)

    The criteria.

  • operator (String)

    The MongoDB operator.

Returns:

  • (Mergeable)

    The new mergeable.

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/mergeable.rb', line 369

def __union__(criterion, operator)
  with_strategy(:__union__, criterion, operator)
end

#_mongoid_add_top_level_operation(operator, criteria) (private)

This method is for internal use only.

Combines criteria into a MongoDB selector.

::Mongoid::Criteria is an array of criterion objects which will be flattened.

Each criterion can be:

  • A hash

  • A Criteria instance

  • nil, in which case it is ignored

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/mergeable.rb', line 185

private 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) ⇒ BSON::Document (private)

Takes a criteria hash and expands Key objects into hashes containing MQL corresponding to said key objects. Also converts the input to BSON::Document to permit indifferent access.

The argument must be a hash containing key-value pairs of the following forms:

  • value

  • => value

  • value

  • => operator_value_expression

  • => operator_value_expression

Ruby does not permit multiple symbol operators. For example, => 1, :foo.gt => 2 is collapsed to => 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.

This method effectively converts symbol keys to string keys in the input expr, such that the downstream code can assume that conditions always contain string keys.

Parameters:

Returns:

  • (BSON::Document)

    The expanded criteria.

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/mergeable.rb', line 259

private def _mongoid_expand_keys(expr)
  unless expr.is_a?(Hash)
    raise ArgumentError, 'Argument must be a Hash'
  end

  result = BSON::Document.new
  expr.each do |field, value|
    field.__expr_part__(value.__expand_complex__, negating?).each do |k, v|
      if existing = result[k]
        if existing.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 & existing.keys).empty?
              existing.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.
            # Transform the implicit equality to either $eq or $regexp
            # depending on the type of the argument. See
            # https://www.mongodb.com/docs/manual/reference/operator/query/eq/#std-label-eq-usage-examples
            # for the description of relevant server behavior.
            op = case v
            when Regexp, BSON::Regexp::Raw
              '$regex'
            else
              '$eq'
            end
            # If there isn't an $eq/$regex operator already in the
            # query, transform the new value into an operator
            # expression and add it to the existing hash. Otherwise
            # add the new condition with $and to the top level.
            if existing.key?(op)
              raise NotImplementedError, 'Ruby does not allow same symbol operator with different values'
              result['$and'] ||= []
              result['$and'] << {k => v}
            else
              existing.update(op => v)
            end
          end
        else
          # Existing value is a simple value.
          # See the notes above about transformations to $eq/$regex.
          op = case existing
          when Regexp, BSON::Regexp::Raw
            '$regex'
          else
            '$eq'
          end
          if v.is_a?(Hash) && !v.key?(op)
            result[k] = {op => existing}.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) (private)

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

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/mergeable.rb', line 216

private 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

#and_with_operator(criterion, operator) ⇒ Criteria

Merge criteria with operators using the and operator.

Parameters:

  • criterion (Hash)

    The criterion to add to the criteria.

  • operator (String)

    The MongoDB operator.

Returns:

  • (Criteria)

    The resulting criteria.

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/mergeable.rb', line 62

def and_with_operator(criterion, operator)
  crit = self
  if criterion
    criterion.each_pair do |field, value|
      val = prepare(field, operator, value)
      # The prepare method already takes the negation into account. We
      # set negating to false here so that ``and`` doesn't also apply
      # negation and we have a double negative.
      crit.negating = false
      crit = crit.and(field => val)
    end
  end
  crit
end

#intersectMergeable

Instruct the next mergeable call to use intersection.

Examples:

Use intersection on the next call.

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

Returns:

  • (Mergeable)

    The intersect flagged mergeable.

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/mergeable.rb', line 20

def intersect
  use(:__intersect__)
end

#overrideMergeable

Instruct the next mergeable call to use override.

Examples:

Use override on the next call.

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

Returns:

  • (Mergeable)

    The override flagged mergeable.

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/mergeable.rb', line 30

def override
  use(:__override__)
end

#prepare(field, operator, value) ⇒ Object (private)

This method is for internal use only.

Prepare the value for merging.

Examples:

Prepare the value.

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

Parameters:

  • field (String)

    The name of the field.

  • value (Object)

    The value.

Returns:

  • (Object)

    The serialized value.

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/mergeable.rb', line 421

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

#reset_strategies!Criteria

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

Examples:

Reset the strategies.

mergeable.reset_strategies!

Returns:

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/mergeable.rb', line 50

def reset_strategies!
  self.strategy = nil
  self.negating = nil
  self
end

#unionMergeable

Instruct the next mergeable call to use union.

Examples:

Use union on the next call.

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

Returns:

  • (Mergeable)

    The union flagged mergeable.

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/mergeable.rb', line 40

def union
  use(:__union__)
end

#use(strategy) ⇒ Mergeable (private)

This method is for internal use only.

Use the named strategy for the next operation.

Examples:

Use intersection.

mergeable.use(:__intersect__)

Parameters:

  • strategy (Symbol)

    The strategy to use.

Returns:

  • (Mergeable)

    The existing mergeable.

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/mergeable.rb', line 383

def use(strategy)
  tap do |mergeable|
    mergeable.strategy = strategy
  end
end

#with_strategy(strategy, criterion, operator) ⇒ Mergeable (private)

This method is for internal use only.

Add criterion to the selection with the named strategy.

Examples:

Add criterion with a strategy.

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

Parameters:

  • strategy (Symbol)

    The name of the strategy method.

  • criterion (Object)

    The criterion to add.

  • operator (String)

    The MongoDB operator.

Returns:

  • (Mergeable)

    The cloned query.

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/mergeable.rb', line 401

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