class Mongoid::Criteria::Queryable::Selector

The selector is a special kind of hash that knows how to serialize values coming into it as well as being alias and locale aware for key names.

Public Instance Methods

[]=(key, value)
Alias for: store
merge!(other) click to toggle source

Merges another selector into this one.

@example Merge in another selector.

selector.merge!(name: "test")

@param [ Hash | Selector ] other The object to merge in.

@return [ Selector ] The selector.

# File lib/mongoid/criteria/queryable/selector.rb, line 20
def merge!(other)
  other.each_pair do |key, value|
    if value.is_a?(Hash) && self[key.to_s].is_a?(Hash)
      value = self[key.to_s].merge(value) do |_key, old_val, new_val|
        case _key.to_s
        when '$in'
          new_val & old_val
        when '$nin'
          (old_val + new_val).uniq
        else
          new_val
        end
      end
    end
    if multi_selection?(key)
      value = (self[key.to_s] || []).concat(value)
    end
    store(key, value)
  end
end
store(key, value) click to toggle source

Store the value in the selector for the provided key. The selector will handle all necessary serialization and localization in this step.

@example Store a value in the selector.

selector.store(:key, "testing")

@param [ String | Symbol ] key The name of the attribute. @param [ Object ] value The value to add.

@return [ Object ] The stored object.

Calls superclass method
# File lib/mongoid/criteria/queryable/selector.rb, line 51
def store(key, value)
  name, serializer = storage_pair(key)
  if multi_selection?(name)
    store_name = name
    store_value = evolve_multi(value)
  else
    store_name, store_value = store_creds(name, serializer, value)
  end
  super(store_name, store_value)
end
Also aliased as: []=
to_pipeline() click to toggle source

Convert the selector to an aggregation pipeline entry.

@example Convert the selector to a pipeline.

selector.to_pipeline

@return [ Array<Hash> ] The pipeline entry for the selector.

# File lib/mongoid/criteria/queryable/selector.rb, line 69
def to_pipeline
  pipeline = []
  pipeline.push({ "$match" => self }) unless empty?
  pipeline
end

Private Instance Methods

evolve(serializer, value) click to toggle source

Evolve a single key selection with various types of values.

@api private

@example Evolve a simple selection.

selector.evolve(field, 5)

@param [ Object ] serializer The optional serializer for the field. @param [ Object ] value The value to serialize.

@return [ Object ] The serialized object.

# File lib/mongoid/criteria/queryable/selector.rb, line 153
def evolve(serializer, value)
  case value
  when Mongoid::RawValue
    value.raw_value
  when Hash
    evolve_hash(serializer, value)
  when Array
    evolve_array(serializer, value)
  when Range
    value.__evolve_range__(serializer: serializer)
  else
    (serializer || value.class).evolve(value)
  end
end
evolve_array(serializer, value) click to toggle source

Evolve a single key selection with array values.

@api private

@example Evolve a simple selection.

selector.evolve(field, [ 1, 2, 3 ])

@param [ Object ] serializer The optional serializer for the field. @param [ Array<Object> ] value The array to serialize.

@return [ Object ] The serialized array.

# File lib/mongoid/criteria/queryable/selector.rb, line 179
def evolve_array(serializer, value)
  value.map do |_value|
    evolve(serializer, _value)
  end
end
evolve_hash(serializer, value) click to toggle source

Evolve a single key selection with hash values.

@api private

@example Evolve a simple selection.

selector.evolve(field, { "$gt" => 5 })

@param [ Object ] serializer The optional serializer for the field. @param [ Hash ] value The hash to serialize.

@return [ Object ] The serialized hash.

# File lib/mongoid/criteria/queryable/selector.rb, line 196
def evolve_hash(serializer, value)
  value.each_pair do |operator, _value|
    if operator =~ /exists|type|size/
      value[operator] = _value
    else
      value[operator] = evolve(serializer, _value)
    end
  end
end
evolve_multi(specs) click to toggle source

Evolves a multi-list selection, like an $and or $or criterion, and performs the necessary serialization.

@example Evolve the multi-selection.

selector.evolve_multi([{ field: "value" }])

@param [ Array<Hash> ] specs The multi-selection.

@return [ Array<Hash> ] The serialized values.

@api private

