couchrest_model 1.0.0 → 1.1.0.beta

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 (41) hide show
  1. data/.gitignore +1 -1
  2. data/Gemfile.lock +19 -20
  3. data/README.md +145 -20
  4. data/VERSION +1 -1
  5. data/couchrest_model.gemspec +2 -3
  6. data/history.txt +14 -0
  7. data/lib/couchrest/model/associations.rb +4 -4
  8. data/lib/couchrest/model/base.rb +5 -0
  9. data/lib/couchrest/model/callbacks.rb +1 -2
  10. data/lib/couchrest/model/collection.rb +1 -1
  11. data/lib/couchrest/model/designs/view.rb +486 -0
  12. data/lib/couchrest/model/designs.rb +81 -0
  13. data/lib/couchrest/model/document_queries.rb +1 -1
  14. data/lib/couchrest/model/persistence.rb +25 -16
  15. data/lib/couchrest/model/properties.rb +5 -1
  16. data/lib/couchrest/model/property.rb +2 -2
  17. data/lib/couchrest/model/proxyable.rb +152 -0
  18. data/lib/couchrest/model/typecast.rb +1 -1
  19. data/lib/couchrest/model/validations/casted_model.rb +3 -1
  20. data/lib/couchrest/model/validations/locale/en.yml +1 -1
  21. data/lib/couchrest/model/validations/uniqueness.rb +6 -7
  22. data/lib/couchrest/model/validations.rb +1 -0
  23. data/lib/couchrest/model/views.rb +11 -9
  24. data/lib/couchrest_model.rb +3 -0
  25. data/spec/couchrest/assocations_spec.rb +2 -2
  26. data/spec/couchrest/base_spec.rb +15 -1
  27. data/spec/couchrest/casted_model_spec.rb +30 -12
  28. data/spec/couchrest/class_proxy_spec.rb +2 -2
  29. data/spec/couchrest/collection_spec.rb +89 -0
  30. data/spec/couchrest/designs/view_spec.rb +766 -0
  31. data/spec/couchrest/designs_spec.rb +110 -0
  32. data/spec/couchrest/persistence_spec.rb +36 -7
  33. data/spec/couchrest/property_spec.rb +15 -0
  34. data/spec/couchrest/proxyable_spec.rb +329 -0
  35. data/spec/couchrest/{validations.rb → validations_spec.rb} +1 -3
  36. data/spec/couchrest/view_spec.rb +19 -91
  37. data/spec/fixtures/base.rb +8 -6
  38. data/spec/fixtures/more/article.rb +1 -1
  39. data/spec/fixtures/more/course.rb +4 -2
  40. metadata +21 -76
  41. data/lib/couchrest/model/view.rb +0 -190
