123456789_123456789_123456789_123456789_123456789_

Class: Mongoid::Criteria::Queryable::Selector

Relationships & Source Files
Super Chains via Extension / Inclusion / Inheritance
Class Chain:
Instance Chain:
Inherits: Mongoid::Criteria::Queryable::Smash
Defined in: lib/mongoid/criteria/queryable/selector.rb

Overview

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.

Class Attribute Summary

::Mongoid::Extensions::Hash::ClassMethods - Extended

resizable?

Can the size of this object change?

Class Method Summary

Smash - Inherited

.new

Initialize the new selector.

::Mongoid::Extensions::Hash::ClassMethods - Extended

mongoize

Turn the object from the ruby type we deal with to a Mongo friendly type.

Instance Attribute Summary

Instance Method Summary

Smash - Inherited

#[]

Get an item from the smart hash by the provided key.

#__deep_copy__

Perform a deep copy of the smash.

#get_serializer

Retrieves the serializer for the given name.

#localized_key

Get the localized value for the key if needed.

#storage_pair

Get the pair of objects needed to store the value in a hash by the provided key.

::Mongoid::Extensions::Hash - Included

#__consolidate__

Consolidate the key/values in the hash under an atomic $set.

#__evolve_object_id__

Evolves each value in the hash to an object id if it is convertable.

#__mongoize_object_id__

Mongoizes each value in the hash to an object id if it is convertable.

#delete_id

Deletes an id value from the hash.

#extract_id

Get the id attribute from this hash, whether it’s prefixed with an underscore or is a symbol.

#mongoize

Turn the object from the ruby type we deal with to a Mongo friendly type.

#to_criteria

Convert this hash to a criteria.

Constructor Details

This class inherits a constructor from Mongoid::Criteria::Queryable::Smash

Instance Method Details

#[]=(key, value)

Alias for #store.

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/selector.rb', line 61
alias :[]= :store

#evolve(serializer, value) ⇒ Object (private)

This method is for internal use only.

Evolve a single key selection with various types of values.

Examples:

Evolve a simple selection.

selector.evolve(field, 5)

Parameters:

  • serializer (Object)

    The optional serializer for the field.

  • value (Object)

    The value to serialize.

Returns:

  • (Object)

    The serialized object.

[ GitHub ]

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

This method is for internal use only.

Evolve a single key selection with array values.

Examples:

Evolve a simple selection.

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

Parameters:

  • serializer (Object)

    The optional serializer for the field.

  • value (Array<Object>)

    The array to serialize.

Returns:

  • (Object)

    The serialized array.

[ GitHub ]

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

This method is for internal use only.

Evolve a single key selection with hash values.

Examples:

Evolve a simple selection.

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

Parameters:

  • serializer (Object)

    The optional serializer for the field.

  • value (Hash)

    The hash to serialize.

Returns:

  • (Object)

    The serialized hash.

[ GitHub ]

  
# 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) ⇒ Array<Hash> (private)

This method is for internal use only.

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

Examples:

Evolve the multi-selection.

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

Parameters:

  • specs (Array<Hash>)

    The multi-selection.

Returns:

[ GitHub ]

  
# 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) ⇒ Array<String, Hash> (private)

This method is for internal use only.

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).

Parameters:

  • key (String)

    The key at which to store the range.

  • serializer (Object)

    The optional serializer for the field.

  • value (Range)

    The Range to serialize.

Returns:

[ GitHub ]

  
# 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

#merge!(other) ⇒ Selector

Merges another selector into this one.

Examples:

Merge in another selector.

selector.merge!(name: "test")

Parameters:

  • other (Hash | Selector)

    The object to merge in.

Returns:

  • (Selector)

    The selector.

[ GitHub ]

  
# 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

#multi_selection?(key) ⇒ true | false (private)

This method is for internal use only.

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

Examples:

Is the selection a multi-select?

selector.multi_selection?("$and")

Parameters:

  • key (String)

    The key to check.

Returns:

  • (true | false)

    If the key is for a multi-select.

[ GitHub ]

  
# File 'lib/mongoid/criteria/queryable/selector.rb', line 284
def multi_selection?(key)
  %w($and $or $nor).include?(key)
end

#store(key, value) ⇒ Object Also known as: #[]=

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

Examples:

Store a value in the selector.

selector.store(:key, "testing")

Parameters:

  • key (String | Symbol)

    The name of the attribute.

  • value (Object)

    The value to add.

Returns:

  • (Object)

    The stored object.

[ GitHub ]

  
# 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

#store_creds(name, serializer, value) ⇒ Array<String, String> (private)

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.

Parameters:

  • name (String)

    The name of the field.

  • serializer (Object)

    The optional serializer for the field.

  • value (Object)

    The value to serialize.

Returns:

[ GitHub ]

  
# 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

#to_pipelineArray<Hash>

Convert the selector to an aggregation pipeline entry.

Examples:

Convert the selector to a pipeline.

selector.to_pipeline

Returns:

  • (Array<Hash>)

    The pipeline entry for the selector.

[ GitHub ]

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