mongodoc 0.0.0 → 0.1.0

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,846 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "spec_helper.rb"))
2
+
3
+ describe MongoDoc::Criteria do
4
+ class CountryCode < MongoDoc::Document
5
+ key :code
6
+ end
7
+
8
+ class Phone < MongoDoc::Document
9
+ key :number
10
+ has_one :country_code
11
+ end
12
+
13
+ class Animal < MongoDoc::Document
14
+ key :name
15
+ end
16
+
17
+ class Address < MongoDoc::Document
18
+ key :street
19
+ key :city
20
+ key :state
21
+ key :post_code
22
+ end
23
+
24
+ class Name < MongoDoc::Document
25
+ key :first_name
26
+ key :last_name
27
+ end
28
+
29
+ class Person < MongoDoc::Document
30
+ key :title
31
+ key :terms
32
+ key :age
33
+ key :dob
34
+ key :mixed_drink
35
+ key :employer_id
36
+ key :lunch_time
37
+
38
+ has_many :addresses
39
+ has_many :phone_numbers, :class_name => "Phone"
40
+
41
+ has_one :name
42
+ has_one :pet
43
+
44
+ def update_addresses
45
+ addresses.each_with_index do |address, i|
46
+ address.street = "Updated #{i}"
47
+ end
48
+ end
49
+
50
+ def employer=(emp)
51
+ self.employer_id = emp.id
52
+ end
53
+
54
+ class << self
55
+ def accepted
56
+ criteria.where(:terms => true)
57
+ end
58
+ def knight
59
+ criteria.where(:title => "Sir")
60
+ end
61
+ def old
62
+ criteria.where(:age => { "$gt" => 50 })
63
+ end
64
+ end
65
+
66
+ end
67
+
68
+ before do
69
+ @criteria = MongoDoc::Criteria.new(Person)
70
+ end
71
+
72
+ describe "#aggregate" do
73
+
74
+ context "when klass provided" do
75
+
76
+ before do
77
+ @reduce = "function(obj, prev) { prev.count++; }"
78
+ @collection = mock
79
+ Animal.should_receive(:collection).and_return(@collection)
80
+ end
81
+
82
+ it "calls group on the collection with the aggregate js" do
83
+ @collection.should_receive(:group).with([:field1], {}, {:count => 0}, @reduce)
84
+ @criteria.select(:field1).aggregate(Animal)
85
+ end
86
+
87
+ end
88
+
89
+ context "when klass not provided" do
90
+
91
+ before do
92
+ @reduce = "function(obj, prev) { prev.count++; }"
93
+ @collection = mock
94
+ Person.should_receive(:collection).and_return(@collection)
95
+ end
96
+
97
+ it "calls group on the collection with the aggregate js" do
98
+ @collection.should_receive(:group).with([:field1], {}, {:count => 0}, @reduce)
99
+ @criteria.select(:field1).aggregate
100
+ end
101
+
102
+ end
103
+
104
+ end
105
+
106
+ describe "#every" do
107
+
108
+ it "adds the $all query to the selector" do
109
+ @criteria.every(:title => ["title1", "title2"])
110
+ @criteria.selector.should == { :title => { "$all" => ["title1", "title2"] } }
111
+ end
112
+
113
+ it "and_return self" do
114
+ @criteria.every(:title => [ "title1" ]).should == @criteria
115
+ end
116
+
117
+ end
118
+
119
+ describe "including Enumerable" do
120
+
121
+ before do
122
+ @cursor = stub('cursor').as_null_object
123
+ @criteria.stub(:execute).and_return(@cursor)
124
+ end
125
+
126
+ it "calls each" do
127
+ @cursor.should_receive(:each).and_return(@cursor)
128
+ @criteria.collect
129
+ end
130
+
131
+ end
132
+
133
+ describe "#count" do
134
+
135
+ context "when criteria has not been executed" do
136
+
137
+ before do
138
+ @count = 27
139
+ @cursor = stub('cursor', :count => @count)
140
+ Person.stub(:collection).and_return(@collection)
141
+ end
142
+
143
+ it "calls through to the collection" do
144
+ @collection.should_receive(:find).and_return(@cursor)
145
+ @criteria.count
146
+ end
147
+
148
+ it "returns the count from the cursor" do
149
+ @collection.stub(:find).and_return(@cursor)
150
+ @criteria.count.should == @count
151
+ end
152
+
153
+ end
154
+
155
+ context "when criteria has been executed" do
156
+
157
+ before do
158
+ @count = 28
159
+ @criteria.instance_variable_set(:@count, @count)
160
+ end
161
+
162
+ it "returns the count from the cursor without creating the documents" do
163
+ @criteria.count.should == @count
164
+ end
165
+
166
+ end
167
+
168
+ end
169
+
170
+ describe "#each" do
171
+
172
+ before do
173
+ @criteria.where(:title => "Sir")
174
+ @person = Person.new(:title => "Sir")
175
+ @cursor = [@person]
176
+ end
177
+
178
+ context "when no block given" do
179
+ it "executes the criteria" do
180
+ @criteria.should_receive(:execute).and_return(@cursor)
181
+ @criteria.each
182
+ end
183
+
184
+ it "returns self" do
185
+ @criteria.stub(:execute).and_return(@cursor)
186
+ @criteria.each.should == @criteria
187
+ end
188
+ end
189
+
190
+ context "when a block is given" do
191
+ context "when the criteria has not been executed" do
192
+ it "executes the criteria" do
193
+ @criteria.should_receive(:execute).and_return(@cursor)
194
+ @criteria.each do |person|
195
+ person.should == @person
196
+ end
197
+ end
198
+
199
+ it "yields into the block" do
200
+ @criteria.stub(:execute).and_return(@cursor)
201
+ @criteria.each do |person|
202
+ @result = person
203
+ end
204
+ @result.should == @person
205
+ end
206
+
207
+ it "returns self" do
208
+ @criteria.stub(:execute).and_return(@cursor)
209
+ @criteria.each.should == @criteria
210
+ end
211
+ end
212
+
213
+ context "when the criteria has been executed" do
214
+ before do
215
+ @criteria.stub(:execute).and_return(@cursor)
216
+ @criteria.each
217
+ end
218
+
219
+ it "does not execute the criteria" do
220
+ @criteria.should_not_receive(:execute)
221
+ @criteria.each do |person|
222
+ person.should == @person
223
+ end
224
+ end
225
+
226
+ it "yields into the block" do
227
+ @criteria.each do |person|
228
+ @result = person
229
+ end
230
+ @result.should == @person
231
+ end
232
+
233
+ it "returns self" do
234
+ @criteria.each.should == @criteria
235
+ end
236
+ end
237
+ end
238
+ end
239
+
240
+ describe "#excludes" do
241
+
242
+ it "adds the $ne query to the selector" do
243
+ @criteria.excludes(:title => "Bad Title", :text => "Bad Text")
244
+ @criteria.selector.should == { :title => { "$ne" => "Bad Title"}, :text => { "$ne" => "Bad Text" } }
245
+ end
246
+
247
+ it "and_return self" do
248
+ @criteria.excludes(:title => "Bad").should == @criteria
249
+ end
250
+
251
+ end
252
+
253
+ describe "#extras" do
254
+
255
+ context "filtering" do
256
+
257
+ context "when page is provided" do
258
+
259
+ it "sets the limit and skip options" do
260
+ @criteria.extras({ :page => "2" })
261
+ @criteria.page.should == 2
262
+ @criteria.options.should == { :skip => 20, :limit => 20 }
263
+ end
264
+
265
+ end
266
+
267
+ context "when per_page is provided" do
268
+
269
+ it "sets the limit and skip options" do
270
+ @criteria.extras({ :per_page => 45 })
271
+ @criteria.options.should == { :skip => 0, :limit => 45 }
272
+ end
273
+
274
+ end
275
+
276
+ context "when page and per_page both provided" do
277
+
278
+ it "sets the limit and skip options" do
279
+ @criteria.extras({ :per_page => 30, :page => "4" })
280
+ @criteria.options.should == { :skip => 90, :limit => 30 }
281
+ @criteria.page.should == 4
282
+ end
283
+
284
+ end
285
+
286
+ end
287
+
288
+ it "adds the extras to the options" do
289
+ @criteria.extras({ :skip => 10 })
290
+ @criteria.options.should == { :skip => 10 }
291
+ end
292
+
293
+ it "and_return self" do
294
+ @criteria.extras({}).should == @criteria
295
+ end
296
+
297
+ it "adds the extras without overwriting existing options" do
298
+ @criteria.order_by([:field1])
299
+ @criteria.extras({ :skip => 10 })
300
+ @criteria.options.should have_key(:sort)
301
+ end
302
+
303
+ end
304
+
305
+ describe "#group" do
306
+
307
+ before do
308
+ @grouping = [{ "title" => "Sir", "group" => [{ "title" => "Sir", "age" => 30 }] }]
309
+ end
310
+
311
+ context "when klass provided" do
312
+
313
+ before do
314
+ @reduce = "function(obj, prev) { prev.group.push(obj); }"
315
+ @collection = mock
316
+ Animal.should_receive(:collection).and_return(@collection)
317
+ end
318
+
319
+ it "calls group on the collection with the aggregate js" do
320
+ @collection.should_receive(:group).with([:field1], {}, {:group => []}, @reduce).and_return(@grouping)
321
+ @criteria.select(:field1).group(Animal)
322
+ end
323
+
324
+ end
325
+
326
+ context "when klass not provided" do
327
+
328
+ before do
329
+ @reduce = "function(obj, prev) { prev.group.push(obj); }"
330
+ @collection = mock
331
+ Person.should_receive(:collection).and_return(@collection)
332
+ end
333
+
334
+ it "calls group on the collection with the aggregate js" do
335
+ @collection.should_receive(:group).with([:field1], {}, {:group => []}, @reduce).and_return(@grouping)
336
+ @criteria.select(:field1).group
337
+ end
338
+
339
+ end
340
+
341
+ end
342
+
343
+ describe "#id" do
344
+
345
+ it "adds the _id query to the selector" do
346
+ id = Mongo::ObjectID.new.to_s
347
+ @criteria.id(id)
348
+ @criteria.selector.should == { :_id => id }
349
+ end
350
+
351
+ it "and_return self" do
352
+ id = Mongo::ObjectID.new.to_s
353
+ @criteria.id(id.to_s).should == @criteria
354
+ end
355
+
356
+ end
357
+
358
+ describe "#in" do
359
+
360
+ it "adds the $in clause to the selector" do
361
+ @criteria.in(:title => ["title1", "title2"], :text => ["test"])
362
+ @criteria.selector.should == { :title => { "$in" => ["title1", "title2"] }, :text => { "$in" => ["test"] } }
363
+ end
364
+
365
+ it "and_return self" do
366
+ @criteria.in(:title => ["title1"]).should == @criteria
367
+ end
368
+
369
+ end
370
+
371
+ describe "#last" do
372
+
373
+ context "when documents exist" do
374
+
375
+ before do
376
+ @collection = mock
377
+ Person.should_receive(:collection).and_return(@collection)
378
+ @collection.should_receive(:find_one).with(@criteria.selector, { :sort => [[:title, :desc]] }).and_return(Person.new(:title => "Sir"))
379
+ end
380
+
381
+ it "calls find on the collection with the selector and sort options reversed" do
382
+ @criteria.order_by([[:title, :asc]])
383
+ @criteria.last.should be_a_kind_of(Person)
384
+ end
385
+
386
+ end
387
+
388
+ context "when no documents exist" do
389
+
390
+ before do
391
+ @collection = mock
392
+ Person.should_receive(:collection).and_return(@collection)
393
+ @collection.should_receive(:find_one).with(@criteria.selector, { :sort => [[:_id, :desc]] }).and_return(nil)
394
+ end
395
+
396
+ it "and_return nil" do
397
+ @criteria.last.should be_nil
398
+ end
399
+
400
+ end
401
+
402
+ context "when no sorting options provided" do
403
+
404
+ before do
405
+ @collection = mock
406
+ Person.should_receive(:collection).and_return(@collection)
407
+ @collection.should_receive(:find_one).with(@criteria.selector, { :sort => [[:_id, :desc]] }).and_return({ :title => "Sir" })
408
+ end
409
+
410
+ it "defaults to sort by id" do
411
+ @criteria.last
412
+ end
413
+
414
+ end
415
+
416
+ end
417
+
418
+ describe "#limit" do
419
+
420
+ context "when value provided" do
421
+
422
+ it "adds the limit to the options" do
423
+ @criteria.limit(100)
424
+ @criteria.options.should == { :limit => 100 }
425
+ end
426
+
427
+ end
428
+
429
+ context "when value not provided" do
430
+
431
+ it "defaults to 20" do
432
+ @criteria.limit
433
+ @criteria.options.should == { :limit => 20 }
434
+ end
435
+
436
+ end
437
+
438
+ it "and_return self" do
439
+ @criteria.limit.should == @criteria
440
+ end
441
+
442
+ end
443
+
444
+ describe "#merge" do
445
+
446
+ before do
447
+ @criteria.where(:title => "Sir", :age => 30).skip(40).limit(20)
448
+ end
449
+
450
+ context "with another criteria" do
451
+
452
+ context "when the other has a selector and options" do
453
+
454
+ before do
455
+ @other = MongoDoc::Criteria.new(Person)
456
+ @other.where(:name => "Chloe").order_by([[:name, :asc]])
457
+ @selector = { :title => "Sir", :age => 30, :name => "Chloe" }
458
+ @options = { :skip => 40, :limit => 20, :sort => [[:name, :asc]] }
459
+ end
460
+
461
+ it "merges the selector and options hashes together" do
462
+ @criteria.merge(@other)
463
+ @criteria.selector.should == @selector
464
+ @criteria.options.should == @options
465
+ end
466
+
467
+ end
468
+
469
+ context "when the other has no selector or options" do
470
+
471
+ before do
472
+ @other = MongoDoc::Criteria.new(Person)
473
+ @selector = { :title => "Sir", :age => 30 }
474
+ @options = { :skip => 40, :limit => 20 }
475
+ end
476
+
477
+ it "merges the selector and options hashes together" do
478
+ @criteria.merge(@other)
479
+ @criteria.selector.should == @selector
480
+ @criteria.options.should == @options
481
+ end
482
+ end
483
+
484
+ end
485
+
486
+ end
487
+
488
+ describe "#method_missing" do
489
+
490
+ before do
491
+ @criteria.where(:title => "Sir")
492
+ end
493
+
494
+ it "merges the criteria with the next one" do
495
+ @new_criteria = @criteria.accepted
496
+ @new_criteria.selector.should == { :title => "Sir", :terms => true }
497
+ end
498
+
499
+ context "chaining more than one scope" do
500
+
501
+ before do
502
+ @criteria = Person.accepted.old.knight
503
+ end
504
+
505
+ it "and_return the final merged criteria" do
506
+ @criteria.selector.should ==
507
+ { :title => "Sir", :terms => true, :age => { "$gt" => 50 } }
508
+ end
509
+
510
+ end
511
+
512
+ end
513
+
514
+ describe "#not_in" do
515
+
516
+ it "adds the exclusion to the selector" do
517
+ @criteria.not_in(:title => ["title1", "title2"], :text => ["test"])
518
+ @criteria.selector.should == { :title => { "$nin" => ["title1", "title2"] }, :text => { "$nin" => ["test"] } }
519
+ end
520
+
521
+ it "and_return self" do
522
+ @criteria.not_in(:title => ["title1"]).should == @criteria
523
+ end
524
+
525
+ end
526
+
527
+ describe "#offset" do
528
+
529
+ context "when the per_page option exists" do
530
+
531
+ before do
532
+ @criteria.extras({ :per_page => 20, :page => 3 })
533
+ end
534
+
535
+ it "and_return the per_page option" do
536
+ @criteria.offset.should == 40
537
+ end
538
+
539
+ end
540
+
541
+ context "when the skip option exists" do
542
+
543
+ before do
544
+ @criteria.extras({ :skip => 20 })
545
+ end
546
+
547
+ it "and_return the skip option" do
548
+ @criteria.offset.should == 20
549
+ end
550
+
551
+ end
552
+
553
+ context "when no option exists" do
554
+
555
+ context "when page option exists" do
556
+
557
+ before do
558
+ @criteria.extras({ :page => 2 })
559
+ end
560
+
561
+ it "adds the skip option to the options and and_return it" do
562
+ @criteria.offset.should == 20
563
+ @criteria.options[:skip].should == 20
564
+ end
565
+
566
+ end
567
+
568
+ context "when page option does not exist" do
569
+
570
+ it "and_return nil" do
571
+ @criteria.offset.should be_nil
572
+ @criteria.options[:skip].should be_nil
573
+ end
574
+
575
+ end
576
+
577
+ end
578
+
579
+ end
580
+
581
+ describe "#one" do
582
+
583
+ context "when documents exist" do
584
+
585
+ before do
586
+ @collection = mock
587
+ Person.should_receive(:collection).and_return(@collection)
588
+ @collection.should_receive(:find_one).with(@criteria.selector, @criteria.options).and_return(Person.new(:title => "Sir"))
589
+ end
590
+
591
+ it "calls find on the collection with the selector and options" do
592
+ @criteria.one.should be_a_kind_of(Person)
593
+ end
594
+
595
+ end
596
+
597
+ context "when no documents exist" do
598
+
599
+ before do
600
+ @collection = mock
601
+ Person.should_receive(:collection).and_return(@collection)
602
+ @collection.should_receive(:find_one).with(@criteria.selector, @criteria.options).and_return(nil)
603
+ end
604
+
605
+ it "returns nil" do
606
+ @criteria.one.should be_nil
607
+ end
608
+
609
+ end
610
+
611
+ end
612
+
613
+ describe "#order_by" do
614
+
615
+ context "when field names and direction specified" do
616
+
617
+ it "adds the sort to the options" do
618
+ @criteria.order_by([[:title, :asc], [:text, :desc]])
619
+ @criteria.options.should == { :sort => [[:title, :asc], [:text, :desc]] }
620
+ end
621
+
622
+ end
623
+
624
+ it "and_return self" do
625
+ @criteria.order_by.should == @criteria
626
+ end
627
+
628
+ end
629
+
630
+ describe "#page" do
631
+
632
+ context "when the page option exists" do
633
+
634
+ before do
635
+ @criteria.extras({ :page => 5 })
636
+ end
637
+
638
+ it "and_return the page option" do
639
+ @criteria.page.should == 5
640
+ end
641
+
642
+ end
643
+
644
+ context "when the page option does not exist" do
645
+
646
+ it "returns 1" do
647
+ @criteria.page.should == 1
648
+ end
649
+
650
+ end
651
+
652
+ end
653
+
654
+ describe "#paginate" do
655
+
656
+ before do
657
+ @collection = mock
658
+ Person.should_receive(:collection).and_return(@collection)
659
+ @criteria.select.where(:_id => "1").skip(60).limit(20)
660
+ @cursor = mock('cursor', :count => 20, :to_a => [])
661
+ @collection.should_receive(:find).with({:_id => "1"}, :skip => 60, :limit => 20).and_return(@cursor)
662
+ @results = @criteria.paginate
663
+ end
664
+
665
+ it "executes and paginates the results" do
666
+ @results.current_page.should == 4
667
+ @results.per_page.should == 20
668
+ end
669
+
670
+ end
671
+
672
+ describe "#per_page" do
673
+
674
+ context "when the per_page option exists" do
675
+
676
+ before do
677
+ @criteria.extras({ :per_page => 10 })
678
+ end
679
+
680
+ it "and_return the per_page option" do
681
+ @criteria.per_page.should == 10
682
+ end
683
+
684
+ end
685
+
686
+ context "when the per_page option does not exist" do
687
+
688
+ it "returns 1" do
689
+ @criteria.per_page.should == 20
690
+ end
691
+
692
+ end
693
+
694
+ end
695
+
696
+ describe "#select" do
697
+
698
+ context "when args are provided" do
699
+
700
+ it "adds the options for limiting by fields" do
701
+ @criteria.select(:title, :text)
702
+ @criteria.options.should == { :fields => [ :title, :text ] }
703
+ end
704
+
705
+ it "and_return self" do
706
+ @criteria.select.should == @criteria
707
+ end
708
+
709
+ end
710
+
711
+ context "when no args provided" do
712
+
713
+ it "does not add the field option" do
714
+ @criteria.select
715
+ @criteria.options[:fields].should be_nil
716
+ end
717
+
718
+ end
719
+
720
+ end
721
+
722
+ describe "#skip" do
723
+
724
+ context "when value provided" do
725
+
726
+ it "adds the skip value to the options" do
727
+ @criteria.skip(20)
728
+ @criteria.options.should == { :skip => 20 }
729
+ end
730
+
731
+ end
732
+
733
+ context "when value not provided" do
734
+
735
+ it "defaults to zero" do
736
+ @criteria.skip
737
+ @criteria.options.should == { :skip => 0 }
738
+ end
739
+
740
+ end
741
+
742
+ it "and_return self" do
743
+ @criteria.skip.should == @criteria
744
+ end
745
+
746
+ end
747
+
748
+ describe ".translate" do
749
+
750
+ context "with a single argument" do
751
+
752
+ before do
753
+ @id = Mongo::ObjectID.new.to_s
754
+ @document = stub
755
+ @criteria = mock
756
+ MongoDoc::Criteria.should_receive(:new).and_return(@criteria)
757
+ @criteria.should_receive(:id).with(@id).and_return(@criteria)
758
+ @criteria.should_receive(:one).and_return(@document)
759
+ end
760
+
761
+ it "creates a criteria for a string" do
762
+ MongoDoc::Criteria.translate(Person, @id)
763
+ end
764
+
765
+ end
766
+
767
+ context "multiple arguments" do
768
+
769
+ context "when Person, :conditions => {}" do
770
+
771
+ before do
772
+ @criteria = MongoDoc::Criteria.translate(Person, :conditions => { :title => "Test" })
773
+ end
774
+
775
+ it "and_return a criteria with a selector from the conditions" do
776
+ @criteria.selector.should == { :title => "Test" }
777
+ end
778
+
779
+ it "and_return a criteria with klass Person" do
780
+ @criteria.klass.should == Person
781
+ end
782
+
783
+ end
784
+
785
+ context "when :all, :conditions => {}" do
786
+
787
+ before do
788
+ @criteria = MongoDoc::Criteria.translate(Person, :conditions => { :title => "Test" })
789
+ end
790
+
791
+ it "and_return a criteria with a selector from the conditions" do
792
+ @criteria.selector.should == { :title => "Test" }
793
+ end
794
+
795
+ it "and_return a criteria with klass Person" do
796
+ @criteria.klass.should == Person
797
+ end
798
+
799
+ end
800
+
801
+ context "when :last, :conditions => {}" do
802
+
803
+ before do
804
+ @criteria = MongoDoc::Criteria.translate(Person, :conditions => { :title => "Test" })
805
+ end
806
+
807
+ it "and_return a criteria with a selector from the conditions" do
808
+ @criteria.selector.should == { :title => "Test" }
809
+ end
810
+
811
+ it "and_return a criteria with klass Person" do
812
+ @criteria.klass.should == Person
813
+ end
814
+ end
815
+
816
+ context "when options are provided" do
817
+
818
+ before do
819
+ @criteria = MongoDoc::Criteria.translate(Person, :conditions => { :title => "Test" }, :skip => 10)
820
+ end
821
+
822
+ it "adds the criteria and the options" do
823
+ @criteria.selector.should == { :title => "Test" }
824
+ @criteria.options.should == { :skip => 10 }
825
+ end
826
+
827
+ end
828
+
829
+ end
830
+
831
+ end
832
+
833
+ describe "#where" do
834
+
835
+ it "adds the clause to the selector" do
836
+ @criteria.where(:title => "Title", :text => "Text")
837
+ @criteria.selector.should == { :title => "Title", :text => "Text" }
838
+ end
839
+
840
+ it "and_return self" do
841
+ @criteria.where.should == @criteria
842
+ end
843
+
844
+ end
845
+
846
+ end