@@ -0,0 +1,766 @@
1
+ require File.expand_path("../../../spec_helper", __FILE__)
2
+
3
+ class DesignViewModel < CouchRest::Model::Base
4
+ use_database DB
5
+ property :name
6
+ property :title
7
+
8
+ design do
9
+ view :by_name
10
+ end
11
+ end
12
+
13
+ describe "Design View" do
14
+
15
+ describe "(unit tests)" do
16
+
17
+ before :each do
18
+ @klass = CouchRest::Model::Designs::View
19
+ end
20
+
21
+ describe ".new" do
22
+
23
+ describe "with invalid parent model" do
24
+ it "should burn" do
25
+ lambda { @klass.new(String) }.should raise_exception
26
+ end
27
+ end
28
+
29
+ describe "with CouchRest Model" do
30
+
31
+ it "should setup attributes" do
32
+ @obj = @klass.new(DesignViewModel, {}, 'test_view')
33
+ @obj.model.should eql(DesignViewModel)
34
+ @obj.name.should eql('test_view')
35
+ @obj.query.should eql({:reduce => false})
36
+ end
37
+
38
+ it "should complain if there is no name" do
39
+ lambda { @klass.new(DesignViewModel, {}, nil) }.should raise_error
40
+ end
41
+
42
+ end
43
+
44
+ describe "with previous view instance" do
45
+
46
+ before :each do
47
+ first = @klass.new(DesignViewModel, {}, 'test_view')
48
+ @obj = @klass.new(first, {:foo => :bar})
49
+ end
50
+
51
+ it "should copy attributes" do
52
+ @obj.model.should eql(DesignViewModel)
53
+ @obj.name.should eql('test_view')
54
+ @obj.query.should eql({:reduce => false, :foo => :bar})
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+
61
+ describe ".create" do
62
+
63
+ before :each do
64
+ @design_doc = {}
65
+ DesignViewModel.stub!(:design_doc).and_return(@design_doc)
66
+ end
67
+
68
+ it "should add a basic view" do
69
+ @klass.create(DesignViewModel, 'test_view', :map => 'foo')
70
+ @design_doc['views']['test_view'].should_not be_nil
71
+ end
72
+
73
+ it "should auto generate mapping from name" do
74
+ lambda { @klass.create(DesignViewModel, 'by_title') }.should_not raise_error
75
+ str = @design_doc['views']['by_title']['map']
76
+ str.should include("((doc['couchrest-type'] == 'DesignViewModel') && (doc['title'] != null))")
77
+ str.should include("emit(doc['title'], 1);")
78
+ str = @design_doc['views']['by_title']['reduce']
79
+ str.should include("return sum(values);")
80
+ end
81
+
82
+ it "should auto generate mapping from name with and" do
83
+ @klass.create(DesignViewModel, 'by_title_and_name')
84
+ str = @design_doc['views']['by_title_and_name']['map']
85
+ str.should include("(doc['title'] != null) && (doc['name'] != null)")
86
+ str.should include("emit([doc['title'], doc['name']], 1);")
87
+ str = @design_doc['views']['by_title_and_name']['reduce']
88
+ str.should include("return sum(values);")
89
+ end
90
+
91
+ end
92
+
93
+ describe "instance methods" do
94
+
95
+ before :each do
96
+ @obj = @klass.new(DesignViewModel, {}, 'test_view')
97
+ end
98
+
99
+ describe "#rows" do
100
+ it "should execute query" do
101
+ @obj.should_receive(:execute).and_return(true)
102
+ @obj.should_receive(:result).twice.and_return({'rows' => []})
103
+ @obj.rows.should be_empty
104
+ end
105
+
106
+ it "should wrap rows in ViewRow class" do
107
+ @obj.should_receive(:execute).and_return(true)
108
+ @obj.should_receive(:result).twice.and_return({'rows' => [{:foo => :bar}]})
109
+ CouchRest::Model::Designs::ViewRow.should_receive(:new).with({:foo => :bar}, @obj.model)
110
+ @obj.rows
111
+ end
112
+ end
113
+
114
+ describe "#all" do
115
+ it "should ensure docs included and call docs" do
116
+ @obj.should_receive(:include_docs!)
117
+ @obj.should_receive(:docs)
118
+ @obj.all
119
+ end
120
+ end
121
+
122
+ describe "#docs" do
123
+ it "should provide docs from rows" do
124
+ @obj.should_receive(:rows).and_return([])
125
+ @obj.docs
126
+ end
127
+ it "should cache the results" do
128
+ @obj.should_receive(:rows).once.and_return([])
129
+ @obj.docs
130
+ @obj.docs
131
+ end
132
+ end
133
+
134
+ describe "#first" do
135
+ it "should provide the first result of loaded query" do
136
+ @obj.should_receive(:result).and_return(true)
137
+ @obj.should_receive(:all).and_return([:foo])
138
+ @obj.first.should eql(:foo)
139
+ end
140
+ it "should perform a query if no results cached" do
141
+ view = mock('SubView')
142
+ @obj.should_receive(:result).and_return(nil)
143
+ @obj.should_receive(:limit).with(1).and_return(view)
144
+ view.should_receive(:all).and_return([:foo])
145
+ @obj.first.should eql(:foo)
146
+ end
147
+ end
148
+
149
+ describe "#last" do
150
+ it "should provide the last result of loaded query" do
151
+ @obj.should_receive(:result).and_return(true)
152
+ @obj.should_receive(:all).and_return([:foo, :bar])
153
+ @obj.first.should eql(:foo)
154
+ end
155
+ it "should perform a query if no results cached" do
156
+ view = mock('SubView')
157
+ @obj.should_receive(:result).and_return(nil)
158
+ @obj.should_receive(:limit).with(1).and_return(view)
159
+ view.should_receive(:descending).and_return(view)
160
+ view.should_receive(:all).and_return([:foo, :bar])
161
+ @obj.last.should eql(:bar)
162
+ end
163
+ end
164
+
165
+ describe "#count" do
166
+ it "should raise an error if view prepared for group" do
167
+ @obj.should_receive(:query).and_return({:group => true})
168
+ lambda { @obj.count }.should raise_error
169
+ end
170
+
171
+ it "should return first row value if reduce possible" do
172
+ view = mock("SubView")
173
+ row = mock("Row")
174
+ @obj.should_receive(:can_reduce?).and_return(true)
175
+ @obj.should_receive(:reduce).and_return(view)
176
+ view.should_receive(:rows).and_return([row])
177
+ row.should_receive(:value).and_return(2)
178
+ @obj.count.should eql(2)
179
+ end
180
+ it "should return 0 if no rows and reduce possible" do
181
+ view = mock("SubView")
182
+ @obj.should_receive(:can_reduce?).and_return(true)
183
+ @obj.should_receive(:reduce).and_return(view)
184
+ view.should_receive(:rows).and_return([])
185
+ @obj.count.should eql(0)
186
+ end
187
+
188
+ it "should perform limit request for total_rows" do
189
+ view = mock("SubView")
190
+ @obj.should_receive(:limit).with(0).and_return(view)
191
+ view.should_receive(:total_rows).and_return(4)
192
+ @obj.should_receive(:can_reduce?).and_return(false)
193
+ @obj.count.should eql(4)
194
+ end
195
+ end
196
+
197
+ describe "#empty?" do
198
+ it "should check the #all method for any results" do
199
+ all = mock("All")
200
+ all.should_receive(:empty?).and_return('win')
201
+ @obj.should_receive(:all).and_return(all)
202
+ @obj.empty?.should eql('win')
203
+ end
204
+ end
205
+
206
+ describe "#each" do
207
+ it "should call each method on all" do
208
+ @obj.should_receive(:all).and_return([])
209
+ @obj.each
210
+ end
211
+ it "should call each and pass block" do
212
+ set = [:foo, :bar]
213
+ @obj.should_receive(:all).and_return(set)
214
+ result = []
215
+ @obj.each do |s|
216
+ result << s
217
+ end
218
+ result.should eql(set)
219
+ end
220
+ end
221
+
222
+ describe "#offset" do
223
+ it "should excute" do
224
+ @obj.should_receive(:execute).and_return({'offset' => 3})
225
+ @obj.offset.should eql(3)
226
+ end
227
+ end
228
+
229
+ describe "#total_rows" do
230
+ it "should excute" do
231
+ @obj.should_receive(:execute).and_return({'total_rows' => 3})
232
+ @obj.total_rows.should eql(3)
233
+ end
234
+ end
235
+
236
+ describe "#keys" do
237
+ it "should request each row and provide key value" do
238
+ row = mock("Row")
239
+ row.should_receive(:key).twice.and_return('foo')
240
+ @obj.should_receive(:rows).and_return([row, row])
241
+ @obj.keys.should eql(['foo', 'foo'])
242
+ end
243
+ end
244
+
245
+ describe "#values" do
246
+ it "should request each row and provide value" do
247
+ row = mock("Row")
248
+ row.should_receive(:value).twice.and_return('foo')
249
+ @obj.should_receive(:rows).and_return([row, row])
250
+ @obj.values.should eql(['foo', 'foo'])
251
+ end
252
+ end
253
+
254
+ describe "#[]" do
255
+ it "should execute and provide requested field" do
256
+ @obj.should_receive(:execute).and_return({'total_rows' => 2})
257
+ @obj['total_rows'].should eql(2)
258
+ end
259
+ end
260
+
261
+ describe "#info" do
262
+ it "should raise error" do
263
+ lambda { @obj.info }.should raise_error
264
+ end
265
+ end
266
+
267
+ describe "#database" do
268
+ it "should update query with value" do
269
+ @obj.should_receive(:update_query).with({:database => 'foo'})
270
+ @obj.database('foo')
271
+ end
272
+ end
273
+
274
+ describe "#key" do
275
+ it "should update query with value" do
276
+ @obj.should_receive(:update_query).with({:key => 'foo'})
277
+ @obj.key('foo')
278
+ end
279
+ it "should raise and error if startkey set" do
280
+ @obj.query[:startkey] = 'bar'
281
+ lambda { @obj.key('foo') }.should raise_error
282
+ end
283
+ it "should raise and error if endkey set" do
284
+ @obj.query[:endkey] = 'bar'
285
+ lambda { @obj.key('foo') }.should raise_error
286
+ end
287
+ it "should raise and error if both startkey and endkey set" do
288
+ @obj.query[:startkey] = 'bar'
289
+ @obj.query[:endkey] = 'bar'
290
+ lambda { @obj.key('foo') }.should raise_error
291
+ end
292
+ end
293
+
294
+ describe "#startkey" do
295
+ it "should update query with value" do
296
+ @obj.should_receive(:update_query).with({:startkey => 'foo'})
297
+ @obj.startkey('foo')
298
+ end
299
+ it "should raise and error if key set" do
300
+ @obj.query[:key] = 'bar'
301
+ lambda { @obj.startkey('foo') }.should raise_error
302
+ end
303
+ end
304
+
305
+ describe "#startkey_doc" do
306
+ it "should update query with value" do
307
+ @obj.should_receive(:update_query).with({:startkey_docid => 'foo'})
308
+ @obj.startkey_doc('foo')
309
+ end
310
+ it "should update query with object id if available" do
311
+ doc = mock("Document")
312
+ doc.should_receive(:id).and_return(44)
313
+ @obj.should_receive(:update_query).with({:startkey_docid => 44})
314
+ @obj.startkey_doc(doc)
315
+ end
316
+ end
317
+
318
+ describe "#endkey" do
319
+ it "should update query with value" do
320
+ @obj.should_receive(:update_query).with({:endkey => 'foo'})
321
+ @obj.endkey('foo')
322
+ end
323
+ it "should raise and error if key set" do
324
+ @obj.query[:key] = 'bar'
325
+ lambda { @obj.endkey('foo') }.should raise_error
326
+ end
327
+ end
328
+
329
+ describe "#endkey_doc" do
330
+ it "should update query with value" do
331
+ @obj.should_receive(:update_query).with({:endkey_docid => 'foo'})
332
+ @obj.endkey_doc('foo')
333
+ end
334
+ it "should update query with object id if available" do
335
+ doc = mock("Document")
336
+ doc.should_receive(:id).and_return(44)
337
+ @obj.should_receive(:update_query).with({:endkey_docid => 44})
338
+ @obj.endkey_doc(doc)
339
+ end
340
+ end
341
+
342
+ describe "#descending" do
343
+ it "should update query" do
344
+ @obj.should_receive(:update_query).with({:descending => true})
345
+ @obj.descending
346
+ end
347
+ end
348
+
349
+ describe "#limit" do
350
+ it "should update query with value" do
351
+ @obj.should_receive(:update_query).with({:limit => 3})
352
+ @obj.limit(3)
353
+ end
354
+ end
355
+
356
+ describe "#skip" do
357
+ it "should update query with value" do
358
+ @obj.should_receive(:update_query).with({:skip => 3})
359
+ @obj.skip(3)
360
+ end
361
+ it "should update query with default value" do
362
+ @obj.should_receive(:update_query).with({:skip => 0})
363
+ @obj.skip
364
+ end
365
+ end
366
+
367
+ describe "#reduce" do
368
+ it "should update query" do
369
+ @obj.should_receive(:can_reduce?).and_return(true)
370
+ @obj.should_receive(:update_query).with({:reduce => true, :include_docs => nil})
371
+ @obj.reduce
372
+ end
373
+ it "should raise error if query cannot be reduced" do
374
+ @obj.should_receive(:can_reduce?).and_return(false)
375
+ lambda { @obj.reduce }.should raise_error
376
+ end
377
+ end
378
+
379
+ describe "#group" do
380
+ it "should update query" do
381
+ @obj.should_receive(:query).and_return({:reduce => true})
382
+ @obj.should_receive(:update_query).with({:group => true})
383
+ @obj.group
384
+ end
385
+ it "should raise error if query not prepared for reduce" do
386
+ @obj.should_receive(:query).and_return({:reduce => false})
387
+ lambda { @obj.group }.should raise_error
388
+ end
389
+ end
390
+
391
+ describe "#group" do
392
+ it "should update query" do
393
+ @obj.should_receive(:query).and_return({:reduce => true})
394
+ @obj.should_receive(:update_query).with({:group => true})
395
+ @obj.group
396
+ end
397
+ it "should raise error if query not prepared for reduce" do
398
+ @obj.should_receive(:query).and_return({:reduce => false})
399
+ lambda { @obj.group }.should raise_error
400
+ end
401
+ end
402
+
403
+ describe "#group_level" do
404
+ it "should update query" do
405
+ @obj.should_receive(:group).and_return(@obj)
406
+ @obj.should_receive(:update_query).with({:group_level => 3})
407
+ @obj.group_level(3)
408
+ end
409
+ end
410
+
411
+ describe "#include_docs" do
412
+ it "should call include_docs! on new view" do
413
+ @obj.should_receive(:update_query).and_return(@obj)
414
+ @obj.should_receive(:include_docs!)
415
+ @obj.include_docs
416
+ end
417
+ end
418
+
419
+ describe "#reset!" do
420
+ it "should empty all cached data" do
421
+ @obj.should_receive(:result=).with(nil)
422
+ @obj.instance_exec { @rows = 'foo'; @docs = 'foo' }
423
+ @obj.reset!
424
+ @obj.instance_exec { @rows }.should be_nil
425
+ @obj.instance_exec { @docs }.should be_nil
426
+ end
427
+ end
428
+
429
+ #### PROTECTED METHODS
430
+
431
+ describe "#include_docs!" do
432
+ it "should set query value" do
433
+ @obj.should_receive(:result).and_return(false)
434
+ @obj.should_not_receive(:reset!)
435
+ @obj.send(:include_docs!)
436
+ @obj.query[:include_docs].should be_true
437
+ end
438
+ it "should reset if result and no docs" do
439
+ @obj.should_receive(:result).and_return(true)
440
+ @obj.should_receive(:include_docs?).and_return(false)
441
+ @obj.should_receive(:reset!)
442
+ @obj.send(:include_docs!)
443
+ @obj.query[:include_docs].should be_true
444
+ end
445
+ it "should raise an error if view is reduced" do
446
+ @obj.query[:reduce] = true
447
+ lambda { @obj.send(:include_docs!) }.should raise_error
448
+ end
449
+ end
450
+
451
+ describe "#include_docs?" do
452
+ it "should return true if set" do
453
+ @obj.should_receive(:query).and_return({:include_docs => true})
454
+ @obj.send(:include_docs?).should be_true
455
+ end
456
+ it "should return false if not set" do
457
+ @obj.should_receive(:query).and_return({})
458
+ @obj.send(:include_docs?).should be_false
459
+ @obj.should_receive(:query).and_return({:include_docs => false})
460
+ @obj.send(:include_docs?).should be_false
461
+ end
462
+ end
463
+
464
+ describe "#update_query" do
465
+ it "returns a new instance of view" do
466
+ @obj.send(:update_query).object_id.should_not eql(@obj.object_id)
467
+ end
468
+
469
+ it "returns a new instance of view with extra parameters" do
470
+ new_obj = @obj.send(:update_query, {:foo => :bar})
471
+ new_obj.query[:foo].should eql(:bar)
472
+ end
473
+ end
474
+
475
+ describe "#design_doc" do
476
+ it "should call design_doc on model" do
477
+ @obj.model.should_receive(:design_doc)
478
+ @obj.send(:design_doc)
479
+ end
480
+ end
481
+
482
+ describe "#can_reduce?" do
483
+ it "should check and prove true" do
484
+ @obj.should_receive(:name).and_return('test_view')
485
+ @obj.should_receive(:design_doc).and_return({'views' => {'test_view' => {'reduce' => 'foo'}}})
486
+ @obj.send(:can_reduce?).should be_true
487
+ end
488
+ it "should check and prove false" do
489
+ @obj.should_receive(:name).and_return('test_view')
490
+ @obj.should_receive(:design_doc).and_return({'views' => {'test_view' => {'reduce' => nil}}})
491
+ @obj.send(:can_reduce?).should be_false
492
+ end
493
+ end
494
+
495
+ describe "#execute" do
496
+ before :each do
497
+ # disable real execution!
498
+ @design_doc = mock("DesignDoc")
499
+ @design_doc.stub!(:view_on)
500
+ @obj.model.stub!(:design_doc).and_return(@design_doc)
501
+ end
502
+
503
+ it "should return previous result if set" do
504
+ @obj.result = "foo"
505
+ @obj.send(:execute).should eql('foo')
506
+ end
507
+
508
+ it "should raise issue if no database" do
509
+ @obj.should_receive(:query).and_return({:database => nil})
510
+ model = mock("SomeModel")
511
+ model.should_receive(:database).and_return(nil)
512
+ @obj.should_receive(:model).and_return(model)
513
+ lambda { @obj.send(:execute) }.should raise_error
514
+ end
515
+
516
+ it "should delete the reduce option if not going to be used" do
517
+ @obj.should_receive(:can_reduce?).and_return(false)
518
+ @obj.query.should_receive(:delete).with(:reduce)
519
+ @obj.send(:execute)
520
+ end
521
+
522
+ it "should populate the results" do
523
+ @obj.should_receive(:can_reduce?).and_return(true)
524
+ @design_doc.should_receive(:view_on).and_return('foos')
525
+ @obj.send(:execute)
526
+ @obj.result.should eql('foos')
527
+ end
528
+
529
+ it "should retry once on a resource not found error" do
530
+ @obj.should_receive(:can_reduce?).and_return(true)
531
+ @obj.model.should_receive(:save_design_doc)
532
+ @design_doc.should_receive(:view_on).ordered.and_raise(RestClient::ResourceNotFound)
533
+ @design_doc.should_receive(:view_on).ordered.and_return('foos')
534
+ @obj.send(:execute)
535
+ @obj.result.should eql('foos')
536
+ end
537
+
538
+ it "should retry twice and fail on a resource not found error" do
539
+ @obj.should_receive(:can_reduce?).and_return(true)
540
+ @obj.model.should_receive(:save_design_doc)
541
+ @design_doc.should_receive(:view_on).twice.and_raise(RestClient::ResourceNotFound)
542
+ lambda { @obj.send(:execute) }.should raise_error(RestClient::ResourceNotFound)
543
+ end
544
+
545
+ it "should remove nil values from query" do
546
+ @obj.should_receive(:can_reduce?).and_return(true)
547
+ @obj.stub!(:use_database).and_return('database')
548
+ @obj.query = {:reduce => true, :limit => nil, :skip => nil}
549
+ @design_doc.should_receive(:view_on).with('database', 'test_view', {:reduce => true})
550
+ @obj.send(:execute)
551
+ end
552
+
553
+
554
+ end
555
+
556
+ describe "pagination methods" do
557
+
558
+ describe "#page" do
559
+ it "should call limit and skip" do
560
+ @obj.should_receive(:limit).with(25).and_return(@obj)
561
+ @obj.should_receive(:skip).with(25).and_return(@obj)
562
+ @obj.page(2)
563
+ end
564
+ end
565
+
566
+ describe "#per" do
567
+ it "should raise an error if page not called before hand" do
568
+ lambda { @obj.per(12) }.should raise_error
569
+ end
570
+ it "should not do anything if number less than or eql 0" do
571
+ view = @obj.page(1)
572
+ view.per(0).should eql(view)
573
+ end
574
+ it "should set limit and update skip" do
575
+ view = @obj.page(2).per(10)
576
+ view.query[:skip].should eql(10)
577
+ view.query[:limit].should eql(10)
578
+ end
579
+ end
580
+
581
+ describe "#total_count" do
582
+ it "set limit and skip to nill and perform count" do
583
+ @obj.should_receive(:limit).with(nil).and_return(@obj)
584
+ @obj.should_receive(:skip).with(nil).and_return(@obj)
585
+ @obj.should_receive(:count).and_return(5)
586
+ @obj.total_count.should eql(5)
587
+ @obj.total_count.should eql(5) # Second to test caching
588
+ end
589
+ end
590
+
591
+ describe "#num_pages" do
592
+ it "should use total_count and limit_value" do
593
+ @obj.should_receive(:total_count).and_return(200)
594
+ @obj.should_receive(:limit_value).and_return(25)
595
+ @obj.num_pages.should eql(8)
596
+ end
597
+ end
598
+
599
+ describe "#current_page" do
600
+ it "should use offset and limit" do
601
+ @obj.should_receive(:offset_value).and_return(25)
602
+ @obj.should_receive(:limit_value).and_return(25)
603
+ @obj.current_page.should eql(2)
604
+ end
605
+ end
606
+ end
607
+ end
608
+ end
609
+
610
+ describe "ViewRow" do
611
+
612
+ before :all do
613
+ @klass = CouchRest::Model::Designs::ViewRow
614
+ end
615
+
616
+ describe "intialize" do
617
+ it "should store reference to model" do
618
+ obj = @klass.new({}, "model")
619
+ obj.model.should eql('model')
620
+ end
621
+ it "should copy details from hash" do
622
+ obj = @klass.new({:foo => :bar, :test => :example}, "")
623
+ obj[:foo].should eql(:bar)
624
+ obj[:test].should eql(:example)
625
+ end
626
+ end
627
+
628
+ describe "running" do
629
+ before :each do
630
+ end
631
+
632
+ it "should provide id" do
633
+ obj = @klass.new({'id' => '123456'}, 'model')
634
+ obj.id.should eql('123456')
635
+ end
636
+
637
+ it "should provide key" do
638
+ obj = @klass.new({'key' => 'thekey'}, 'model')
639
+ obj.key.should eql('thekey')
640
+ end
641
+
642
+ it "should provide the value" do
643
+ obj = @klass.new({'value' => 'thevalue'}, 'model')
644
+ obj.value.should eql('thevalue')
645
+ end
646
+
647
+ it "should provide the raw document" do
648
+ obj = @klass.new({'doc' => 'thedoc'}, 'model')
649
+ obj.raw_doc.should eql('thedoc')
650
+ end
651
+
652
+ it "should instantiate a new document" do
653
+ hash = {'doc' => {'_id' => '12345', 'name' => 'sam'}}
654
+ obj = @klass.new(hash, DesignViewModel)
655
+ doc = mock('DesignViewModel')
656
+ obj.model.should_receive(:build_from_database).with(hash['doc']).and_return(doc)
657
+ obj.doc.should eql(doc)
658
+ end
659
+
660
+ it "should try to load from id if no document" do
661
+ hash = {'id' => '12345', 'value' => 5}
662
+ obj = @klass.new(hash, DesignViewModel)
663
+ doc = mock('DesignViewModel')
664
+ obj.model.should_receive(:get).with('12345').and_return(doc)
665
+ obj.doc.should eql(doc)
666
+ end
667
+
668
+ it "should try to load linked document if available" do
669
+ hash = {'id' => '12345', 'value' => {'_id' => '54321'}}
670
+ obj = @klass.new(hash, DesignViewModel)
671
+ doc = mock('DesignViewModel')
672
+ obj.model.should_receive(:get).with('54321').and_return(doc)
673
+ obj.doc.should eql(doc)
674
+ end
675
+
676
+ it "should try to return nil for document if none available" do
677
+ hash = {'value' => 23} # simulate reduce
678
+ obj = @klass.new(hash, DesignViewModel)
679
+ doc = mock('DesignViewModel')
680
+ obj.model.should_not_receive(:get)
681
+ obj.doc.should be_nil
682
+ end
683
+
684
+
685
+ end
686
+
687
+ end
688
+
689
+
690
+ describe "scenarios" do
691
+
692
+ before :all do
693
+ @objs = [
694
+ {:name => "Judith"},
695
+ {:name => "Lorena"},
696
+ {:name => "Peter"},
697
+ {:name => "Sam"},
698
+ {:name => "Vilma"}
699
+ ].map{|h| DesignViewModel.create(h)}
700
+ end
701
+
702
+ describe "loading documents" do
703
+
704
+ it "should return first" do
705
+ DesignViewModel.by_name.first.name.should eql("Judith")
706
+ end
707
+
708
+ it "should return last" do
709
+ DesignViewModel.by_name.last.name.should eql("Vilma")
710
+ end
711
+
712
+ it "should allow multiple results" do
713
+ view = DesignViewModel.by_name.limit(3)
714
+ view.total_rows.should eql(5)
715
+ view.last.name.should eql("Peter")
716
+ view.all.length.should eql(3)
717
+ end
718
+
719
+ end
720
+
721
+ describe "index information" do
722
+ it "should provide total_rows" do
723
+ DesignViewModel.by_name.total_rows.should eql(5)
724
+ end
725
+ it "should provide total_rows" do
726
+ DesignViewModel.by_name.total_rows.should eql(5)
727
+ end
728
+ it "should provide an offset" do
729
+ DesignViewModel.by_name.offset.should eql(0)
730
+ end
731
+ it "should provide a set of keys" do
732
+ DesignViewModel.by_name.limit(2).keys.should eql(["Judith", "Lorena"])
733
+ end
734
+ end
735
+
736
+ describe "pagination" do
737
+ before :all do
738
+ DesignViewModel.paginates_per 3
739
+ end
740
+ before :each do
741
+ @view = DesignViewModel.by_name.page(1)
742
+ end
743
+
744
+ it "should calculate number of pages" do
745
+ @view.num_pages.should eql(2)
746
+ end
747
+ it "should return results from first page" do
748
+ @view.all.first.name.should eql('Judith')
749
+ @view.all.last.name.should eql('Peter')
750
+ end
751
+ it "should return results from second page" do
752
+ @view.page(2).all.first.name.should eql('Sam')
753
+ @view.page(2).all.last.name.should eql('Vilma')
754
+ end
755
+
756
+ it "should allow overriding per page count" do
757
+ @view = @view.per(10)
758
+ @view.num_pages.should eql(1)
759
+ @view.all.last.name.should eql('Vilma')
760
+ end
761
+ end
762
+
763
+ end
764
+
765
+
766
+ end