plucky 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,302 @@
1
+ require 'helper'
2
+
3
+ class OptionsHashTest < Test::Unit::TestCase
4
+ include Plucky
5
+
6
+ context "Plucky::OptionsHash" do
7
+ should "delegate missing methods to the source hash" do
8
+ hash = {:limit => 1, :skip => 1}
9
+ options = OptionsHash.new(hash)
10
+ options[:skip].should == 1
11
+ options[:limit].should == 1
12
+ options.keys.should == [:limit, :skip]
13
+ end
14
+
15
+ context "#initialize_copy" do
16
+ setup do
17
+ @original = OptionsHash.new(:fields => {:name => true}, :sort => :name, :limit => 10)
18
+ @cloned = @original.clone
19
+ end
20
+
21
+ should "duplicate source hash" do
22
+ @cloned.source.should_not equal(@original.source)
23
+ end
24
+
25
+ should "clone duplicable? values" do
26
+ @cloned[:fields].should_not equal(@original[:fields])
27
+ @cloned[:sort].should_not equal(@original[:sort])
28
+ end
29
+ end
30
+
31
+ context "#fields?" do
32
+ should "be true if fields have been selected" do
33
+ OptionsHash.new(:fields => :name).fields?.should be(true)
34
+ end
35
+
36
+ should "be false if no fields have been selected" do
37
+ OptionsHash.new.fields?.should be(false)
38
+ end
39
+ end
40
+
41
+ context "#[]=" do
42
+ should "convert order to sort" do
43
+ options = OptionsHash.new(:order => :foo)
44
+ options[:order].should be_nil
45
+ options[:sort].should == [['foo', 1]]
46
+ end
47
+
48
+ should "convert select to fields" do
49
+ options = OptionsHash.new(:select => 'foo')
50
+ options[:select].should be_nil
51
+ options[:fields].should == ['foo']
52
+ end
53
+
54
+ should "convert offset to skip" do
55
+ options = OptionsHash.new(:offset => 1)
56
+ options[:offset].should be_nil
57
+ options[:skip].should == 1
58
+ end
59
+
60
+ context ":fields" do
61
+ setup { @options = OptionsHash.new }
62
+ subject { @options }
63
+
64
+ should "default to nil" do
65
+ subject[:fields].should be_nil
66
+ end
67
+
68
+ should "be nil if empty string" do
69
+ subject[:fields] = ''
70
+ subject[:fields].should be_nil
71
+ end
72
+
73
+ should "be nil if empty array" do
74
+ subject[:fields] = []
75
+ subject[:fields].should be_nil
76
+ end
77
+
78
+ should "work with array" do
79
+ subject[:fields] = %w[one two]
80
+ subject[:fields].should == %w[one two]
81
+ end
82
+
83
+ # Ruby 1.9.1 was sending array [{:age => 20}],
84
+ # instead of hash.
85
+ should "work with array that has one hash" do
86
+ subject[:fields] = [{:age => 20}]
87
+ subject[:fields].should == {:age => 20}
88
+ end
89
+
90
+ should "flatten multi-dimensional array" do
91
+ subject[:fields] = [[:one, :two]]
92
+ subject[:fields].should == [:one, :two]
93
+ end
94
+
95
+ should "work with symbol" do
96
+ subject[:fields] = :one
97
+ subject[:fields].should == [:one]
98
+ end
99
+
100
+ should "work with array of symbols" do
101
+ subject[:fields] = [:one, :two]
102
+ subject[:fields].should == [:one, :two]
103
+ end
104
+
105
+ should "work with hash" do
106
+ subject[:fields] = {:one => 1, :two => -1}
107
+ subject[:fields].should == {:one => 1, :two => -1}
108
+ end
109
+
110
+ should "convert comma separated list to array" do
111
+ subject[:fields] = 'one, two'
112
+ subject[:fields].should == %w[one two]
113
+ end
114
+
115
+ should "convert select" do
116
+ subject[:select] = 'one, two'
117
+ subject[:select].should be_nil
118
+ subject[:fields].should == %w[one two]
119
+ end
120
+ end
121
+
122
+ context ":limit" do
123
+ setup { @options = OptionsHash.new }
124
+ subject { @options }
125
+
126
+ should "default to nil" do
127
+ subject[:limit].should be_nil
128
+ end
129
+
130
+ should "use limit provided" do
131
+ subject[:limit] = 1
132
+ subject[:limit].should == 1
133
+ end
134
+
135
+ should "convert string to integer" do
136
+ subject[:limit] = '1'
137
+ subject[:limit].should == 1
138
+ end
139
+ end
140
+
141
+ context ":skip" do
142
+ setup { @options = OptionsHash.new }
143
+ subject { @options }
144
+
145
+ should "default to nil" do
146
+ subject[:skip].should be_nil
147
+ end
148
+
149
+ should "use limit provided" do
150
+ subject[:skip] = 1
151
+ subject[:skip].should == 1
152
+ end
153
+
154
+ should "convert string to integer" do
155
+ subject[:skip] = '1'
156
+ subject[:skip].should == 1
157
+ end
158
+
159
+ should "be set from offset" do
160
+ subject[:offset] = '1'
161
+ subject[:offset].should be_nil
162
+ subject[:skip].should == 1
163
+ end
164
+ end
165
+
166
+ context ":sort" do
167
+ setup { @options = OptionsHash.new }
168
+ subject { @options }
169
+
170
+ should "default to nil" do
171
+ subject[:sort].should be_nil
172
+ end
173
+
174
+ should "work with natural order ascending" do
175
+ subject[:sort] = {'$natural' => 1}
176
+ subject[:sort].should == {'$natural' => 1}
177
+ end
178
+
179
+ should "work with natural order descending" do
180
+ subject[:sort] = {'$natural' => -1}
181
+ subject[:sort].should =={'$natural' => -1}
182
+ end
183
+
184
+ should "convert single ascending field (string)" do
185
+ subject[:sort] = 'foo asc'
186
+ subject[:sort].should == [['foo', 1]]
187
+
188
+ subject[:sort] = 'foo ASC'
189
+ subject[:sort].should == [['foo', 1]]
190
+ end
191
+
192
+ should "convert single descending field (string)" do
193
+ subject[:sort] = 'foo desc'
194
+ subject[:sort].should == [['foo', -1]]
195
+
196
+ subject[:sort] = 'foo DESC'
197
+ subject[:sort].should == [['foo', -1]]
198
+ end
199
+
200
+ should "convert multiple fields (string)" do
201
+ subject[:sort] = 'foo desc, bar asc'
202
+ subject[:sort].should == [['foo', -1], ['bar', 1]]
203
+ end
204
+
205
+ should "convert multiple fields and default no direction to ascending (string)" do
206
+ subject[:sort] = 'foo desc, bar, baz'
207
+ subject[:sort].should == [['foo', -1], ['bar', 1], ['baz', 1]]
208
+ end
209
+
210
+ should "convert symbol" do
211
+ subject[:sort] = :name
212
+ subject[:sort] = [['name', 1]]
213
+ end
214
+
215
+ should "convert operator" do
216
+ subject[:sort] = :foo.desc
217
+ subject[:sort].should == [['foo', -1]]
218
+ end
219
+
220
+ should "convert array of operators" do
221
+ subject[:sort] = [:foo.desc, :bar.asc]
222
+ subject[:sort].should == [['foo', -1], ['bar', 1]]
223
+ end
224
+
225
+ should "convert array of symbols" do
226
+ subject[:sort] = [:first_name, :last_name]
227
+ subject[:sort] = [['first_name', 1], ['last_name', 1]]
228
+ end
229
+
230
+ should "work with array and one string element" do
231
+ subject[:sort] = ['foo, bar desc']
232
+ subject[:sort].should == [['foo', 1], ['bar', -1]]
233
+ end
234
+
235
+ should "work with array of single array" do
236
+ subject[:sort] = [['foo', -1]]
237
+ subject[:sort].should == [['foo', -1]]
238
+ end
239
+
240
+ should "work with array of multiple arrays" do
241
+ subject[:sort] = [['foo', -1], ['bar', 1]]
242
+ subject[:sort].should == [['foo', -1], ['bar', 1]]
243
+ end
244
+
245
+ should "compact nil values in array" do
246
+ subject[:sort] = [nil, :foo.desc]
247
+ subject[:sort].should == [['foo', -1]]
248
+ end
249
+
250
+ should "convert array with mix of values" do
251
+ subject[:sort] = [:foo.desc, 'bar']
252
+ subject[:sort].should == [['foo', -1], ['bar', 1]]
253
+ end
254
+
255
+ should "convert id to _id" do
256
+ subject[:sort] = [:id.asc]
257
+ subject[:sort].should == [['_id', 1]]
258
+ end
259
+
260
+ should "convert string with $natural correctly" do
261
+ subject[:sort] = '$natural desc'
262
+ subject[:sort].should == [['$natural', -1]]
263
+ end
264
+ end
265
+ end
266
+ end
267
+
268
+ context "#merge" do
269
+ setup do
270
+ @o1 = OptionsHash.new(:skip => 5, :sort => :name)
271
+ @o2 = OptionsHash.new(:limit => 10, :skip => 15)
272
+ @merged = @o1.merge(@o2)
273
+ end
274
+
275
+ should "override options in first with options in second" do
276
+ @merged.should == OptionsHash.new(:limit => 10, :skip => 15, :sort => :name)
277
+ end
278
+
279
+ should "return new instance and not change either of the merged" do
280
+ @o1[:skip].should == 5
281
+ @o2[:sort].should be_nil
282
+ @merged.should_not equal(@o1)
283
+ @merged.should_not equal(@o2)
284
+ end
285
+ end
286
+
287
+ context "#merge!" do
288
+ setup do
289
+ @o1 = OptionsHash.new(:skip => 5, :sort => :name)
290
+ @o2 = OptionsHash.new(:limit => 10, :skip => 15)
291
+ @merged = @o1.merge!(@o2)
292
+ end
293
+
294
+ should "override options in first with options in second" do
295
+ @merged.should == OptionsHash.new(:limit => 10, :skip => 15, :sort => :name)
296
+ end
297
+
298
+ should "just update the first" do
299
+ @merged.should equal(@o1)
300
+ end
301
+ end
302
+ end
@@ -0,0 +1,819 @@
1
+ require 'helper'
2
+
3
+ class QueryTest < Test::Unit::TestCase
4
+ context "Query" do
5
+ include Plucky
6
+
7
+ setup do
8
+ @chris = oh(['_id', 'chris'], ['age', 26], ['name', 'Chris'])
9
+ @steve = oh(['_id', 'steve'], ['age', 29], ['name', 'Steve'])
10
+ @john = oh(['_id', 'john'], ['age', 28], ['name', 'John'])
11
+ @collection = DB['users']
12
+ @collection.insert(@chris)
13
+ @collection.insert(@steve)
14
+ @collection.insert(@john)
15
+ end
16
+
17
+ context "#initialize" do
18
+ setup { @query = Query.new(@collection) }
19
+ subject { @query }
20
+
21
+ should "default options to options hash" do
22
+ @query.options.should be_instance_of(OptionsHash)
23
+ end
24
+
25
+ should "default criteria to criteria hash" do
26
+ @query.criteria.should be_instance_of(CriteriaHash)
27
+ end
28
+ end
29
+
30
+ context "#initialize_copy" do
31
+ setup do
32
+ @original = Query.new(@collection)
33
+ @cloned = @original.clone
34
+ end
35
+
36
+ should "duplicate options" do
37
+ @cloned.options.should_not equal(@original.options)
38
+ end
39
+
40
+ should "duplicate criteria" do
41
+ @cloned.criteria.should_not equal(@original.criteria)
42
+ end
43
+ end
44
+
45
+ context "#[]=" do
46
+ setup { @query = Query.new(@collection) }
47
+ subject { @query }
48
+
49
+ should "set key on options for option" do
50
+ subject[:skip] = 1
51
+ subject[:skip].should == 1
52
+ end
53
+
54
+ should "set key on criteria for criteria" do
55
+ subject[:foo] = 'bar'
56
+ subject[:foo].should == 'bar'
57
+ end
58
+ end
59
+
60
+ context "#find_each" do
61
+ should "return a cursor" do
62
+ cursor = Query.new(@collection).find_each
63
+ cursor.should be_instance_of(Mongo::Cursor)
64
+ end
65
+
66
+ should "work with and normalize criteria" do
67
+ cursor = Query.new(@collection).find_each(:id.in => ['john'])
68
+ cursor.to_a.should == [@john]
69
+ end
70
+
71
+ should "work with and normalize options" do
72
+ cursor = Query.new(@collection).find_each(:order => :name.asc)
73
+ cursor.to_a.should == [@chris, @john, @steve]
74
+ end
75
+ end
76
+
77
+ context "#find_one" do
78
+ should "work with and normalize criteria" do
79
+ Query.new(@collection).find_one(:id.in => ['john']).should == @john
80
+ end
81
+
82
+ should "work with and normalize options" do
83
+ Query.new(@collection).find_one(:order => :age.desc).should == @steve
84
+ end
85
+ end
86
+
87
+ context "#find" do
88
+ setup do
89
+ @query = Query.new(@collection)
90
+ end
91
+ subject { @query }
92
+
93
+ should "work with single id" do
94
+ @query.find('chris').should == @chris
95
+ end
96
+
97
+ should "work with multiple ids" do
98
+ @query.find('chris', 'john').should == [@chris, @john]
99
+ end
100
+
101
+ should "work with array of one id" do
102
+ @query.find(['chris']).should == [@chris]
103
+ end
104
+
105
+ should "work with array of ids" do
106
+ @query.find(['chris', 'john']).should == [@chris, @john]
107
+ end
108
+
109
+ should "ignore those not found" do
110
+ @query.find('john', 'frank').should == [@john]
111
+ end
112
+
113
+ should "return nil for nil" do
114
+ @query.find(nil).should be_nil
115
+ end
116
+
117
+ should "return nil for *nil" do
118
+ @query.find(*nil).should be_nil
119
+ end
120
+
121
+ should "normalize if using object id" do
122
+ id = @collection.insert(:name => 'Frank')
123
+ @query.object_ids([:_id])
124
+ doc = @query.find(id.to_s)
125
+ doc['name'].should == 'Frank'
126
+ end
127
+ end
128
+
129
+ context "#per_page" do
130
+ should "default to 25" do
131
+ Query.new(@collection).per_page.should == 25
132
+ end
133
+
134
+ should "be changeable and chainable" do
135
+ query = Query.new(@collection)
136
+ query.per_page(10).per_page.should == 10
137
+ end
138
+ end
139
+
140
+ context "#paginate" do
141
+ setup do
142
+ @query = Query.new(@collection).sort(:age).per_page(1)
143
+ end
144
+ subject { @query }
145
+
146
+ should "default to page 1" do
147
+ subject.paginate.should == [@chris]
148
+ end
149
+
150
+ should "work with other pages" do
151
+ subject.paginate(:page => 2).should == [@john]
152
+ subject.paginate(:page => 3).should == [@steve]
153
+ end
154
+
155
+ should "work with string page number" do
156
+ subject.paginate(:page => '2').should == [@john]
157
+ end
158
+
159
+ should "allow changing per_page" do
160
+ subject.paginate(:per_page => 2).should == [@chris, @john]
161
+ end
162
+
163
+ should "decorate return value" do
164
+ docs = subject.paginate
165
+ docs.should respond_to(:paginator)
166
+ docs.should respond_to(:total_entries)
167
+ end
168
+
169
+ should "not modify the original query" do
170
+ subject.paginate(:name => 'John')
171
+ subject[:page].should be_nil
172
+ subject[:per_page].should be_nil
173
+ subject[:name].should be_nil
174
+ end
175
+
176
+ context "with options" do
177
+ setup do
178
+ @result = @query.sort(:age).paginate(:age.gt => 27, :per_page => 10)
179
+ end
180
+ subject { @result }
181
+
182
+ should "only return matching" do
183
+ subject.should == [@john, @steve]
184
+ end
185
+
186
+ should "correctly count matching" do
187
+ subject.total_entries.should == 2
188
+ end
189
+ end
190
+ end
191
+
192
+ context "#all" do
193
+ should "work with no arguments" do
194
+ docs = Query.new(@collection).all
195
+ docs.size.should == 3
196
+ docs.should include(@john)
197
+ docs.should include(@steve)
198
+ docs.should include(@chris)
199
+ end
200
+
201
+ should "work with and normalize criteria" do
202
+ docs = Query.new(@collection).all(:id.in => ['steve'])
203
+ docs.should == [@steve]
204
+ end
205
+
206
+ should "work with and normalize options" do
207
+ docs = Query.new(@collection).all(:order => :name.asc)
208
+ docs.should == [@chris, @john, @steve]
209
+ end
210
+
211
+ should "not modify original query object" do
212
+ query = Query.new(@collection)
213
+ query.all(:name => 'Steve')
214
+ query[:name].should be_nil
215
+ end
216
+ end
217
+
218
+ context "#first" do
219
+ should "work with and normalize criteria" do
220
+ Query.new(@collection).first(:age.lt => 29).should == @chris
221
+ end
222
+
223
+ should "work with and normalize options" do
224
+ Query.new(@collection).first(:age.lte => 29, :order => :name.desc).should == @steve
225
+ end
226
+
227
+ should "not modify original query object" do
228
+ query = Query.new(@collection)
229
+ query.first(:name => 'Steve')
230
+ query[:name].should be_nil
231
+ end
232
+ end
233
+
234
+ context "#last" do
235
+ should "work with and normalize criteria" do
236
+ Query.new(@collection).last(:age.lte => 29, :order => :name.asc).should == @steve
237
+ end
238
+
239
+ should "work with and normalize options" do
240
+ Query.new(@collection).last(:age.lte => 26, :order => :name.desc).should == @chris
241
+ end
242
+
243
+ should "not modify original query object" do
244
+ query = Query.new(@collection)
245
+ query.last(:name => 'Steve')
246
+ query[:name].should be_nil
247
+ end
248
+ end
249
+
250
+ context "#count" do
251
+ should "work with no arguments" do
252
+ Query.new(@collection).count.should == 3
253
+ end
254
+
255
+ should "work with and normalize criteria" do
256
+ Query.new(@collection).count(:age.lte => 28).should == 2
257
+ end
258
+
259
+ should "not modify original query object" do
260
+ query = Query.new(@collection)
261
+ query.count(:name => 'Steve')
262
+ query[:name].should be_nil
263
+ end
264
+ end
265
+
266
+ context "#size" do
267
+ should "work just like count without options" do
268
+ Query.new(@collection).size.should == 3
269
+ end
270
+ end
271
+
272
+ context "#distinct" do
273
+ setup do
274
+ # same age as John
275
+ @mark = oh(['_id', 'mark'], ['age', 28], ['name', 'Mark'])
276
+ @collection.insert(@mark)
277
+ end
278
+
279
+ should "work with just a key" do
280
+ Query.new(@collection).distinct(:age).sort.should == [26, 28, 29]
281
+ end
282
+
283
+ should "work with criteria" do
284
+ Query.new(@collection).distinct(:age, :age.gt => 26).sort.should == [28, 29]
285
+ end
286
+
287
+ should "not modify the original query object" do
288
+ query = Query.new(@collection)
289
+ query.distinct(:age, :name => 'Mark').should == [28]
290
+ query[:name].should be_nil
291
+ end
292
+ end
293
+
294
+ context "#remove" do
295
+ should "work with no arguments" do
296
+ lambda { Query.new(@collection).remove }.should change { @collection.count }.by(3)
297
+ end
298
+
299
+ should "work with and normalize criteria" do
300
+ lambda { Query.new(@collection).remove(:age.lte => 28) }.should change { @collection.count }
301
+ end
302
+
303
+ should "work with options" do
304
+ lambda { Query.new(@collection).remove({:age.lte => 28}, :safe => true) }.should change { @collection.count }
305
+ end
306
+
307
+ should "not modify original query object" do
308
+ query = Query.new(@collection)
309
+ query.remove(:name => 'Steve')
310
+ query[:name].should be_nil
311
+ end
312
+ end
313
+
314
+ context "#update" do
315
+ setup do
316
+ @query = Query.new(@collection).where('_id' => 'john')
317
+ end
318
+
319
+ should "work with document" do
320
+ @query.update('$set' => {'age' => 29})
321
+ doc = @query.first('_id' => 'john')
322
+ doc['age'].should be(29)
323
+ end
324
+
325
+ should "work with document and driver options" do
326
+ @query.update({'$set' => {'age' => 30}}, :multi => true)
327
+ @query.each do |doc|
328
+ doc['age'].should be(30)
329
+ end
330
+ end
331
+ end
332
+
333
+ context "#[]" do
334
+ should "return value if key in criteria (symbol)" do
335
+ Query.new(@collection, :count => 1)[:count].should == 1
336
+ end
337
+
338
+ should "return value if key in criteria (string)" do
339
+ Query.new(@collection, :count => 1)['count'].should == 1
340
+ end
341
+
342
+ should "return nil if key not in criteria" do
343
+ Query.new(@collection)[:count].should be_nil
344
+ end
345
+ end
346
+
347
+ context "#[]=" do
348
+ setup { @query = Query.new(@collection) }
349
+
350
+ should "set the value of the given criteria key" do
351
+ @query[:count] = 1
352
+ @query[:count].should == 1
353
+ end
354
+
355
+ should "overwrite value if key already exists" do
356
+ @query[:count] = 1
357
+ @query[:count] = 2
358
+ @query[:count].should == 2
359
+ end
360
+
361
+ should "normalize value" do
362
+ now = Time.now
363
+ @query[:published_at] = now
364
+ @query[:published_at].should == now.utc
365
+ end
366
+ end
367
+
368
+ context "#fields" do
369
+ setup { @query = Query.new(@collection) }
370
+ subject { @query }
371
+
372
+ should "work" do
373
+ subject.fields(:name).first(:id => 'john').keys.should == ['_id', 'name']
374
+ end
375
+
376
+ should "return new instance of query" do
377
+ new_query = subject.fields(:name)
378
+ new_query.should_not equal(subject)
379
+ subject[:fields].should be_nil
380
+ end
381
+
382
+ should "work with hash" do
383
+ subject.fields(:name => 0).
384
+ first(:id => 'john').keys.sort.
385
+ should == ['_id', 'age']
386
+ end
387
+ end
388
+
389
+ context "#ignore" do
390
+ setup {@query = Query.new(@collection)}
391
+ subject {@query}
392
+
393
+ should "include a list of keys to ignore" do
394
+ new_query = subject.ignore(:name).first(:id => 'john')
395
+ new_query.keys.should == ['_id', 'age']
396
+ end
397
+ end
398
+
399
+ context "#only" do
400
+ setup {@query = Query.new(@collection)}
401
+ subject {@query}
402
+
403
+ should "inclue a list of keys with others excluded" do
404
+ new_query = subject.only(:name).first(:id => 'john')
405
+ new_query.keys.should == ['_id', 'name']
406
+ end
407
+
408
+ end
409
+
410
+ context "#skip" do
411
+ setup { @query = Query.new(@collection) }
412
+ subject { @query }
413
+
414
+ should "work" do
415
+ subject.skip(2).all(:order => :age).should == [@steve]
416
+ end
417
+
418
+ should "set skip option" do
419
+ subject.skip(5).options[:skip].should == 5
420
+ end
421
+
422
+ should "override existing skip" do
423
+ subject.skip(5).skip(10).options[:skip].should == 10
424
+ end
425
+
426
+ should "return nil for nil" do
427
+ subject.skip.options[:skip].should be_nil
428
+ end
429
+
430
+ should "return new instance of query" do
431
+ new_query = subject.skip(2)
432
+ new_query.should_not equal(subject)
433
+ subject[:skip].should be_nil
434
+ end
435
+
436
+ should "alias to offset" do
437
+ subject.offset(5).options[:skip].should == 5
438
+ end
439
+ end
440
+
441
+ context "#limit" do
442
+ setup { @query = Query.new(@collection) }
443
+ subject { @query }
444
+
445
+ should "work" do
446
+ subject.limit(2).all(:order => :age).should == [@chris, @john]
447
+ end
448
+
449
+ should "set limit option" do
450
+ subject.limit(5).options[:limit].should == 5
451
+ end
452
+
453
+ should "overwrite existing limit" do
454
+ subject.limit(5).limit(15).options[:limit].should == 15
455
+ end
456
+
457
+ should "return new instance of query" do
458
+ new_query = subject.limit(2)
459
+ new_query.should_not equal(subject)
460
+ subject[:limit].should be_nil
461
+ end
462
+ end
463
+
464
+ context "#sort" do
465
+ setup { @query = Query.new(@collection) }
466
+ subject { @query }
467
+
468
+ should "work" do
469
+ subject.sort(:age).all.should == [@chris, @john, @steve]
470
+ subject.sort(:age.desc).all.should == [@steve, @john, @chris]
471
+ end
472
+
473
+ should "work with symbol operators" do
474
+ subject.sort(:foo.asc, :bar.desc).options[:sort].should == [['foo', 1], ['bar', -1]]
475
+ end
476
+
477
+ should "work with string" do
478
+ subject.sort('foo, bar desc').options[:sort].should == [['foo', 1], ['bar', -1]]
479
+ end
480
+
481
+ should "work with just a symbol" do
482
+ subject.sort(:foo).options[:sort].should == [['foo', 1]]
483
+ end
484
+
485
+ should "work with symbol descending" do
486
+ subject.sort(:foo.desc).options[:sort].should == [['foo', -1]]
487
+ end
488
+
489
+ should "work with multiple symbols" do
490
+ subject.sort(:foo, :bar).options[:sort].should == [['foo', 1], ['bar', 1]]
491
+ end
492
+
493
+ should "return new instance of query" do
494
+ new_query = subject.sort(:name)
495
+ new_query.should_not equal(subject)
496
+ subject[:sort].should be_nil
497
+ end
498
+
499
+ should "be aliased to order" do
500
+ subject.order(:foo).options[:sort].should == [['foo', 1]]
501
+ subject.order(:foo, :bar).options[:sort].should == [['foo', 1], ['bar', 1]]
502
+ end
503
+ end
504
+
505
+ context "#reverse" do
506
+ setup { @query = Query.new(@collection) }
507
+ subject { @query }
508
+
509
+ should "work" do
510
+ subject.sort(:age).reverse.all.should == [@steve, @john, @chris]
511
+ end
512
+
513
+ should "not error if no sort provided" do
514
+ assert_nothing_raised do
515
+ subject.reverse
516
+ end
517
+ end
518
+
519
+ should "reverse the sort order" do
520
+ subject.sort('foo asc, bar desc').
521
+ reverse.options[:sort].should == [['foo', -1], ['bar', 1]]
522
+ end
523
+
524
+ should "return new instance of query" do
525
+ sorted_query = subject.sort(:name)
526
+ new_query = sorted_query.reverse
527
+ new_query.should_not equal(sorted_query)
528
+ sorted_query[:sort].should == [['name', 1]]
529
+ end
530
+ end
531
+
532
+ context "#amend" do
533
+ should "normalize and update options" do
534
+ Query.new(@collection).amend(:order => :age.desc).options[:sort].should == [['age', -1]]
535
+ end
536
+
537
+ should "work with simple stuff" do
538
+ Query.new(@collection).
539
+ amend(:foo => 'bar').
540
+ amend(:baz => 'wick').
541
+ criteria.should == CriteriaHash.new(:foo => 'bar', :baz => 'wick')
542
+ end
543
+ end
544
+
545
+ context "#where" do
546
+ setup { @query = Query.new(@collection) }
547
+ subject { @query }
548
+
549
+ should "work" do
550
+ subject.where(:age.lt => 29).where(:name => 'Chris').all.should == [@chris]
551
+ end
552
+
553
+ should "work with literal regexp" do
554
+ subject.where(:name => /^c/i).all.should == [@chris]
555
+ end
556
+
557
+ should "update criteria" do
558
+ subject.
559
+ where(:moo => 'cow').
560
+ where(:foo => 'bar').
561
+ criteria.should == CriteriaHash.new(:foo => 'bar', :moo => 'cow')
562
+ end
563
+
564
+ should "get normalized" do
565
+ subject.
566
+ where(:moo => 'cow').
567
+ where(:foo.in => ['bar']).
568
+ criteria.should == CriteriaHash.new(:moo => 'cow', :foo => {'$in' => ['bar']})
569
+ end
570
+
571
+ should "normalize merged criteria" do
572
+ subject.
573
+ where(:foo => 'bar').
574
+ where(:foo => 'baz').
575
+ criteria.should == CriteriaHash.new(:foo => {'$in' => %w[bar baz]})
576
+ end
577
+
578
+ should "return new instance of query" do
579
+ new_query = subject.where(:name => 'John')
580
+ new_query.should_not equal(subject)
581
+ subject[:name].should be_nil
582
+ end
583
+ end
584
+
585
+ context "#filter" do
586
+ setup { @query = Query.new(@collection) }
587
+ subject { @query }
588
+
589
+ should "work the same as where" do
590
+ subject.filter(:age.lt => 29).filter(:name => 'Chris').all.should == [@chris]
591
+ end
592
+ end
593
+
594
+ context "#empty?" do
595
+ should "be true if empty" do
596
+ @collection.remove
597
+ Query.new(@collection).should be_empty
598
+ end
599
+
600
+ should "be false if not empty" do
601
+ Query.new(@collection).should_not be_empty
602
+ end
603
+ end
604
+
605
+ context "#exists?" do
606
+ should "be true if found" do
607
+ Query.new(@collection).exists?(:name => 'John').should be(true)
608
+ end
609
+
610
+ should "be false if not found" do
611
+ Query.new(@collection).exists?(:name => 'Billy Bob').should be(false)
612
+ end
613
+ end
614
+
615
+ context "#exist?" do
616
+ should "be true if found" do
617
+ Query.new(@collection).exist?(:name => 'John').should be(true)
618
+ end
619
+
620
+ should "be false if not found" do
621
+ Query.new(@collection).exist?(:name => 'Billy Bob').should be(false)
622
+ end
623
+ end
624
+
625
+ context "#include?" do
626
+ should "be true if included" do
627
+ Query.new(@collection).include?(@john).should be(true)
628
+ end
629
+
630
+ should "be false if not included" do
631
+ Query.new(@collection).include?(['_id', 'frankyboy']).should be(false)
632
+ end
633
+ end
634
+
635
+ context "#to_a" do
636
+ should "return all documents the query matches" do
637
+ Query.new(@collection).sort(:name).to_a.
638
+ should == [@chris, @john, @steve]
639
+
640
+ Query.new(@collection).where(:name => 'John').sort(:name).to_a.
641
+ should == [@john]
642
+ end
643
+ end
644
+
645
+ context "#each" do
646
+ should "iterate through matching documents" do
647
+ docs = []
648
+ Query.new(@collection).sort(:name).each do |doc|
649
+ docs << doc
650
+ end
651
+ docs.should == [@chris, @john, @steve]
652
+ end
653
+ end
654
+
655
+ context "enumerables" do
656
+ should "work" do
657
+ query = Query.new(@collection).sort(:name)
658
+ query.map { |doc| doc['name'] }.should == %w(Chris John Steve)
659
+ query.collect { |doc| doc['name'] }.should == %w(Chris John Steve)
660
+ query.detect { |doc| doc['name'] == 'John' }.should == @john
661
+ query.min { |a, b| a['age'] <=> b['age'] }.should == @chris
662
+ end
663
+ end
664
+
665
+ context "#object_ids" do
666
+ setup { @query = Query.new(@collection) }
667
+ subject { @query }
668
+
669
+ should "set criteria's object_ids" do
670
+ subject.criteria.expects(:object_ids=).with([:foo, :bar])
671
+ subject.object_ids(:foo, :bar)
672
+ end
673
+
674
+ should "return current object ids if keys argument is empty" do
675
+ subject.object_ids(:foo, :bar)
676
+ subject.object_ids.should == [:foo, :bar]
677
+ end
678
+ end
679
+
680
+ context "#merge" do
681
+ should "overwrite options" do
682
+ query1 = Query.new(@collection, :skip => 5, :limit => 5)
683
+ query2 = Query.new(@collection, :skip => 10, :limit => 10)
684
+ new_query = query1.merge(query2)
685
+ new_query.options[:skip].should == 10
686
+ new_query.options[:limit].should == 10
687
+ end
688
+
689
+ should "merge criteria" do
690
+ query1 = Query.new(@collection, :foo => 'bar')
691
+ query2 = Query.new(@collection, :foo => 'baz', :fent => 'wick')
692
+ new_query = query1.merge(query2)
693
+ new_query.criteria[:fent].should == 'wick'
694
+ new_query.criteria[:foo].should == {'$in' => %w[bar baz]}
695
+ end
696
+
697
+ should "not affect either of the merged queries" do
698
+ query1 = Query.new(@collection, :foo => 'bar', :limit => 5)
699
+ query2 = Query.new(@collection, :foo => 'baz', :limit => 10)
700
+ new_query = query1.merge(query2)
701
+ query1[:foo].should == 'bar'
702
+ query1[:limit].should == 5
703
+ query2[:foo].should == 'baz'
704
+ query2[:limit].should == 10
705
+ end
706
+ end
707
+
708
+ context "Criteria/option auto-detection" do
709
+ should "know :conditions are criteria" do
710
+ query = Query.new(@collection, :conditions => {:foo => 'bar'})
711
+ query.criteria.should == CriteriaHash.new(:foo => 'bar')
712
+ query.options.keys.should_not include(:conditions)
713
+ end
714
+
715
+ {
716
+ :fields => ['foo'],
717
+ :sort => [['foo', 1]],
718
+ :hint => '',
719
+ :skip => 0,
720
+ :limit => 0,
721
+ :batch_size => 0,
722
+ :timeout => 0,
723
+ }.each do |option, value|
724
+ should "know #{option} is an option" do
725
+ query = Query.new(@collection, option => value)
726
+ query.options[option].should == value
727
+ query.criteria.keys.should_not include(option)
728
+ end
729
+ end
730
+
731
+ should "know select is an option and remove it from options" do
732
+ query = Query.new(@collection, :select => 'foo')
733
+ query.options[:fields].should == ['foo']
734
+ query.criteria.keys.should_not include(:select)
735
+ query.options.keys.should_not include(:select)
736
+ end
737
+
738
+ should "know order is an option and remove it from options" do
739
+ query = Query.new(@collection, :order => 'foo')
740
+ query.options[:sort].should == [['foo', 1]]
741
+ query.criteria.keys.should_not include(:order)
742
+ query.options.keys.should_not include(:order)
743
+ end
744
+
745
+ should "know offset is an option and remove it from options" do
746
+ query = Query.new(@collection, :offset => 0)
747
+ query.options[:skip].should == 0
748
+ query.criteria.keys.should_not include(:offset)
749
+ query.options.keys.should_not include(:offset)
750
+ end
751
+
752
+ should "work with full range of things" do
753
+ query = Query.new(@collection, {
754
+ :foo => 'bar',
755
+ :baz => true,
756
+ :sort => [['foo', 1]],
757
+ :fields => ['foo', 'baz'],
758
+ :limit => 10,
759
+ :skip => 10,
760
+ })
761
+ query.criteria.should == CriteriaHash.new(:foo => 'bar', :baz => true)
762
+ query.options.should == OptionsHash.new({
763
+ :sort => [['foo', 1]],
764
+ :fields => ['foo', 'baz'],
765
+ :limit => 10,
766
+ :skip => 10,
767
+ })
768
+ end
769
+ end
770
+
771
+ should "inspect pretty" do
772
+ inspect = Query.new(@collection, :baz => 'wick', :foo => 'bar').inspect
773
+ inspect.should == '#<Plucky::Query baz: "wick", foo: "bar">'
774
+ end
775
+
776
+ should "delegate simple? to criteria" do
777
+ query = Query.new(@collection)
778
+ query.criteria.expects(:simple?)
779
+ query.simple?
780
+ end
781
+
782
+ should "delegate fields? to options" do
783
+ query = Query.new(@collection)
784
+ query.options.expects(:fields?)
785
+ query.fields?
786
+ end
787
+
788
+ context "#explain" do
789
+ setup { @query = Query.new(@collection) }
790
+ subject { @query }
791
+
792
+ should "work" do
793
+ explain = subject.where(:age.lt => 28).explain
794
+ explain['cursor'].should == 'BasicCursor'
795
+ explain['nscanned'].should == 3
796
+ end
797
+ end
798
+
799
+ context "Transforming documents" do
800
+ setup do
801
+ transformer = lambda { |doc| @user_class.new(doc['_id'], doc['name'], doc['age']) }
802
+ @user_class = Struct.new(:id, :name, :age)
803
+ @query = Query.new(@collection, :transformer => transformer)
804
+ end
805
+
806
+ should "work with find_one" do
807
+ result = @query.find_one('_id' => 'john')
808
+ result.should be_instance_of(@user_class)
809
+ end
810
+
811
+ should "work with find_each" do
812
+ results = @query.find_each
813
+ results.each do |result|
814
+ result.should be_instance_of(@user_class)
815
+ end
816
+ end
817
+ end
818
+ end
819
+ end