mongomodel 0.1.6 → 0.2.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.
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