mongomodel 0.1.6 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/README.md +6 -0
  2. data/bin/console +4 -4
  3. data/lib/mongomodel.rb +4 -4
  4. data/lib/mongomodel/concerns/associations/base/definition.rb +4 -0
  5. data/lib/mongomodel/concerns/associations/has_many_by_foreign_key.rb +7 -16
  6. data/lib/mongomodel/concerns/associations/has_many_by_ids.rb +6 -12
  7. data/lib/mongomodel/concerns/attributes.rb +1 -7
  8. data/lib/mongomodel/document.rb +0 -1
  9. data/lib/mongomodel/document/dynamic_finders.rb +2 -69
  10. data/lib/mongomodel/document/indexes.rb +0 -6
  11. data/lib/mongomodel/document/persistence.rb +1 -21
  12. data/lib/mongomodel/document/scopes.rb +59 -135
  13. data/lib/mongomodel/document/validations/uniqueness.rb +7 -5
  14. data/lib/mongomodel/support/dynamic_finder.rb +68 -0
  15. data/lib/mongomodel/support/mongo_operator.rb +29 -0
  16. data/lib/mongomodel/support/mongo_options.rb +0 -101
  17. data/lib/mongomodel/support/mongo_order.rb +78 -0
  18. data/lib/mongomodel/support/scope.rb +186 -0
  19. data/lib/mongomodel/support/scope/dynamic_finders.rb +21 -0
  20. data/lib/mongomodel/support/scope/finder_methods.rb +61 -0
  21. data/lib/mongomodel/support/scope/query_methods.rb +43 -0
  22. data/lib/mongomodel/support/scope/spawn_methods.rb +35 -0
  23. data/lib/mongomodel/version.rb +1 -1
  24. data/mongomodel.gemspec +20 -3
  25. data/spec/mongomodel/concerns/associations/has_many_by_foreign_key_spec.rb +1 -1
  26. data/spec/mongomodel/concerns/associations/has_many_by_ids_spec.rb +1 -1
  27. data/spec/mongomodel/document/dynamic_finders_spec.rb +0 -1
  28. data/spec/mongomodel/document/finders_spec.rb +0 -144
  29. data/spec/mongomodel/document/indexes_spec.rb +2 -2
  30. data/spec/mongomodel/document/persistence_spec.rb +1 -15
  31. data/spec/mongomodel/document/scopes_spec.rb +64 -167
  32. data/spec/mongomodel/support/mongo_operator_spec.rb +29 -0
  33. data/spec/mongomodel/support/mongo_options_spec.rb +0 -150
  34. data/spec/mongomodel/support/mongo_order_spec.rb +127 -0
  35. data/spec/mongomodel/support/scope_spec.rb +932 -0
  36. data/spec/support/helpers/document_finder_stubs.rb +40 -0
  37. data/spec/support/matchers/find_with.rb +36 -0
  38. metadata +22 -5
  39. data/lib/mongomodel/document/finders.rb +0 -82
