Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Refactored Query in a massive way. Now have CriteriaHash and OptionsH…

…ash which do all the dirty work. Query just has an instance of each of these.
  • Loading branch information...
commit 4c7ddbee6699c32d38d482796078415e8658c52b 1 parent 9aea956
@jnunemaker jnunemaker authored
View
1  lib/plucky.rb
@@ -3,6 +3,7 @@
require 'plucky/support'
module Plucky
+ autoload :OptionsHash, 'plucky/options_hash'
autoload :CriteriaHash, 'plucky/criteria_hash'
autoload :Query, 'plucky/query'
View
112 lib/plucky/criteria_hash.rb
@@ -1,27 +1,113 @@
module Plucky
class CriteriaHash
- attr_reader :hash
+ attr_reader :source
- def initialize(hash)
- @hash = hash
+ def initialize(hash={}, options={})
+ @source, @options = {}, options
+ hash.each { |key, value| self[key] = value }
+ end
+
+ 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
+
+ def ==(other)
+ source == other.source
+ end
+
+ def to_hash
+ source
end
def merge(other)
- hash.dup.tap do |target|
- other.keys.each do |key|
- value, other_value = target[key], other[key]
- target[key] =
- if target.key?(key)
- if value.is_a?(Hash)
- self.class.new(value).merge(other_value)
+ target = source.dup
+ other.source.each_key do |key|
+ value, other_value = target[key], other[key]
+ target[key] =
+ if target.key?(key)
+ value_is_hash = value.is_a?(Hash)
+ other_is_hash = other_value.is_a?(Hash)
+
+ if value_is_hash && other_is_hash
+ value.update(other_value) do |key, old_value, new_value|
+ Array(old_value).concat(Array(new_value)).uniq
+ end
+ elsif value_is_hash && !other_is_hash
+ if modifier_key = value.keys.detect { |k| k.to_s[0, 1] == '$' }
+ value[modifier_key].concat(Array(other_value)).uniq!
else
- Array(value).concat(Array(other_value)).uniq
+ # kaboom! Array(value).concat(Array(other_value)).uniq
+ end
+ elsif other_is_hash && !value_is_hash
+ if modifier_key = other_value.keys.detect { |k| k.to_s[0, 1] == '$' }
+ other_value[modifier_key].concat(Array(value)).uniq!
+ else
+ # kaboom! Array(value).concat(Array(other_value)).uniq
end
else
- other_value
+ Array(value).concat(Array(other_value)).uniq
end
- end
+ else
+ other_value
+ end
end
+ self.class.new(target)
end
+
+ def object_ids
+ @options[:object_ids]
+ end
+
+ def object_ids=(value)
+ @options[:object_ids] = value
+ end
+
+ private
+ def method_missing(method, *args, &block)
+ @source.send(method, *args, &block)
+ end
+
+ def object_id?(key)
+ return false if object_ids.nil?
+ object_ids.include?(key.to_sym)
+ end
+
+ def normalized_key(key)
+ key = key.to_sym if key.respond_to?(:to_sym)
+ return normalized_key(key.field) if key.respond_to?(:field)
+ return :_id if key == :id
+ key
+ end
+
+ def normalized_value(parent_key, key, value)
+ case value
+ when Array, Set
+ value.map! { |v| Plucky.to_object_id(v) } if object_id?(parent_key)
+ parent_key == key ? {'$in' => value.to_a} : value.to_a
+ when Time
+ value.utc
+ when String
+ return Plucky.to_object_id(value) if object_id?(key)
+ value
+ when Hash
+ value.each { |k, v| value[k] = normalized_value(key, k, v) }
+ value
+ else
+ value
+ end
+ end
end
end
View
97 lib/plucky/options_hash.rb
@@ -0,0 +1,97 @@
+module Plucky
+ class OptionsHash
+ attr_reader :source
+
+ def initialize(hash={})
+ @source = {}
+ hash.each { |key, value| self[key] = value }
+ end
+
+ def []=(key, value)
+ key = normalized_key(key)
+ source[key] = normalized_value(key, value)
+ end
+
+ def ==(other)
+ source == other.source
+ end
+
+ def to_hash
+ source
+ end
+
+ private
+ def method_missing(method, *args, &block)
+ @source.send(method, *args, &block)
+ end
+
+ NormalizedKeys = {
+ :order => :sort,
+ :select => :fields,
+ :offset => :skip,
+ :id => :_id,
+ }
+
+ def normalized_key(key)
+ NormalizedKeys.default = key
+ NormalizedKeys[key.to_sym]
+ end
+
+ def normalized_value(key, value)
+ case key
+ when :fields
+ normalized_fields(value)
+ when :sort
+ normalized_sort(value)
+ when :limit, :skip
+ value.nil? ? nil : value.to_i
+ else
+ value
+ end
+ end
+
+ def normalized_fields(value)
+ return nil if value.respond_to?(:empty?) && value.empty?
+ case value
+ when Array
+ value.flatten
+ when Symbol
+ [value]
+ when String
+ value.split(',').map { |v| v.strip }
+ else
+ value
+ end
+ end
+
+ def normalized_sort(value)
+ case value
+ when Array
+ value.compact.map { |v| normalized_sort_piece(v).flatten }
+ else
+ normalized_sort_piece(value)
+ end
+ end
+
+ def normalized_sort_piece(value)
+ case value
+ when SymbolOperator
+ [normalized_direction(value.field, value.operator)]
+ when String
+ value.split(',').map do |piece|
+ normalized_direction(*piece.split(' '))
+ end
+ when Symbol
+ [normalized_direction(value)]
+ else
+ value
+ end
+ end
+
+ def normalized_direction(field, direction=nil)
+ direction ||= 'ASC'
+ direction = direction.upcase == 'ASC' ? 1 : -1
+ [normalized_key(field).to_s, direction]
+ end
+ end
+end
View
191 lib/plucky/query.rb
@@ -5,229 +5,108 @@ class Query
:fields, :skip, :limit, :sort, :hint, :snapshot, :batch_size, :timeout # Ruby Driver
]
- attr_reader :criteria, :options, :collection
+ attr_reader :criteria, :options, :collection
def initialize(collection, opts={})
- @collection, @options, @criteria, = collection, {}, {}
- update(opts)
+ @collection, @options, @criteria = collection, OptionsHash.new, CriteriaHash.new
+ opts.each { |key, value| self[key] = value }
end
def object_ids(*keys)
- @object_id_keys = keys.flatten.map { |k| k.to_sym }
+ criteria.object_ids = @object_ids
self
end
def find(opts={})
- update(opts).collection.find(criteria, options)
+ update(opts).collection.find(criteria.to_hash, options.to_hash)
end
def find_one(opts={})
- update(opts).collection.find_one(criteria, options)
+ update(opts).collection.find_one(criteria.to_hash, options.to_hash)
end
def all(opts={})
[].tap do |docs|
- update(opts).find(criteria.merge(options)).each { |doc| docs << doc }
+ update(opts).find(to_hash).each { |doc| docs << doc }
end
end
def first(opts={})
- update(opts).find_one(criteria.merge(options))
+ update(opts).find_one(to_hash)
end
def last(opts={})
- update(opts).reverse.find_one(criteria.merge(options))
+ update(opts).reverse.find_one(to_hash)
end
def remove(opts={})
- update(opts).collection.remove(criteria)
+ update(opts).collection.remove(criteria.to_hash)
end
def count(opts={})
- update(opts).find(criteria.merge(options)).count
+ update(opts).find(to_hash).count
end
def update(opts={})
- separate_criteria_and_options(opts)
+ opts.each { |key, value| self[key] = value }
self
end
def fields(*args)
- @options[:fields] = normalized_fields(args)
+ self[:fields] = args
self
end
def limit(count=nil)
- @options[:limit] = count.nil? ? nil : count.to_i
+ self[:limit] = count
self
end
def reverse
- @options[:sort] = @options[:sort].map { |s| [s[0], -s[1]] }
+ self[:sort].map! { |s| [s[0], -s[1]] }
self
end
def skip(count=nil)
- @options[:skip] = count.nil? ? nil : count.to_i
+ self[:skip] = count
self
end
def sort(*args)
- @options[:sort] = normalized_sort(args)
+ self[:sort] = *args
self
end
def where(hash={})
- merged_criteria = CriteriaHash.new(@criteria).merge(normalized_criteria(hash))
- @criteria.update(normalized_criteria(merged_criteria))
+ criteria.merge(CriteriaHash.new(hash)).to_hash.each { |key, value| self[key] = value }
self
end
def [](key)
- @criteria[key.to_sym]
+ key = key.to_sym if key.respond_to?(:to_sym)
+ if OptionKeys.include?(key)
+ @options[key]
+ else
+ @criteria[key]
+ end
end
def []=(key, value)
- @criteria[key.to_sym] = normalized_value(@criteria, key, value)
+ key = key.to_sym if key.respond_to?(:to_sym)
+ if OptionKeys.include?(key)
+ @options[key] = value
+ else
+ @criteria[key] = value
+ end
end
def merge(other)
- clone.update(other.options).where(other.criteria)
+ merged = criteria.merge(other.criteria).to_hash.merge(options.to_hash.merge(other.options.to_hash))
+ clone.update(merged)
end
- private
- def object_id_key?(key)
- return false if @object_id_keys.nil?
- key = key.respond_to?(:field) ? key.field.to_sym : key.to_sym
- @object_id_keys.include?(key)
- end
-
- def normalized_criteria(criteria, parent=nil)
- {}.tap do |hash|
- criteria.each_pair do |key, value|
- key = normalized_key(key)
-
- if object_id_key?(key)
- case value
- when String
- value = Plucky.to_object_id(value)
- when Array
- value.map! { |id| Plucky.to_object_id(id) }
- end
- end
-
- if symbol_operator?(key)
- key, value = normalized_key(key.field), {"$#{key.operator}" => value}
- end
-
- hash[key] = normalized_value(hash, key, value)
- end
- end
- end
-
- def normalize_options
- sort @options[:sort] || @options.delete(:order)
- skip @options[:skip] || @options.delete(:offset)
- limit @options[:limit]
- fields @options[:fields] || @options.delete(:select)
- end
-
- def normalized_key(field)
- field.to_s == 'id' ? :_id : field
- end
-
- def normalized_value(criteria, key, value)
- case value
- when Array, Set
- modifier?(key) ? value.to_a : {'$in' => value.to_a}
- when Hash
- if criteria[key].kind_of?(Hash)
- criteria[key].merge(normalized_criteria(value, key))
- else
- normalized_criteria(value, key)
- end
- when Time
- value.utc
- else
- value
- end
- end
-
- def normalized_sort(sort)
- return if sort.nil?
- return if sort.respond_to?(:compact) && sort.compact.empty?
-
- sort = sort[0] if sort.size == 1
-
- case sort
- when Array
- sort.map do |s|
- case s
- when SymbolOperator
- normalized_order(s.field, s.operator)
- when Array
- s
- else
- [s.to_s, 1]
- end
- end
- when SymbolOperator
- [normalized_order(sort.field, sort.operator)]
- when String
- sort.split(',').map { |str| normalized_order(*str.strip.split(' ')) }
- when Symbol
- [[sort, 1]]
- else
- sort
- end
- end
-
- def normalized_fields(fields)
- return if fields.nil?
- fields = fields[0] if fields.size == 1
- return if fields.respond_to?(:empty?) && fields.empty?
-
- case fields
- when Array
- fields.flatten.compact
- when String
- fields.split(',').map { |field| field.strip }
- when Symbol
- [fields]
- else
- fields
- end
- end
-
- def normalized_order(field, direction=nil)
- direction ||= 'ASC'
- direction = direction.upcase == 'ASC' ? 1 : -1
- [normalized_key(field).to_s, direction]
- end
-
- def symbol_operator?(object)
- object.respond_to?(:field, :operator)
- end
-
- def modifier?(key)
- key.to_s =~ /^\$/
- end
-
- def separate_criteria_and_options(opts={})
- opts.each_pair do |key, value|
- key = key.respond_to?(:to_sym) ? key.to_sym : key
-
- if OptionKeys.include?(key)
- @options[key] = value
- elsif key == :conditions
- @criteria.update(value)
- else
- @criteria[key] = value
- end
- end
-
- @criteria = normalized_criteria(@criteria)
- normalize_options
- end
+ def to_hash
+ criteria.to_hash.merge(options.to_hash)
+ end
end
end
View
10 test/helper.rb
@@ -2,10 +2,16 @@
require 'shoulda'
require 'matchy'
require 'mocha'
+require 'logger'
+require 'fileutils'
require File.expand_path('../../lib/plucky', __FILE__)
-connection = Mongo::Connection.new
-DB = connection.db('testing')
+log_dir = File.join(File.dirname(__FILE__), '..', 'log')
+FileUtils.mkdir_p(log_dir)
+Log = Logger.new(File.join(log_dir, 'test.log'))
+
+connection = Mongo::Connection.new('127.0.0.1', 27017, :logger => Log)
+DB = connection.db('plucky')
class Test::Unit::TestCase
def setup
View
225 test/plucky/test_criteria_hash.rb
@@ -4,35 +4,218 @@ class CriteriaHashTest < Test::Unit::TestCase
include Plucky
context "Plucky::CriteriaHash" do
+ should "delegate missing methods to the source hash" do
+ hash = {:baz => 'wick', :foo => 'bar'}
+ criteria = CriteriaHash.new(hash)
+ criteria[:foo].should == 'bar'
+ criteria[:baz].should == 'wick'
+ criteria.keys.should == [:baz, :foo]
+ end
+
+ SymbolOperators.each do |operator|
+ should "work with #{operator} symbol operator" do
+ CriteriaHash.new(:age.send(operator) => 21)[:age].should == {"$#{operator}" => 21}
+ end
+ end
+
+ should "handle multiple symbol operators on the same field" do
+ CriteriaHash.new(:age.gt => 12, :age.lt => 20)[:age].should == {
+ '$gt' => 12, '$lt' => 20
+ }
+ end
+
+ should "allow setting the object ids" do
+ criteria = CriteriaHash.new
+ criteria.object_ids = [:_id]
+ criteria.object_ids.should == [:_id]
+ end
+
+ context "#[]=" do
+ should "leave string values for string keys alone" do
+ criteria = CriteriaHash.new
+ criteria[:foo] = 'bar'
+ criteria[:foo].should == 'bar'
+ end
+
+ should "convert string values to object ids for object id keys" do
+ id = BSON::ObjectID.new
+ criteria = CriteriaHash.new({}, :object_ids => [:_id])
+ criteria[:_id] = id.to_s
+ criteria[:_id].should == id
+ end
+
+ should "convert sets to arrays" do
+ criteria = CriteriaHash.new
+ criteria[:foo] = [1, 2].to_set
+ criteria[:foo].should == {'$in' => [1, 2]}
+ end
+
+ should "convert times to utc" do
+ time = Time.now
+ criteria = CriteriaHash.new
+ criteria[:foo] = time
+ criteria[:foo].should be_utc
+ criteria[:foo].should == time.utc
+ end
+
+ should "convert :id to :_id" do
+ criteria = CriteriaHash.new
+ criteria[:id] = 1
+ criteria[:_id].should == 1
+ criteria[:id].should be_nil
+ end
+
+ should "work with symbol operators" do
+ criteria = CriteriaHash.new
+ criteria[:_id.in] = ['foo']
+ criteria[:_id].should == {'$in' => ['foo']}
+ end
+
+ should "set each of the conditions pairs" do
+ criteria = CriteriaHash.new
+ criteria[:conditions] = {:_id => 'john', :foo => 'bar'}
+ criteria[:_id].should == 'john'
+ criteria[:foo].should == 'bar'
+ end
+ end
+
+ context "with id key" do
+ should "convert to _id" do
+ id = BSON::ObjectID.new
+ criteria = CriteriaHash.new(:id => id)
+ criteria[:_id].should == id
+ criteria[:id].should be_nil
+ end
+
+ should "convert id with symbol operator to _id with modifier" do
+ id = BSON::ObjectID.new
+ criteria = CriteriaHash.new(:id.ne => id)
+ criteria[:_id].should == {'$ne' => id}
+ criteria[:id].should be_nil
+ end
+ end
+
+ context "with time value" do
+ should "convert to utc if not utc" do
+ CriteriaHash.new(:created_at => Time.now)[:created_at].utc?.should be(true)
+ end
+
+ should "leave utc alone" do
+ CriteriaHash.new(:created_at => Time.now.utc)[:created_at].utc?.should be(true)
+ end
+ end
+
+ context "with array value" do
+ should "default to $in" do
+ CriteriaHash.new(:numbers => [1,2,3])[:numbers].should == {'$in' => [1,2,3]}
+ end
+
+ should "use existing modifier if present" do
+ CriteriaHash.new(:numbers => {'$all' => [1,2,3]})[:numbers].should == {'$all' => [1,2,3]}
+ CriteriaHash.new(:numbers => {'$any' => [1,2,3]})[:numbers].should == {'$any' => [1,2,3]}
+ end
+ end
+
+ context "with set value" do
+ should "default to $in and convert to array" do
+ CriteriaHash.new(:numbers => [1,2,3].to_set)[:numbers].should == {'$in' => [1,2,3]}
+ end
+
+ should "use existing modifier if present and convert to array" do
+ CriteriaHash.new(:numbers => {'$all' => [1,2,3].to_set})[:numbers].should == {'$all' => [1,2,3]}
+ CriteriaHash.new(:numbers => {'$any' => [1,2,3].to_set})[:numbers].should == {'$any' => [1,2,3]}
+ end
+ end
+
+ context "with string ids for string keys" do
+ setup do
+ @id = BSON::ObjectID.new
+ @room_id = BSON::ObjectID.new
+ @criteria = CriteriaHash.new(:_id => @id.to_s, :room_id => @room_id.to_s)
+ end
+
+ should "leave string ids as strings" do
+ @criteria[:_id].should == @id.to_s
+ @criteria[:room_id].should == @room_id.to_s
+ @criteria[:_id].should be_instance_of(String)
+ @criteria[:room_id].should be_instance_of(String)
+ end
+ end
+
+ context "with string ids for object id keys" do
+ setup do
+ @id = BSON::ObjectID.new
+ @room_id = BSON::ObjectID.new
+ end
+
+ should "convert strings to object ids" do
+ criteria = CriteriaHash.new({:_id => @id.to_s, :room_id => @room_id.to_s}, :object_ids => [:_id, :room_id])
+ criteria[:_id].should == @id
+ criteria[:room_id].should == @room_id
+ criteria[:_id].should be_instance_of(BSON::ObjectID)
+ criteria[:room_id].should be_instance_of(BSON::ObjectID)
+ end
+
+ should "convert :id with string value to object id value" do
+ criteria = CriteriaHash.new({:id => @id.to_s}, :object_ids => [:_id])
+ criteria[:_id].should == @id
+ end
+ end
+
+ context "with string ids for object id keys (nested)" do
+ setup do
+ @id1 = BSON::ObjectID.new
+ @id2 = BSON::ObjectID.new
+ @criteria = CriteriaHash.new({:_id => {'$in' => [@id1.to_s, @id2.to_s]}}, :object_ids => [:_id])
+ end
+
+ should "convert strings to object ids" do
+ @criteria[:_id].should == {'$in' => [@id1, @id2]}
+ end
+ end
+
context "#merge" do
should "work when no keys match" do
- first, second = {:foo => 'bar'}, {:baz => 'wick'}
- CriteriaHash.new(first).merge(second).should == {
- :foo => 'bar',
- :baz => 'wick',
- }
+ c1 = CriteriaHash.new(:foo => 'bar')
+ c2 = CriteriaHash.new(:baz => 'wick')
+ c1.merge(c2).should == CriteriaHash.new(:foo => 'bar', :baz => 'wick')
end
should "turn matching keys with simple values into array" do
- first, second = {:foo => 'bar'}, {:foo => 'baz'}
- CriteriaHash.new(first).merge(second).should == {
- :foo => %w[bar baz],
- }
+ c1 = CriteriaHash.new(:foo => 'bar')
+ c2 = CriteriaHash.new(:foo => 'baz')
+ c1.merge(c2).should == CriteriaHash.new(:foo => {'$in' => %w[bar baz]})
end
should "unique matching key values" do
- first, second = {:foo => 'bar'}, {:foo => %w(bar baz)}
- CriteriaHash.new(first).merge(second).should == {
- :foo => %w[bar baz],
- }
- end
-
- should "turn matching keys with $in => arrays to one $in => array of uniq values" do
- first = {:foo => {'$in' => [1, 2, 3]}}
- second = {:foo => {'$in' => [1, 4, 5]}}
- CriteriaHash.new(first).merge(second).should == {
- :foo => {'$in' => [1, 2, 3, 4, 5]}
- }
+ c1 = CriteriaHash.new(:foo => 'bar')
+ c2 = CriteriaHash.new(:foo => 'bar')
+ c1.merge(c2).should == CriteriaHash.new(:foo => {'$in' => %w[bar]})
+ end
+
+ should "correctly merge arrays and non-arrays" do
+ c1 = CriteriaHash.new(:foo => 'bar')
+ c2 = CriteriaHash.new(:foo => %w[bar baz])
+ c1.merge(c2).should == CriteriaHash.new(:foo => {'$in' => %w[bar baz]})
+ c2.merge(c1).should == CriteriaHash.new(:foo => {'$in' => %w[bar baz]})
+ end
+
+ should "be able to merge two modifier hashes" do
+ c1 = CriteriaHash.new('$in' => [1, 2])
+ c2 = CriteriaHash.new('$in' => [2, 3])
+ c1.merge(c2).should == CriteriaHash.new('$in' => [1, 2, 3])
+ end
+
+ should "merge matching keys with a single modifier" do
+ c1 = CriteriaHash.new(:foo => {'$in' => [1, 2, 3]})
+ c2 = CriteriaHash.new(:foo => {'$in' => [1, 4, 5]})
+ c1.merge(c2).should == CriteriaHash.new(:foo => {'$in' => [1, 2, 3, 4, 5]})
+ end
+
+ should "merge matching keys with multiple modifiers" do
+ c1 = CriteriaHash.new(:foo => {'$in' => [1, 2, 3]})
+ c2 = CriteriaHash.new(:foo => {'$all' => [1, 4, 5]})
+ c1.merge(c2).should == CriteriaHash.new(:foo => {'$in' => [1, 2, 3], '$all' => [1, 4, 5]})
end
end
end
View
229 test/plucky/test_options_hash.rb
@@ -0,0 +1,229 @@
+require 'helper'
+
+class OptionsHashTest < Test::Unit::TestCase
+ include Plucky
+
+ context "Plucky::OptionsHash" do
+ should "delegate missing methods to the source hash" do
+ hash = {:skip => 1, :limit => 1}
+ options = OptionsHash.new(hash)
+ options[:skip].should == 1
+ options[:limit].should == 1
+ options.keys.should == [:limit, :skip]
+ end
+
+ context "#[]=" do
+ should "convert order to sort" do
+ options = OptionsHash.new(:order => :foo)
+ options[:order].should be_nil
+ options[:sort].should == [['foo', 1]]
+ end
+
+ should "convert select to fields" do
+ options = OptionsHash.new(:select => 'foo')
+ options[:select].should be_nil
+ options[:fields].should == ['foo']
+ end
+
+ should "convert offset to skip" do
+ options = OptionsHash.new(:offset => 1)
+ options[:offset].should be_nil
+ options[:skip].should == 1
+ end
+
+ context ":fields" do
+ setup { @options = OptionsHash.new }
+ subject { @options }
+
+ should "default to nil" do
+ subject[:fields].should be_nil
+ end
+
+ should "be nil if empty string" do
+ subject[:fields] = ''
+ subject[:fields].should be_nil
+ end
+
+ should "be nil if empty array" do
+ subject[:fields] = []
+ subject[:fields].should be_nil
+ end
+
+ should "work with array" do
+ subject[:fields] = %w[one two]
+ subject[:fields].should == %w[one two]
+ end
+
+ should "flatten multi-dimensional array" do
+ subject[:fields] = [[:one, :two]]
+ subject[:fields].should == [:one, :two]
+ end
+
+ should "work with symbol" do
+ subject[:fields] = :one
+ subject[:fields].should == [:one]
+ end
+
+ should "work with array of symbols" do
+ subject[:fields] = [:one, :two]
+ subject[:fields].should == [:one, :two]
+ end
+
+ should "work with hash" do
+ subject[:fields] = {:one => 1, :two => -1}
+ subject[:fields].should == {:one => 1, :two => -1}
+ end
+
+ should "convert comma separated list to array" do
+ subject[:fields] = 'one, two'
+ subject[:fields].should == %w[one two]
+ end
+
+ should "convert select" do
+ subject[:select] = 'one, two'
+ subject[:select].should be_nil
+ subject[:fields].should == %w[one two]
+ end
+ end
+
+ context ":limit" do
+ setup { @options = OptionsHash.new }
+ subject { @options }
+
+ should "default to nil" do
+ subject[:limit].should be_nil
+ end
+
+ should "use limit provided" do
+ subject[:limit] = 1
+ subject[:limit].should == 1
+ end
+
+ should "convert string to integer" do
+ subject[:limit] = '1'
+ subject[:limit].should == 1
+ end
+ end
+
+ context ":skip" do
+ setup { @options = OptionsHash.new }
+ subject { @options }
+
+ should "default to nil" do
+ subject[:skip].should be_nil
+ end
+
+ should "use limit provided" do
+ subject[:skip] = 1
+ subject[:skip].should == 1
+ end
+
+ should "convert string to integer" do
+ subject[:skip] = '1'
+ subject[:skip].should == 1
+ end
+
+ should "be set from offset" do
+ subject[:offset] = '1'
+ subject[:offset].should be_nil
+ subject[:skip].should == 1
+ end
+ end
+
+ context ":sort" do
+ setup { @options = OptionsHash.new }
+ subject { @options }
+
+ should "default to nil" do
+ subject[:sort].should be_nil
+ end
+
+ should "work with natural order ascending" do
+ subject[:sort] = {'$natural' => 1}
+ subject[:sort].should == {'$natural' => 1}
+ end
+
+ should "work with natural order descending" do
+ subject[:sort] = {'$natural' => -1}
+ subject[:sort].should =={'$natural' => -1}
+ end
+
+ should "convert single ascending field (string)" do
+ subject[:sort] = 'foo asc'
+ subject[:sort].should == [['foo', 1]]
+
+ subject[:sort] = 'foo ASC'
+ subject[:sort].should == [['foo', 1]]
+ end
+
+ should "convert single descending field (string)" do
+ subject[:sort] = 'foo desc'
+ subject[:sort].should == [['foo', -1]]
+
+ subject[:sort] = 'foo DESC'
+ subject[:sort].should == [['foo', -1]]
+ end
+
+ should "convert multiple fields (string)" do
+ subject[:sort] = 'foo desc, bar asc'
+ subject[:sort].should == [['foo', -1], ['bar', 1]]
+ end
+
+ should "convert multiple fields and default no direction to ascending (string)" do
+ subject[:sort] = 'foo desc, bar, baz'
+ subject[:sort].should == [['foo', -1], ['bar', 1], ['baz', 1]]
+ end
+
+ should "convert symbol" do
+ subject[:sort] = :name
+ subject[:sort] = [['name', 1]]
+ end
+
+ should "convert operator" do
+ subject[:sort] = :foo.desc
+ subject[:sort].should == [['foo', -1]]
+ end
+
+ should "convert array of operators" do
+ subject[:sort] = [:foo.desc, :bar.asc]
+ subject[:sort].should == [['foo', -1], ['bar', 1]]
+ end
+
+ should "convert array of symbols" do
+ subject[:sort] = [:first_name, :last_name]
+ subject[:sort] = [['first_name', 1], ['last_name', 1]]
+ end
+
+ should "work with array of single array" do
+ subject[:sort] = [['foo', -1]]
+ subject[:sort].should == [['foo', -1]]
+ end
+
+ should "work with array of multiple arrays" do
+ subject[:sort] = [['foo', -1], ['bar', 1]]
+ subject[:sort].should == [['foo', -1], ['bar', 1]]
+ end
+
+ should "compact nil values in array" do
+ subject[:sort] = [nil, :foo.desc]
+ subject[:sort].should == [['foo', -1]]
+ end
+
+ should "convert array with mix of values" do
+ subject[:sort] = [:foo.desc, 'bar']
+ subject[:sort].should == [['foo', -1], ['bar', 1]]
+ end
+
+ should "convert id to _id" do
+ subject[:sort] = [:id.asc]
+ subject[:sort].should == [['_id', 1]]
+ end
+
+ should "convert string with $natural correctly" do
+ subject[:sort] = '$natural desc'
+ subject[:sort].should == [['$natural', -1]]
+ end
+ end
+ end
+ end
+end
View
351 test/plucky/test_query.rb
@@ -14,6 +14,34 @@ class QueryTest < Test::Unit::TestCase
@collection.insert(@john)
end
+ context "#initialize" do
+ setup { @query = Query.new(@collection) }
+ subject { @query }
+
+ should "default options to options hash" do
+ @query.options.should be_instance_of(OptionsHash)
+ end
+
+ should "default criteria to criteria hash" do
+ @query.criteria.should be_instance_of(CriteriaHash)
+ end
+ end
+
+ context "#[]=" do
+ setup { @query = Query.new(@collection) }
+ subject { @query }
+
+ should "set key on options for option" do
+ subject[:skip] = 1
+ subject[:skip].should == 1
+ end
+
+ should "set key on criteria for criteria" do
+ subject[:foo] = 'bar'
+ subject[:foo].should == 'bar'
+ end
+ end
+
context "#find" do
should "return a cursor" do
cursor = Query.new(@collection).find
@@ -102,26 +130,6 @@ class QueryTest < Test::Unit::TestCase
end
context "#fields" do
- should "update options (with array)" do
- Query.new(@collection).fields([:foo, :bar, :baz]).options[:fields].should == [:foo, :bar, :baz]
- end
-
- should "update options (with hash)" do
- Query.new(@collection).fields(:foo => 1, :bar => 0).options[:fields].should == {:foo => 1, :bar => 0}
- end
-
- should "normalize fields" do
- Query.new(@collection).fields('foo, bar').options[:fields].should == %w(foo bar)
- end
-
- should "work with symbol" do
- Query.new(@collection).fields(:foo).options[:fields].should == [:foo]
- end
-
- should "work with array of symbols" do
- Query.new(@collection).fields(:foo, :bar).options[:fields].should == [:foo, :bar]
- end
-
should "work" do
Query.new(@collection).fields(:name).first(:id => 'john').keys.should == ['_id', 'name']
end
@@ -209,7 +217,7 @@ class QueryTest < Test::Unit::TestCase
end
should "work with just a symbol" do
- Query.new(@collection).sort(:foo).options[:sort].should == [[:foo, 1]]
+ Query.new(@collection).sort(:foo).options[:sort].should == [['foo', 1]]
end
should "work with multiple symbols" do
@@ -234,10 +242,10 @@ class QueryTest < Test::Unit::TestCase
end
should "work with simple stuff" do
- Query.new(@collection).update(:foo => 'bar').update(:baz => 'wick').criteria.should == {
- :foo => 'bar',
- :baz => 'wick',
- }
+ Query.new(@collection).
+ update(:foo => 'bar').
+ update(:baz => 'wick').
+ criteria.should == CriteriaHash.new(:foo => 'bar', :baz => 'wick')
end
end
@@ -247,19 +255,22 @@ class QueryTest < Test::Unit::TestCase
end
should "update criteria" do
- Query.new(@collection, :moo => 'cow').where(:foo => 'bar').criteria.should == {:foo => 'bar', :moo => 'cow'}
+ Query.new(@collection, :moo => 'cow').
+ where(:foo => 'bar').
+ criteria.should == CriteriaHash.new(:foo => 'bar', :moo => 'cow')
end
should "get normalized" do
- Query.new(@collection, :moo => 'cow').where(:foo.in => ['bar']).criteria.should == {
- :moo => 'cow', :foo => {'$in' => ['bar']}
- }
+ Query.new(@collection, :moo => 'cow').
+ where(:foo.in => ['bar']).
+ criteria.should == CriteriaHash.new(:moo => 'cow', :foo => {'$in' => ['bar']})
end
should "normalize merged criteria" do
- Query.new(@collection).where(:foo => 'bar').where(:foo => 'baz').criteria.should == {
- :foo => {'$in' => %w[bar baz]}
- }
+ Query.new(@collection).
+ where(:foo => 'bar').
+ where(:foo => 'baz').
+ criteria.should == CriteriaHash.new(:foo => {'$in' => %w[bar baz]})
end
end
@@ -276,274 +287,15 @@ class QueryTest < Test::Unit::TestCase
query1 = Query.new(@collection, :foo => 'bar')
query2 = Query.new(@collection, :foo => 'baz', :fent => 'wick')
new_query = query1.merge(query2)
- new_query.criteria.should == {:foo => {'$in' => %w[bar baz]}, :fent => 'wick'}
- end
- end
-
- context "Converting criteria" do
- SymbolOperators.each do |operator|
- should "work with #{operator} symbol operator" do
- Query.new(@collection, :age.send(operator) => 21).criteria.should == {:age => {"$#{operator}" => 21}}
- end
- end
-
- should "work with simple criteria" do
- Query.new(@collection, :foo => 'bar').criteria.should == {:foo => 'bar'}
- Query.new(@collection, :foo => 'bar', :baz => 'wick').criteria.should == {:foo => 'bar', :baz => 'wick'}
- end
-
- should "work with multiple symbol operators on the same field" do
- Query.new(@collection, :position.gt => 0, :position.lte => 10).criteria.should == {
- :position => {"$gt" => 0, "$lte" => 10}
- }
- end
-
- context "with id key" do
- should "convert to _id" do
- id = BSON::ObjectID.new
- Query.new(@collection, :id => id).criteria.should == {:_id => id}
- end
-
- should "convert id with symbol operator to _id with modifier" do
- id = BSON::ObjectID.new
- Query.new(@collection, :id.ne => id).criteria.should == {:_id => {'$ne' => id}}
- end
- end
-
- context "with time value" do
- should "convert to utc if not utc" do
- Query.new(@collection, :created_at => Time.now).criteria[:created_at].utc?.should be(true)
- end
-
- should "leave utc alone" do
- Query.new(@collection, :created_at => Time.now.utc).criteria[:created_at].utc?.should be(true)
- end
- end
-
- context "with array value" do
- should "default to $in" do
- Query.new(@collection, :numbers => [1,2,3]).criteria.should == {:numbers => {'$in' => [1,2,3]}}
- end
-
- should "use existing modifier if present" do
- Query.new(@collection, :numbers => {'$all' => [1,2,3]}).criteria.should == {:numbers => {'$all' => [1,2,3]}}
- Query.new(@collection, :numbers => {'$any' => [1,2,3]}).criteria.should == {:numbers => {'$any' => [1,2,3]}}
- end
-
- should "work arbitrarily deep" do
- Query.new(@collection, :foo => {:bar => [1,2,3]}).criteria.should == {:foo => {:bar => {'$in' => [1,2,3]}}}
- Query.new(@collection, :foo => {:bar => {'$any' => [1,2,3]}}).criteria.should == {:foo => {:bar => {'$any' => [1,2,3]}}}
- end
- end
-
- context "with set value" do
- should "default to $in and convert to array" do
- Query.new(@collection, :numbers => Set.new([1,2,3])).criteria.should == {:numbers => {'$in' => [1,2,3]}}
- end
-
- should "use existing modifier if present and convert to array" do
- Query.new(@collection, :numbers => {'$all' => Set.new([1,2,3])}).criteria.should == {:numbers => {'$all' => [1,2,3]}}
- Query.new(@collection, :numbers => {'$any' => Set.new([1,2,3])}).criteria.should == {:numbers => {'$any' => [1,2,3]}}
- end
- end
-
- context "with string ids for string keys" do
- setup do
- @id = BSON::ObjectID.new.to_s
- @room_id = BSON::ObjectID.new.to_s
- @query = Query.new(@collection)
- @query.where(:_id => @id, :room_id => @room_id)
- end
-
- should "convert strings to object ids" do
- @query[:_id].should == @id
- @query[:room_id].should == @room_id
- @query[:_id].should be_instance_of(String)
- @query[:room_id].should be_instance_of(String)
- end
- end
-
- context "with string ids for object id keys (*keys)" do
- setup do
- @id = BSON::ObjectID.new
- @room_id = BSON::ObjectID.new
- @query = Query.new(@collection).object_ids(:_id, :room_id)
- @query.where(:_id => @id.to_s, :room_id => @room_id.to_s)
- end
-
- should "convert strings to object ids" do
- @query[:_id].should == @id
- @query[:room_id].should == @room_id
- @query[:_id].should be_instance_of(BSON::ObjectID)
- @query[:room_id].should be_instance_of(BSON::ObjectID)
- end
- end
-
- context "with string ids for object id keys (array of keys)" do
- setup do
- @id = BSON::ObjectID.new
- @room_id = BSON::ObjectID.new
- @query = Query.new(@collection).object_ids([:_id, :room_id])
- @query.where(:_id => @id.to_s, :room_id => @room_id.to_s)
- end
-
- should "convert strings to object ids" do
- @query[:_id].should == @id
- @query[:room_id].should == @room_id
- @query[:_id].should be_instance_of(BSON::ObjectID)
- @query[:room_id].should be_instance_of(BSON::ObjectID)
- end
- end
-
- context "with string ids for object id keys (array)" do
- setup do
- @id1 = BSON::ObjectID.new
- @id2 = BSON::ObjectID.new
- @query = Query.new(@collection).object_ids(:_id)
- @query.where(:_id.in => [@id1.to_s, @id2.to_s])
- end
-
- should "convert strings to object ids" do
- @query[:_id].should == {'$in' => [@id1, @id2]}
- end
- end
- end
-
- context "order option" do
- should "single field with ascending direction" do
- sort = [['foo', 1]]
- Query.new(@collection, :order => 'foo asc').options[:sort].should == sort
- Query.new(@collection, :order => 'foo ASC').options[:sort].should == sort
- end
-
- should "single field with descending direction" do
- sort = [['foo', -1]]
- Query.new(@collection, :order => 'foo desc').options[:sort].should == sort
- Query.new(@collection, :order => 'foo DESC').options[:sort].should == sort
- end
-
- should "convert order operators to mongo sort" do
- query = Query.new(@collection, :order => :foo.asc)
- query.options[:sort].should == [['foo', 1]]
- query.options[:order].should be_nil
-
- query = Query.new(@collection, :order => :foo.desc)
- query.options[:sort].should == [['foo', -1]]
- query.options[:order].should be_nil
- end
-
- should "convert array of order operators to mongo sort" do
- Query.new(@collection, :order => [:foo.asc, :bar.desc]).options[:sort].should == [['foo', 1], ['bar', -1]]
- end
-
- should "convert field without direction to ascending" do
- sort = [['foo', 1]]
- Query.new(@collection, :order => 'foo').options[:sort].should == sort
- end
-
- should "convert multiple fields with directions" do
- sort = [['foo', -1], ['bar', 1], ['baz', -1]]
- Query.new(@collection, :order => 'foo desc, bar asc, baz desc').options[:sort].should == sort
- end
-
- should "convert multiple fields with some missing directions" do
- sort = [['foo', -1], ['bar', 1], ['baz', 1]]
- Query.new(@collection, :order => 'foo desc, bar, baz').options[:sort].should == sort
- end
-
- should "normalize id to _id" do
- Query.new(@collection, :order => :id.asc).options[:sort].should == [['_id', 1]]
- end
-
- should "convert natural in order to proper" do
- sort = [['$natural', 1]]
- Query.new(@collection, :order => '$natural asc').options[:sort].should == sort
- sort = [['$natural', -1]]
- Query.new(@collection, :order => '$natural desc').options[:sort].should == sort
- end
- end
-
- context "sort option" do
- should "work for natural order ascending" do
- Query.new(@collection, :sort => {'$natural' => 1}).options[:sort]['$natural'].should == 1
- end
-
- should "work for natural order descending" do
- Query.new(@collection, :sort => {'$natural' => -1}).options[:sort]['$natural'].should == -1
- end
-
- should "should be used if both sort and order are present" do
- sort = [['$natural', 1]]
- Query.new(@collection, :sort => sort, :order => 'foo asc').options[:sort].should == sort
- end
- end
-
- context "skip option" do
- should "default to nil" do
- Query.new(@collection, {}).options[:skip].should == nil
- end
-
- should "use skip provided" do
- Query.new(@collection, :skip => 2).options[:skip].should == 2
- end
-
- should "convert string to integer" do
- Query.new(@collection, :skip => '2').options[:skip].should == 2
- end
-
- should "convert offset to skip" do
- Query.new(@collection, :offset => 1).options[:skip].should == 1
- end
- end
-
- context "limit option" do
- should "default to nil" do
- Query.new(@collection, {}).options[:limit].should == nil
- end
-
- should "use limit provided" do
- Query.new(@collection, :limit => 2).options[:limit].should == 2
- end
-
- should "convert string to integer" do
- Query.new(@collection, :limit => '2').options[:limit].should == 2
- end
- end
-
- context "fields option" do
- should "default to nil" do
- Query.new(@collection, {}).options[:fields].should be(nil)
- end
-
- should "be converted to nil if empty string" do
- Query.new(@collection, :fields => '').options[:fields].should be(nil)
- end
-
- should "be converted to nil if []" do
- Query.new(@collection, :fields => []).options[:fields].should be(nil)
- end
-
- should "should work with array" do
- Query.new(@collection, :fields => %w(a b)).options[:fields].should == %w(a b)
- end
-
- should "convert comma separated list to array" do
- Query.new(@collection, :fields => 'a, b').options[:fields].should == %w(a b)
- end
-
- should "also work as select" do
- Query.new(@collection, :select => %w(a b)).options[:fields].should == %w(a b)
- end
-
- should "also work with select as array of symbols" do
- Query.new(@collection, :select => [:a, :b]).options[:fields].should == [:a, :b]
+ new_query.criteria[:fent].should == 'wick'
+ new_query.criteria[:foo].should == {'$in' => %w[bar baz]}
end
end
context "Criteria/option auto-detection" do
should "know :conditions are criteria" do
query = Query.new(@collection, :conditions => {:foo => 'bar'})
- query.criteria.should == {:foo => 'bar'}
+ query.criteria.should == CriteriaHash.new(:foo => 'bar')
query.options.keys.should_not include(:conditions)
end
@@ -593,18 +345,13 @@ class QueryTest < Test::Unit::TestCase
:limit => 10,
:skip => 10,
})
-
- query.criteria.should == {
- :foo => 'bar',
- :baz => true,
- }
-
- query.options.should == {
+ query.criteria.should == CriteriaHash.new(:foo => 'bar', :baz => true)
+ query.options.should == OptionsHash.new({
:sort => [['foo', 1]],
:fields => ['foo', 'baz'],
:limit => 10,
:skip => 10,
- }
+ })
end
end
end
Please sign in to comment.
Something went wrong with that request. Please try again.