couchrest_model 1.0.0 → 1.1.0.beta

Sign up to get free protection for your applications and to get access to all the features.
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