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 9

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 9

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 87

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 103

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 137

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 120

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 157

def __multi__(criteria, operator)
  clone.tap do |query|
    sel = query.selector
    criteria.flatten.each do |expr|
      next unless expr

      result_criteria = sel[operator] || []
      expr = expr.selector if expr.is_a?(Selectable)
      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 337

def __override__(criterion, operator)
  criterion = criterion.selector if criterion.is_a?(Selectable)
  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 361

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 181

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|
      expr = if criterion.is_a?(Selectable)
               _mongoid_expand_keys(criterion.selector)
             else
               _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.

Raises:

  • (ArgumentError)
[ GitHub ]

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

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

  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 212

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 59

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 17

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 27

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 413

def prepare(field, operator, value)
  unless /exists|type|size/.match?(operator)
    value = value.__expand_complex__
    field = field.to_s
    name = aliases[field] || field
    serializer = serializers[name]
    value = serializer.evolve(value) if serializer
  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 47

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 37

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 375

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 393

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