@@ -0,0 +1,127 @@
1
+ require 'spec_helper'
2
+
3
+ module MongoModel
4
+ describe MongoOrder do
5
+ def c(field, order)
6
+ MongoOrder::Clause.new(field, order)
7
+ end
8
+
9
+ subject { MongoOrder.new(c(:name, :ascending), c(:age, :descending)) }
10
+
11
+ it "should convert to string" do
12
+ subject.to_s.should == "name ascending, age descending"
13
+ end
14
+
15
+ describe "#to_sort" do
16
+ it "should convert to mongo sort array" do
17
+ model = mock('model', :properties => mock('properties', :[] => nil))
18
+ subject.to_sort(model).should == [['name', :ascending], ['age', :descending]]
19
+ end
20
+ end
21
+
22
+ it "should be reversable" do
23
+ subject.reverse.should == MongoOrder.new(c(:name, :descending), c(:age, :ascending))
24
+ end
25
+
26
+ it "should equal another order object with identical clauses" do
27
+ subject.should == MongoOrder.new(c(:name, :ascending), c(:age, :descending))
28
+ end
29
+
30
+ it "should equal another order object with different clauses" do
31
+ subject.should_not == MongoOrder.new(c(:name, :ascending))
32
+ subject.should_not == MongoOrder.new(c(:age, :ascending), c(:name, :ascending))
33
+ end
34
+
35
+ describe "#parse" do
36
+ it "should not change a MongoOrder" do
37
+ MongoOrder.parse(subject).should == subject
38
+ end
39
+
40
+ it "should convert individual clause to MongoOrder" do
41
+ MongoOrder.parse(c(:name, :ascending)).should == MongoOrder.new(c(:name, :ascending))
42
+ end
43
+
44
+ it "should convert symbol to MongoOrder" do
45
+ MongoOrder.parse(:name).should == MongoOrder.new(c(:name, :ascending))
46
+ end
47
+
48
+ it "should convert array of clauses to MongoOrder" do
49
+ MongoOrder.parse([c(:name, :ascending), c(:age, :descending)]).should == MongoOrder.new(c(:name, :ascending), c(:age, :descending))
50
+ end
51
+
52
+ it "should convert array of symbols to MongoOrder" do
53
+ MongoOrder.parse([:name, :age]).should == MongoOrder.new(c(:name, :ascending), c(:age, :ascending))
54
+ end
55
+
56
+ it "should convert array of strings to MongoOrder" do
57
+ MongoOrder.parse(['name ASC', 'age DESC']).should == MongoOrder.new(c(:name, :ascending), c(:age, :descending))
58
+ end
59
+
60
+ it "should convert string (no order specified) to MongoOrder" do
61
+ MongoOrder.parse('name').should == MongoOrder.new(c(:name, :ascending))
62
+ end
63
+
64
+ it "should convert string (single order) to MongoOrder" do
65
+ MongoOrder.parse('name DESC').should == MongoOrder.new(c(:name, :descending))
66
+ end
67
+
68
+ it "should convert string (multiple orders) to MongoOrder" do
69
+ MongoOrder.parse('name DESC, age ASC').should == MongoOrder.new(c(:name, :descending), c(:age, :ascending))
70
+ end
71
+ end
72
+ end
73
+
74
+ describe MongoOrder::Clause do
75
+ subject { MongoOrder::Clause.new(:name, :ascending) }
76
+
77
+ it "should convert to string" do
78
+ subject.to_s.should == "name ascending"
79
+ end
80
+
81
+ it "should equal another clause with the same field and order" do
82
+ subject.should == MongoOrder::Clause.new(:name, :ascending)
83
+ end
84
+
85
+ it "should equal another clause with a different field or order" do
86
+ subject.should_not == MongoOrder::Clause.new(:age, :ascending)
87
+ subject.should_not == MongoOrder::Clause.new(:name, :descending)
88
+ end
89
+
90
+ it "should be reversable" do
91
+ subject.reverse.should == MongoOrder::Clause.new(:name, :descending)
92
+ end
93
+
94
+ describe "#to_sort" do
95
+ context "given property" do
96
+ it "should use property as value to convert to mongo sort" do
97
+ property = mock('property', :as => '_name')
98
+ subject.to_sort(property).should == ['_name', :ascending]
99
+ end
100
+ end
101
+
102
+ context "given nil" do
103
+ it "should convert to mongo sort" do
104
+ subject.to_sort(nil).should == ['name', :ascending]
105
+ end
106
+ end
107
+ end
108
+
109
+ describe "#parse" do
110
+ let(:asc) { MongoOrder::Clause.new(:name, :ascending) }
111
+ let(:desc) { MongoOrder::Clause.new(:name, :descending) }
112
+
113
+ it "should create Clause from string (no order)" do
114
+ MongoOrder::Clause.parse('name').should == asc
115
+ end
116
+
117
+ it "should create Clause from string (with order)" do
118
+ MongoOrder::Clause.parse('name ASC').should == asc
119
+ MongoOrder::Clause.parse('name asc').should == asc
120
+ MongoOrder::Clause.parse('name ascending').should == asc
121
+ MongoOrder::Clause.parse('name DESC').should == desc
122
+ MongoOrder::Clause.parse('name desc').should == desc
123
+ MongoOrder::Clause.parse('name descending').should == desc
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,932 @@
1
+ require 'spec_helper'
2
+
3
+ module MongoModel
4
+ describe Scope do
5
+ define_class(:Post, Document) do
6
+ property :published, Boolean, :default => false
7
+ property :author, String
8
+ property :date, Time
9
+ end
10
+
11
+ let(:basic_scope) { Scope.new(Post) }
12
+ let(:posts) { (1..5).map { Post.new } }
13
+
14
+ subject { Scope.new(Post) }
15
+
16
+ MongoModel::Document.extend(DocumentFinderStubs)
17
+
18
+
19
+ def self.subject_loaded(&block)
20
+ context "when loaded" do
21
+ before(:each) { subject.to_a }
22
+ class_eval(&block)
23
+ end
24
+ end
25
+
26
+ def self.subject_not_loaded(&block)
27
+ context "when not loaded", &block
28
+ end
29
+
30
+ def self.always(&block)
31
+ subject_loaded(&block)
32
+ subject_not_loaded(&block)
33
+ end
34
+
35
+
36
+ shared_examples_for "all scopes" do
37
+ def finder_conditions
38
+ finder_options[:conditions] || {}
39
+ end
40
+
41
+ describe "#to_a" do
42
+ it "should find and return documents matching conditions" do
43
+ model.should_find(finder_options, posts) do
44
+ subject.to_a.should == posts
45
+ end
46
+ end
47
+
48
+ it "should load the scope" do
49
+ subject.to_a
50
+ subject.should be_loaded
51
+ end
52
+
53
+ it "should cache the documents" do
54
+ subject.to_a
55
+ model.should_not_find { subject.to_a }
56
+ end
57
+ end
58
+
59
+ describe "#count" do
60
+ it "should count documents matching conditions and return the result" do
61
+ model.should_count(finder_options, 4) do
62
+ subject.count
63
+ end
64
+ end
65
+
66
+ it "should not cache the result" do
67
+ subject.count
68
+ model.should_count(finder_options, 4) do
69
+ subject.count.should == 4
70
+ end
71
+ end
72
+ end
73
+
74
+ describe "#size" do
75
+ before(:each) { model.stub_find(posts) }
76
+
77
+ subject_loaded do
78
+ it "should return the number of matching documents" do
79
+ subject.size.should == 5
80
+ end
81
+
82
+ it "should not perform a count on the collection" do
83
+ model.should_not_count { subject.size }
84
+ end
85
+ end
86
+
87
+ subject_not_loaded do
88
+ it "should return the number of matching documents" do
89
+ subject.size.should == 5
90
+ end
91
+
92
+ it "should perform a count on the collection" do
93
+ model.should_count(finder_options, 9) do
94
+ subject.size.should == 9
95
+ end
96
+ end
97
+ end
98
+ end
99
+
100
+ describe "#empty?" do
101
+ context "when no matching documents exist" do
102
+ before(:each) { model.stub_find([]) }
103
+
104
+ always do
105
+ it { should be_empty }
106
+ end
107
+ end
108
+
109
+ context "when matching documents exist" do
110
+ before(:each) { model.stub_find(posts) }
111
+
112
+ always do
113
+ it { should_not be_empty }
114
+ end
115
+ end
116
+
117
+ subject_loaded do
118
+ it "should not perform a count on the collection" do
119
+ model.should_not_count { subject.empty? }
120
+ end
121
+ end
122
+ end
123
+
124
+ describe "#any?" do
125
+ context "when no block given" do
126
+ it "should perform a count on the collection" do
127
+ model.should_count(finder_options, 1) { subject.any? }
128
+ end
129
+
130
+ context "when no matching documents exist" do
131
+ before(:each) { model.stub_find([]) }
132
+
133
+ always do
134
+ specify { subject.any?.should be_false }
135
+ end
136
+ end
137
+
138
+ context "when matching documents exist" do
139
+ before(:each) { model.stub_find(posts) }
140
+
141
+ always do
142
+ specify { subject.any?.should be_true }
143
+ end
144
+ end
145
+ end
146
+
147
+ context "when block given" do
148
+ it "should delegate block to to_a" do
149
+ blk = lambda { |*args| true }
150
+ subject.to_a.should_receive(:any?).with(&blk)
151
+ subject.any?(&blk)
152
+ end
153
+ end
154
+ end
155
+
156
+ describe "#reset" do
157
+ always do
158
+ it "should return itself" do
159
+ subject.reset.should equal(subject)
160
+ end
161
+
162
+ it "should not be loaded" do
163
+ subject.reset
164
+ subject.should_not be_loaded
165
+ end
166
+ end
167
+ end
168
+
169
+ describe "#reload" do
170
+ always do
171
+ it "should return itself" do
172
+ subject.reload.should equal(subject)
173
+ end
174
+
175
+ it "should be loaded after calling" do
176
+ subject.reload
177
+ subject.should be_loaded
178
+ end
179
+ end
180
+ end
181
+
182
+ describe "#==" do
183
+ before(:each) { model.stub_find(posts) }
184
+
185
+ it "should be equal to an array with matching results" do
186
+ subject.should == posts
187
+ end
188
+
189
+ it "should not be equal to an array with different results" do
190
+ subject.should_not == []
191
+ end
192
+ end
193
+
194
+ describe "array methods" do
195
+ it "should forward [] to to_a" do
196
+ subject.to_a.should_receive(:[]).with(0)
197
+ subject[0]
198
+ end
199
+
200
+ it "should forward each to to_a" do
201
+ blk = lambda { |*args| true }
202
+ subject.to_a.should_receive(:each).with(&blk)
203
+ subject.each(&blk)
204
+ end
205
+ end
206
+
207
+ describe "#where" do
208
+ it "should return a new scope" do
209
+ subject.where(:author => "Sam").should be_an_instance_of(Scope)
210
+ end
211
+
212
+ it "should not be loaded" do
213
+ subject.to_a
214
+ subject.where(:author => "Sam").should_not be_loaded
215
+ end
216
+
217
+ it "should add individual where values" do
218
+ where_scope = subject.where(:author => "Sam")
219
+ where_scope.where_values.should == subject.where_values + [{ :author => "Sam" }]
220
+ end
221
+
222
+ it "should add multiple where values" do
223
+ where_scope = subject.where({ :author => "Sam" }, { :published => false })
224
+ where_scope.where_values.should == subject.where_values + [{ :author => "Sam" }, { :published => false }]
225
+ end
226
+ end
227
+
228
+ describe "#order" do
229
+ it "should return a new scope" do
230
+ subject.order(:author.asc).should be_an_instance_of(Scope)
231
+ end
232
+
233
+ it "should not be loaded" do
234
+ subject.to_a
235
+ subject.order(:author.asc).should_not be_loaded
236
+ end
237
+
238
+ it "should add individual order values" do
239
+ order_scope = subject.order(:author.asc)
240
+ order_scope.order_values.should == subject.order_values + [:author.asc]
241
+ end
242
+
243
+ it "should add multiple order values" do
244
+ order_scope = subject.order(:author.asc, :published.desc)
245
+ order_scope.order_values.should == subject.order_values + [:author.asc, :published.desc]
246
+ end
247
+ end
248
+
249
+ describe "#select" do
250
+ it "should return a new scope" do
251
+ subject.select(:author).should be_an_instance_of(Scope)
252
+ end
253
+
254
+ it "should not be loaded" do
255
+ subject.to_a
256
+ subject.select(:author).should_not be_loaded
257
+ end
258
+
259
+ it "should add individual select values" do
260
+ select_scope = subject.select(:author)
261
+ select_scope.select_values.should == subject.select_values + [:author]
262
+ end
263
+
264
+ it "should add multiple select values" do
265
+ select_scope = subject.select(:author, :published)
266
+ select_scope.select_values.should == subject.select_values + [:author, :published]
267
+ end
268
+ end
269
+
270
+ describe "#limit" do
271
+ it "should return a new scope" do
272
+ subject.limit(10).should be_an_instance_of(Scope)
273
+ end
274
+
275
+ it "should not be loaded" do
276
+ subject.limit(10).should_not be_loaded
277
+ end
278
+
279
+ it "should override previous limit value" do
280
+ subject.limit(10).limit_value.should == 10
281
+ end
282
+ end
283
+
284
+ describe "#offset" do
285
+ it "should return a new scope" do
286
+ subject.offset(10).should be_an_instance_of(Scope)
287
+ end
288
+
289
+ it "should not be loaded" do
290
+ subject.offset(10).should_not be_loaded
291
+ end
292
+
293
+ it "should override previous offset value" do
294
+ subject.offset(10).offset_value.should == 10
295
+ end
296
+ end
297
+
298
+ describe "#from" do
299
+ define_class(:NotAPost, Document)
300
+
301
+ it "should return a new scope" do
302
+ subject.from(NotAPost.collection).should be_an_instance_of(Scope)
303
+ end
304
+
305
+ it "should not be loaded" do
306
+ subject.from(NotAPost.collection).should_not be_loaded
307
+ end
308
+
309
+ it "should override collection" do
310
+ subject.from(NotAPost.collection).collection.should == NotAPost.collection
311
+ end
312
+ end
313
+
314
+ describe "#first" do
315
+ context "when no matching documents exist" do
316
+ before(:each) { model.stub_find([]) }
317
+
318
+ always do
319
+ it "should return nil" do
320
+ subject.first.should be_nil
321
+ end
322
+ end
323
+
324
+ subject_loaded do
325
+ it "should not perform a find" do
326
+ model.should_not_find do
327
+ subject.first
328
+ end
329
+ end
330
+ end
331
+
332
+ subject_not_loaded do
333
+ it "should find with a limit of 1" do
334
+ model.should_find(finder_options.merge(:limit => 1), []) do
335
+ subject.first
336
+ end
337
+ end
338
+ end
339
+ end
340
+
341
+ context "when matching documents exist" do
342
+ let(:post) { posts.first }
343
+ before(:each) { model.stub_find([post]) }
344
+
345
+ always do
346
+ it "should return the first document" do
347
+ subject.first.should == post
348
+ end
349
+ end
350
+
351
+ subject_not_loaded do
352
+ it "should cache find result" do
353
+ model.should_find(finder_options.merge(:limit => 1), [post]) do
354
+ subject.first
355
+ subject.first
356
+ end
357
+ end
358
+ end
359
+ end
360
+ end
361
+
362
+ describe "#last" do
363
+ def reversed_finder_options
364
+ order = MongoModel::MongoOrder.parse(finder_options[:order] || [:id.asc])
365
+ finder_options.merge(:order => order.reverse.to_a)
366
+ end
367
+
368
+ context "when no matching documents exist" do
369
+ before(:each) { model.stub_find([]) }
370
+
371
+ always do
372
+ it "should return nil" do
373
+ subject.last.should be_nil
374
+ end
375
+ end
376
+
377
+ subject_loaded do
378
+ it "should not perform a find" do
379
+ model.should_not_find do
380
+ subject.last
381
+ end
382
+ end
383
+ end
384
+
385
+ subject_not_loaded do
386
+ it "should find with a limit of 1" do
387
+ model.should_find(reversed_finder_options.merge(:limit => 1), []) do
388
+ subject.last
389
+ end
390
+ end
391
+ end
392
+ end
393
+
394
+ context "when matching documents exist" do
395
+ let(:post) { posts.last }
396
+ before(:each) { model.stub_find([post]) }
397
+
398
+ always do
399
+ it "should return the last document" do
400
+ subject.last.should == post
401
+ end
402
+ end
403
+
404
+ subject_not_loaded do
405
+ it "should cache find result" do
406
+ model.should_find(reversed_finder_options.merge(:limit => 1), [post]) do
407
+ subject.last
408
+ subject.last
409
+ end
410
+ end
411
+ end
412
+ end
413
+ end
414
+
415
+ describe "#all" do
416
+ it "should return all documents" do
417
+ model.should_find(finder_options, posts) do
418
+ subject.all.should == posts
419
+ end
420
+ end
421
+ end
422
+
423
+ describe "#find" do
424
+ context "with single id" do
425
+ let(:post) { posts.first }
426
+
427
+ it "should perform find on collection" do
428
+ model.should_find(finder_options.deep_merge(:conditions => { :id => post.id }, :limit => 1), [post]) do
429
+ subject.find(post.id)
430
+ end
431
+ end
432
+
433
+ context "when document exists" do
434
+ before(:each) { model.stub_find([post]) }
435
+
436
+ it "should find and return document" do
437
+ subject.find(post.id).should == post
438
+ end
439
+ end
440
+
441
+ context "when document does not exist" do
442
+ before(:each) { model.stub_find([]) }
443
+
444
+ it "should raise a DocumentNotFound exception" do
445
+ lambda {
446
+ subject.find('missing')
447
+ }.should raise_error(MongoModel::DocumentNotFound)
448
+ end
449
+ end
450
+ end
451
+
452
+ context "by multiple ids" do
453
+ let(:post1) { posts.first }
454
+ let(:post2) { posts.last }
455
+
456
+ it "should perform find on collection" do
457
+ model.should_find(finder_options.deep_merge(:conditions => { :id.in => [post2.id, post1.id] }), [post1, post2]) do
458
+ subject.find(post2.id, post1.id)
459
+ end
460
+ end
461
+
462
+ context "when all documents exist" do
463
+ before(:each) { model.stub_find([post2, post1]) }
464
+
465
+ it "should return documents in order given" do
466
+ subject.find(post2.id, post1.id).should == [post2, post1]
467
+ end
468
+ end
469
+
470
+ context "when some documents do not exist" do
471
+ before(:each) { model.stub_find([post1]) }
472
+
473
+ it "should raise a DocumentNotFound exception" do
474
+ lambda {
475
+ subject.find(post1.id, 'missing')
476
+ }.should raise_error(MongoModel::DocumentNotFound)
477
+ end
478
+ end
479
+ end
480
+ end
481
+
482
+ describe "#exists?" do
483
+ let(:post) { posts.first }
484
+
485
+ it "should perform a count on the collection" do
486
+ model.should_count(finder_options.deep_merge(:conditions => { :id => post.id }), 1) do
487
+ subject.exists?(post.id)
488
+ end
489
+ end
490
+
491
+ context "when the document exists" do
492
+ before(:each) { model.stub_find([post])}
493
+
494
+ it "should return true" do
495
+ subject.exists?(post.id).should be_true
496
+ end
497
+ end
498
+
499
+ context "when the document does not exist" do
500
+ before(:each) { model.stub_find([])}
501
+
502
+ it "should return false" do
503
+ subject.exists?('missing').should be_false
504
+ end
505
+ end
506
+ end
507
+
508
+ describe "#delete_all" do
509
+ it "should remove all matching documents from collection" do
510
+ model.should_delete(finder_conditions) do
511
+ subject.delete_all
512
+ end
513
+ end
514
+
515
+ subject_loaded do
516
+ it "should reset the scope" do
517
+ subject.delete_all
518
+ subject.should_not be_loaded
519
+ end
520
+ end
521
+ end
522
+
523
+ describe "#delete" do
524
+ context "by single id" do
525
+ it "should remove the document from the collection" do
526
+ model.should_delete(finder_conditions.merge(:id => "the-id")) do
527
+ subject.delete("the-id")
528
+ end
529
+ end
530
+
531
+ subject_loaded do
532
+ it "should reset the scope" do
533
+ subject.delete("the-id")
534
+ subject.should_not be_loaded
535
+ end
536
+ end
537
+ end
538
+
539
+ context "by multiple ids" do
540
+ it "should remove the document from the collection" do
541
+ model.should_delete(finder_conditions.merge(:id.in => ["first-id", "second-id"])) do
542
+ subject.delete("first-id", "second-id")
543
+ end
544
+ end
545
+
546
+ subject_loaded do
547
+ it "should reset the scope" do
548
+ subject.delete("first-id", "second-id")
549
+ subject.should_not be_loaded
550
+ end
551
+ end
552
+ end
553
+ end
554
+
555
+ describe "#destroy_all" do
556
+ let(:post1) { posts.first }
557
+ let(:post2) { posts.last }
558
+
559
+ before(:each) { model.stub_find([post1, post2]) }
560
+
561
+ it "should destroy all matching documents individually" do
562
+ Post.should_delete(:id => post1.id)
563
+ Post.should_delete(:id => post2.id)
564
+ subject.destroy_all
565
+ end
566
+
567
+ subject_loaded do
568
+ it "should reset the scope" do
569
+ subject.destroy_all
570
+ subject.should_not be_loaded
571
+ end
572
+ end
573
+ end
574
+
575
+ describe "#destroy" do
576
+ context "by single id" do
577
+ let(:post) { posts.first }
578
+
579
+ before(:each) { model.stub_find([post]) }
580
+
581
+ it "should destroy the retrieved document" do
582
+ Post.should_delete(:id => post.id)
583
+ subject.destroy(post.id)
584
+ end
585
+
586
+ subject_loaded do
587
+ it "should reset the scope" do
588
+ subject.destroy(post.id)
589
+ subject.should_not be_loaded
590
+ end
591
+ end
592
+ end
593
+
594
+ context "by multiple ids" do
595
+ let(:post1) { posts.first }
596
+ let(:post2) { posts.last }
597
+
598
+ before(:each) { model.stub_find([post1, post2]) }
599
+
600
+ it "should destroy the documents individually" do
601
+ Post.should_delete(:id => post1.id)
602
+ Post.should_delete(:id => post2.id)
603
+ subject.destroy(post1.id, post2.id)
604
+ end
605
+
606
+ subject_loaded do
607
+ it "should reset the scope" do
608
+ subject.destroy(post1.id, post2.id)
609
+ subject.should_not be_loaded
610
+ end
611
+ end
612
+ end
613
+ end
614
+ end
615
+
616
+
617
+ context "without criteria" do
618
+ subject { basic_scope }
619
+
620
+ context "when initialized" do
621
+ it { should_not be_loaded }
622
+ end
623
+
624
+ context "when loaded" do
625
+ before(:each) { subject.to_a }
626
+ its(:clone) { should_not be_loaded }
627
+ end
628
+
629
+ def model
630
+ Post
631
+ end
632
+
633
+ def finder_options
634
+ {}
635
+ end
636
+
637
+ it_should_behave_like "all scopes"
638
+
639
+ it "should use collection from class" do
640
+ subject.collection.should == Post.collection
641
+ end
642
+
643
+ describe "#inspect" do
644
+ before(:each) { Post.stub_find(posts) }
645
+
646
+ it "should delegate to to_a" do
647
+ subject.inspect.should == posts.inspect
648
+ end
649
+ end
650
+
651
+ describe "#==" do
652
+ define_class(:NotAPost, Document)
653
+
654
+ it "should be equal to a new scope for the same class" do
655
+ subject.should == Scope.new(Post)
656
+ end
657
+
658
+ it "should not be equal to a scope for a different class" do
659
+ subject.should_not == Scope.new(NotAPost)
660
+ end
661
+ end
662
+
663
+ describe "#reverse_order" do
664
+ subject { basic_scope.reverse_order }
665
+
666
+ it "should return a new scope" do
667
+ subject.should be_an_instance_of(Scope)
668
+ end
669
+
670
+ it "should not be loaded" do
671
+ basic_scope.to_a # Load parent scope
672
+ subject.should_not be_loaded
673
+ end
674
+
675
+ it "should set the order value to descending by id" do
676
+ subject.order_values.should == [:id.desc]
677
+ end
678
+ end
679
+
680
+ describe "#build" do
681
+ it "should return a new document" do
682
+ subject.build.should be_an_instance_of(Post)
683
+ end
684
+
685
+ it "should be aliased as #new" do
686
+ subject.new(:id => '123').should == subject.build(:id => '123')
687
+ end
688
+ end
689
+
690
+ describe "#create" do
691
+ it "should return a new document" do
692
+ subject.create.should be_an_instance_of(Post)
693
+ end
694
+
695
+ it "should save the document" do
696
+ subject.create.should_not be_a_new_record
697
+ end
698
+ end
699
+
700
+ describe "#apply_finder_options" do
701
+ it "should return a new scope" do
702
+ subject.apply_finder_options({}).should be_an_instance_of(Scope)
703
+ end
704
+
705
+ it "should set where values from options" do
706
+ scope = subject.apply_finder_options({ :conditions => { :author => "John" } })
707
+ scope.where_values.should == [{ :author => "John" }]
708
+ end
709
+
710
+ it "should set order values from options" do
711
+ scope = subject.apply_finder_options({ :order => :author.desc })
712
+ scope.order_values.should == [:author.desc]
713
+ end
714
+
715
+ it "should set select values from options" do
716
+ scope = subject.apply_finder_options({ :select => [:id, :author] })
717
+ scope.select_values.should == [:id, :author]
718
+ end
719
+
720
+ it "should set offset value from options" do
721
+ scope = subject.apply_finder_options({ :offset => 40 })
722
+ scope.offset_value.should == 40
723
+ end
724
+
725
+ it "should set limit value from options" do
726
+ scope = subject.apply_finder_options({ :limit => 50 })
727
+ scope.limit_value.should == 50
728
+ end
729
+ end
730
+ end
731
+
732
+
733
+ context "with criteria" do
734
+ define_class(:OtherPost, Document)
735
+
736
+ let(:timestamp) { Time.now }
737
+ let(:scoped) do
738
+ basic_scope.where(:author => "Sam").
739
+ where(:published => true).
740
+ where(:date.lt => timestamp).
741
+ order(:author.asc).
742
+ order(:published.desc).
743
+ select(:author).
744
+ select(:published).
745
+ offset(15).
746
+ limit(7).
747
+ from(OtherPost.collection)
748
+ end
749
+
750
+ subject { scoped }
751
+
752
+ def model
753
+ OtherPost
754
+ end
755
+
756
+ def finder_options
757
+ {
758
+ :conditions => { "author" => "Sam", "published" => true, "date" => { "$lt" => timestamp } },
759
+ :order => [:author.asc, :published.desc],
760
+ :select => [:author, :published],
761
+ :offset => 15,
762
+ :limit => 7
763
+ }
764
+ end
765
+
766
+ it_should_behave_like "all scopes"
767
+
768
+ describe "#build" do
769
+ it "should use equality where conditions as attributes" do
770
+ doc = subject.build
771
+ doc.author.should == "Sam"
772
+ doc.published.should be_true
773
+ doc.date.should be_nil
774
+ end
775
+ end
776
+
777
+ describe "#create" do
778
+ it "should use equality where conditions as attributes" do
779
+ doc = subject.create
780
+ doc.author.should == "Sam"
781
+ doc.published.should be_true
782
+ doc.date.should be_nil
783
+ end
784
+ end
785
+
786
+ describe "#reverse_order" do
787
+ subject { scoped.reverse_order }
788
+
789
+ it "should set the order values to the reverse order" do
790
+ subject.order_values.should == [:author.desc, :published.asc]
791
+ end
792
+ end
793
+
794
+ describe "#except" do
795
+ context "given :where" do
796
+ it "should return a new scope without where values" do
797
+ s = subject.except(:where)
798
+ s.where_values.should be_empty
799
+ s.order_values.should == [:author.asc, :published.desc]
800
+ s.select_values.should == [:author, :published]
801
+ s.offset_value.should == 15
802
+ s.limit_value.should == 7
803
+ s.collection.should == OtherPost.collection
804
+ end
805
+ end
806
+
807
+ context "given :order" do
808
+ it "should return a new scope without order values" do
809
+ s = subject.except(:order)
810
+ s.where_values.should == [{ :author => "Sam" }, { :published => true }, { :date.lt => timestamp }]
811
+ s.order_values.should be_empty
812
+ s.select_values.should == [:author, :published]
813
+ s.offset_value.should == 15
814
+ s.limit_value.should == 7
815
+ s.collection.should == OtherPost.collection
816
+ end
817
+ end
818
+
819
+ context "given :select" do
820
+ it "should return a new scope without select values" do
821
+ s = subject.except(:select)
822
+ s.where_values.should == [{ :author => "Sam" }, { :published => true }, { :date.lt => timestamp }]
823
+ s.order_values.should == [:author.asc, :published.desc]
824
+ s.select_values.should be_empty
825
+ s.offset_value.should == 15
826
+ s.limit_value.should == 7
827
+ s.collection.should == OtherPost.collection
828
+ end
829
+ end
830
+
831
+ context "given :offset" do
832
+ it "should return a new scope without offset value" do
833
+ s = subject.except(:offset)
834
+ s.where_values.should == [{ :author => "Sam" }, { :published => true }, { :date.lt => timestamp }]
835
+ s.order_values.should == [:author.asc, :published.desc]
836
+ s.select_values.should == [:author, :published]
837
+ s.offset_value.should be_nil
838
+ s.limit_value.should == 7
839
+ s.collection.should == OtherPost.collection
840
+ end
841
+ end
842
+
843
+ context "given :limit" do
844
+ it "should return a new scope without limit value" do
845
+ s = subject.except(:limit)
846
+ s.where_values.should == [{ :author => "Sam" }, { :published => true }, { :date.lt => timestamp }]
847
+ s.order_values.should == [:author.asc, :published.desc]
848
+ s.select_values.should == [:author, :published]
849
+ s.offset_value.should == 15
850
+ s.limit_value.should be_nil
851
+ s.collection.should == OtherPost.collection
852
+ end
853
+ end
854
+
855
+ context "given :from" do
856
+ it "should return a new scope with default collection" do
857
+ s = subject.except(:from)
858
+ s.where_values.should == [{ :author => "Sam" }, { :published => true }, { :date.lt => timestamp }]
859
+ s.order_values.should == [:author.asc, :published.desc]
860
+ s.select_values.should == [:author, :published]
861
+ s.offset_value.should == 15
862
+ s.limit_value.should == 7
863
+ s.collection.should == Post.collection
864
+ end
865
+ end
866
+ end
867
+
868
+ describe "#merge" do
869
+ let(:merged) do
870
+ basic_scope.where(:date.gt => timestamp-1.year).
871
+ order(:date.desc).
872
+ select(:date)
873
+ end
874
+ let(:result) { subject.merge(merged) }
875
+
876
+ it "should combine where values from scopes" do
877
+ result.where_values.should == [
878
+ { :author => "Sam" },
879
+ { :published => true },
880
+ { :date.lt => timestamp },
881
+ { :date.gt => timestamp-1.year }
882
+ ]
883
+ end
884
+
885
+ it "should combine order values from scopes" do
886
+ result.order_values.should == [:author.asc, :published.desc, :date.desc]
887
+ end
888
+
889
+ it "should combine select values from scopes" do
890
+ result.select_values.should == [:author, :published, :date]
891
+ end
892
+
893
+ context "merged scope has offset value" do
894
+ let(:merged) { basic_scope.offset(10) }
895
+
896
+ it "should use offset value from merged scope" do
897
+ result.offset_value.should == 10
898
+ end
899
+ end
900
+
901
+ context "merged scope has no offset value set" do
902
+ let(:merged) { basic_scope }
903
+
904
+ it "should use offset value from original scope" do
905
+ result.offset_value.should == 15
906
+ end
907
+ end
908
+
909
+ context "merged scope has limit value" do
910
+ let(:merged) { basic_scope.limit(50) }
911
+
912
+ it "should use limit value from merged scope" do
913
+ result.limit_value.should == 50
914
+ end
915
+ end
916
+
917
+ context "merged scope has no limit value set" do
918
+ let(:merged) { basic_scope }
919
+
920
+ it "should use limit value from original scope" do
921
+ result.limit_value.should == 7
922
+ end
923
+ end
924
+
925
+ it "should use from value (collection) from merged scope" do
926
+ merged = basic_scope.from(Post.collection)
927
+ subject.merge(merged).collection.should == Post.collection
928
+ end
929
+ end
930
+ end
931
+ end
932
+ end