Permalink
Fetching contributors…
Cannot retrieve contributors at this time
209 lines (178 sloc) 5.28 KB
# encoding: UTF-8
require 'set'
require 'plucky/normalizers/criteria_hash_value'
require 'plucky/normalizers/criteria_hash_key'
module Plucky
class CriteriaHash
# Private: The Hash that stores query criteria
attr_reader :source
# Private: The Hash that stores options
attr_reader :options
# Internal: Used to determine if criteria keys match simple id lookup.
SimpleIdQueryKeys = [:_id].to_set
# Internal: Used to determine if criteria keys match simple id and type
# lookup (for single collection inheritance).
SimpleIdAndTypeQueryKeys = [:_id, :_type].to_set
# Internal: Used to quickly check if it is possible that the
# criteria hash is simple.
SimpleQueryMaxSize = [SimpleIdQueryKeys.size, SimpleIdAndTypeQueryKeys.size].max
# Public
def initialize(hash={}, options={})
@source, @options = {}, options
hash.each { |key, value| self[key] = value }
end
def initialize_copy(original)
super
@options = @options.dup
@source = @source.dup
@source.each do |key, value|
self[key] = value.clone if value.duplicable?
end
end
# Public
def [](key)
@source[key]
end
# Public
# The contents of this make me sad...need to clean it up
def []=(key, value)
normalized_key = normalized_key(key)
if key.is_a?(SymbolOperator)
operator = :"$#{key.operator}"
normalized_value = normalized_value(normalized_key, operator, value)
@source[normalized_key] ||= {}
@source[normalized_key][operator] = normalized_value
else
if key == :conditions
value.each { |k, v| self[k] = v }
else
normalized_value = normalized_value(normalized_key, normalized_key, value)
@source[normalized_key] = normalized_value
end
end
end
# Public
def keys
@source.keys
end
# Public
def ==(other)
@source == other.source
end
# Public
def to_hash
@source
end
# Public
def merge(other)
self.class.new hash_merge(@source, other.source)
end
# Public
def merge!(other)
merge(other).to_hash.each do |key, value|
self[key] = value
end
self
end
# Public: The definition of simple is querying by only _id or _id and _type.
# If this is the case, you can use IdentityMap in library to not perform
# query and instead just return from map.
#
# Returns true or false
def simple?
return false if keys.size > SimpleQueryMaxSize
key_set = keys.to_set
key_set == SimpleIdQueryKeys || key_set == SimpleIdAndTypeQueryKeys
end
def object_id?(key)
object_ids.include?(key.to_sym)
end
# Private
def object_ids
@options[:object_ids] ||= []
end
# Private
def object_ids=(value)
raise ArgumentError unless value.is_a?(Array)
@options[:object_ids] = value.flatten
end
private
# Private
def hash_merge(oldhash, newhash)
merge_compound_or_clauses!(oldhash, newhash)
oldhash.merge(newhash) do |key, oldval, newval|
old_is_hash = oldval.instance_of? Hash
new_is_hash = newval.instance_of? Hash
if oldval == newval
oldval
elsif old_is_hash && new_is_hash
hash_merge(oldval, newval)
elsif old_is_hash
modifier_merge(oldval, newval)
elsif new_is_hash
modifier_merge(newval, oldval)
else
merge_values_into_array(oldval, newval)
end
end
end
def merge_compound_or_clauses!(oldhash, newhash)
old_or = oldhash[:$or]
new_or = newhash[:$or]
if old_or && new_or
oldhash[:$and] ||= []
oldhash[:$and] << {:$or => oldhash.delete(:$or)}
oldhash[:$and] << {:$or => newhash.delete(:$or)}
elsif new_or && oldhash[:$and]
if oldhash[:$and].any? {|v| v.key? :$or }
oldhash[:$and] << {:$or => newhash.delete(:$or)}
end
elsif old_or && newhash[:$and]
if newhash[:$and].any? {|v| v.key? :$or }
newhash[:$and] << {:$or => oldhash.delete(:$or)}
end
end
end
# Private
def modifier_merge(hash, value)
if modifier_key = hash.keys.detect { |k| Plucky.modifier?(k) }
hash[modifier_key].concat( array(value) ).uniq
end
end
# Private
def merge_values_into_array(value, other_value)
array(value).concat(array(other_value)).uniq
end
# Private: Array(BSON::ObjectId) returns the byte array or what not instead
# of the object id. This makes sure it is an array of object ids, not the
# guts of the object id.
def array(value)
case value
when nil, BSON::ObjectId
[value]
else
Array(value)
end
end
# Private
def normalized_key(key)
key_normalizer.call(key)
end
# Private
def key_normalizer
@key_normalizer ||= @options.fetch(:key_normalizer) {
Normalizers::CriteriaHashKey.new
}
end
# Private
def normalized_value(parent_key, key, value)
value_normalizer.call(parent_key, key, value)
end
# Private
def value_normalizer
@value_normalizer ||= @options.fetch(:value_normalizer) {
Normalizers::CriteriaHashValue.new(self)
}
end
end
end