# File lib/mongoid/criteria/queryable/selector.rb, line 106
def evolve_multi(specs)
  unless specs.is_a?(Array)
    raise ArgumentError, "specs is not an array: #{specs.inspect}"
  end
  specs.map do |spec|
    Hash[spec.map do |key, value|
      # If an application nests conditionals, e.g.
      # {'$or' => [{'$or' => {...}}]},
      # when evolve_multi is called for the top level hash,
      # this call recursively transforms the bottom level $or.
      if multi_selection?(key)
        value = evolve_multi(value)
      end

      # storage_pair handles field aliases but not localization for
      # some reason, although per its documentation Smash supposedly
      # owns both.
      name, serializer = storage_pair(key)
      # This performs type conversions on the value and transformations
      # that depend on the type of the field that the value is stored
      # in, but not transformations that have to do with query shape.
      final_key, evolved_value = store_creds(name, serializer, value)

      # This builds a query shape around the value, when the query
      # involves complex keys. For example, {:foo.lt => 5} produces
      # {'foo' => {'$lt' => 5}}. This step should be done after all
      # value-based processing is complete.
      if key.is_a?(Key)
        evolved_value = key.transform_value(evolved_value)
      end

      [ final_key, evolved_value ]
    end]
  end.uniq
end
evolve_range(key, serializer, value) click to toggle source

Evolve a single key selection with range values. This method traverses the association tree to build a query for the given value and serializer. There are three parts to the query here:

(1) “klass.child.gchild” => {

  "$elemMatch" => {
(2) "ggchild.field" => (3) { "$gte" => 6, "$lte" => 10 }
  }
}

(1) The first n fields are dotted together until the last

embeds_many or field of type array. In the above case, gchild
would be an embeds_many or Array, and ggchild would be an
embeds_one or a hash.

(2) The last fields are used inside the $elemMatch. This one is

actually optional, and will be ignored if the last field is an
array or embeds_many. If the last field is an array (1), (2) and
(3) will look like:

  "klass.child.gchild.ggchild.field" => {
    { "$elemMatch" => { "$gte" => 6, "$lte" => 10 } }
  }

(3) This is calculated by:

value.__evolve_range__(serializer: serializer).

@api private

@param [ String ] key The key at which to store the range. @param [ Object ] serializer The optional serializer for the field. @param [ Range ] value The Range to serialize.

@return [ Array<String, Hash> ] The store name and serialized Range.

# File lib/mongoid/criteria/queryable/selector.rb, line 239
def evolve_range(key, serializer, value)
  v = value.__evolve_range__(serializer: serializer)
  assocs = []
  Fields.traverse_association_tree(key, serializers, associations, aliased_associations) do |meth, obj, is_field|
    assocs.push([meth, obj, is_field])
  end

  # Iterate backwards until you get a field with type
  # Array or an embeds_many association.
  inner_key = ""
  loop do
    # If there are no arrays or embeds_many associations, just return
    # the key and value without $elemMatch.
    return [ key, v ] if assocs.empty?

    meth, obj, is_field = assocs.last
    break if (is_field && obj.type == Array) || (!is_field && obj.is_a?(Association::Embedded::EmbedsMany))

    assocs.pop
    inner_key = "#{meth}.#{inner_key}"
  end

  # If the last array or embeds_many association is the last field,
  # the inner key (2) is ignored, and the outer key (1) is the original
  # key.
  if inner_key.blank?
    [ key, { "$elemMatch" => v }]
  else
    store_key = assocs.map(&:first).join('.')
    store_value = { "$elemMatch" => { inner_key.chop => v } }
    [ store_key,  store_value ]
  end
end
multi_selection?(key) click to toggle source

Determines if the selection is a multi-select, like an $and or $or or $nor selection.

@api private

@example Is the selection a multi-select?

selector.multi_selection?("$and")

@param [ String ] key The key to check.

@return [ true | false ] If the key is for a multi-select.

# File lib/mongoid/criteria/queryable/selector.rb, line 284
def multi_selection?(key)
  %w($and $or $nor).include?(key)
end
store_creds(name, serializer, value) click to toggle source

Get the store name and store value. If the value is of type range, we need may need to change the store_name as well as the store_value, therefore, we cannot just use the evolve method.

@param [ String ] name The name of the field. @param [ Object ] serializer The optional serializer for the field. @param [ Object ] value The value to serialize.

@return [ Array<String, String> ] The store name and store value.

# File lib/mongoid/criteria/queryable/selector.rb, line 86
def store_creds(name, serializer, value)
  store_name = localized_key(name, serializer)
  if Range === value
    evolve_range(store_name, serializer, value)
  else
    [ store_name, evolve(serializer, value) ]
  end
end