addywaddy-couch_surfer 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,883 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ class Basic
4
+ include CouchSurfer::Model
5
+ end
6
+
7
+ class BasicWithValidation
8
+ include CouchSurfer::Model
9
+
10
+ before :create, :validate
11
+ before :update, :change_name
12
+ key_accessor :name
13
+
14
+ def validate
15
+ throw(:halt, false) unless name
16
+ end
17
+
18
+ def change_name
19
+ self.name = "Bar"
20
+ end
21
+ end
22
+
23
+ class Article
24
+ include CouchSurfer::Model
25
+ #use_database CouchRest.database!('http://127.0.0.1:5984/couchrest-model-test')
26
+ unique_id :slug
27
+
28
+ view_by :date, :descending => true
29
+ view_by :user_id, :date
30
+
31
+ view_by :tags,
32
+ :map =>
33
+ "function(doc) {
34
+ if (doc['couchrest-type'] == 'Article' && doc.tags) {
35
+ doc.tags.forEach(function(tag){
36
+ emit(tag, 1);
37
+ });
38
+ }
39
+ }",
40
+ :reduce =>
41
+ "function(keys, values, rereduce) {
42
+ return sum(values);
43
+ }"
44
+
45
+ key_writer :date
46
+ key_reader :slug, :created_at, :updated_at
47
+ key_accessor :title, :tags
48
+
49
+ timestamps!
50
+
51
+ before(:save, :generate_slug_from_title)
52
+ def generate_slug_from_title
53
+ self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'') if new_document?
54
+ end
55
+ end
56
+
57
+ class WithTemplateAndUniqueID
58
+ include CouchSurfer::Model
59
+
60
+ unique_id do |model|
61
+ model['important-field']
62
+ end
63
+ set_default({
64
+ :preset => 'value',
65
+ 'more-template' => [1,2,3]
66
+ })
67
+ key_accessor :preset
68
+ key_accessor :has_no_default
69
+ end
70
+
71
+ class Question
72
+ include CouchSurfer::Model
73
+
74
+ key_accessor :q, :a
75
+ couchrest_type = 'Question'
76
+ end
77
+
78
+ class Person
79
+ include CouchSurfer::Model
80
+
81
+ key_accessor :name
82
+ def last_name
83
+ name.last
84
+ end
85
+ end
86
+
87
+ class Course
88
+ include CouchSurfer::Model
89
+
90
+ key_accessor :title
91
+ cast :questions, :as => ['Question']
92
+ cast :professor, :as => 'Person'
93
+ cast :final_test_at, :as => 'Time'
94
+ view_by :title
95
+ view_by :dept, :ducktype => true
96
+ end
97
+
98
+ class Event
99
+ include CouchSurfer::Model
100
+
101
+ key_accessor :subject, :occurs_at
102
+
103
+ cast :occurs_at, :as => 'Time', :send => 'parse'
104
+ end
105
+
106
+ describe CouchSurfer::Model do
107
+ before(:all) do
108
+ @cr = CouchRest.new(COUCHHOST)
109
+ @db = @cr.database(TESTDB)
110
+ @db.delete! rescue nil
111
+ @db = @cr.create_db(TESTDB) rescue nil
112
+ @adb = @cr.database('couchrest-model-test')
113
+ @adb.delete! rescue nil
114
+ CouchSurfer::Model.default_database = CouchRest.database!('http://127.0.0.1:5984/couch_surfer-test')
115
+ end
116
+
117
+ describe "setting the database" do
118
+ it "should use the default database" do
119
+ Basic.database.info['db_name'].should == 'couch_surfer-test'
120
+ end
121
+
122
+ it "should be able to overwrite the default" do
123
+ Basic.use_database CouchRest.database!('http://127.0.0.1:5984/couch_surfer-custom')
124
+ Basic.database.info['db_name'].should == 'couch_surfer-custom'
125
+ end
126
+ end
127
+
128
+ describe "a new model" do
129
+ it "should be a new_record" do
130
+ Basic.new.should be_a_new_record
131
+ end
132
+ end
133
+
134
+ describe "a model with key_accessors" do
135
+ it "should allow reading keys" do
136
+ @art = Article.new(:title => 'My Article Title')
137
+ @art.title.should == 'My Article Title'
138
+ end
139
+ it "should allow setting keys" do
140
+ @art = Article.new(:title => 'My Article Title')
141
+ @art.title = 'My New Article Title'
142
+ @art.title.should == 'My New Article Title'
143
+ end
144
+ end
145
+
146
+ describe "a model with key_writers" do
147
+ it "should allow setting keys" do
148
+ @art = Article.new
149
+ t = Time.now
150
+ @art.date = t
151
+ @art.attributes[:date].should == t
152
+ end
153
+ it "should not allow reading keys" do
154
+ @art = Article.new
155
+ t = Time.now
156
+ @art.date = t
157
+ lambda{@art.date}.should raise_error
158
+ end
159
+ end
160
+
161
+ describe "a model with key_readers" do
162
+ it "should allow reading keys" do
163
+ @art = Article.new(:slug => "my-slug")
164
+ @art.slug.should == 'my-slug'
165
+ end
166
+ it "should not allow setting keys" do
167
+ @art = Article.new
168
+ lambda{@art.slug = 'My Article Title'}.should raise_error
169
+ end
170
+ end
171
+
172
+ describe "update attributes without saving" do
173
+ before(:each) do
174
+ a = Article.get "big-bad-danger" rescue nil
175
+ a.destroy if a
176
+ @art = Article.new(:title => "big bad danger")
177
+ @art.save
178
+ end
179
+ it "should work for attribute= methods" do
180
+ @art['title'].should == "big bad danger"
181
+ @art.update_attributes(:date => Time.now, :title => "super danger")
182
+ @art['title'].should == "super danger"
183
+ end
184
+
185
+ it "should flip out if an attribute= method is missing" do
186
+ lambda {
187
+ @art.update_attributes('slug' => "new-slug", :title => "super danger")
188
+ }.should raise_error
189
+ end
190
+
191
+ it "should not change other attributes if there is an error" do
192
+ lambda {
193
+ @art.update_attributes('slug' => "new-slug", :title => "super danger")
194
+ }.should raise_error
195
+ @art['title'].should == "big bad danger"
196
+ end
197
+ end
198
+
199
+ describe "update attributes" do
200
+ before(:each) do
201
+ a = Article.get "big-bad-danger" rescue nil
202
+ a.destroy if a
203
+ @art = Article.new(:title => "big bad danger")
204
+ @art.save
205
+ end
206
+ it "should save" do
207
+ @art['title'].should == "big bad danger"
208
+ @art.update_attributes('date' => Time.now, :title => "super danger")
209
+ loaded = Article.get @art.id
210
+ loaded['title'].should == "super danger"
211
+ end
212
+ end
213
+
214
+ describe "a model with template values" do
215
+ before(:all) do
216
+ @tmpl = WithTemplateAndUniqueID.new
217
+ @tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'important-field' => '1')
218
+ end
219
+ it "should have fields set when new" do
220
+ @tmpl.preset.should == 'value'
221
+ end
222
+ it "shouldn't override explicitly set values" do
223
+ @tmpl2.preset.should == 'not_value'
224
+ end
225
+ it "shouldn't override existing documents" do
226
+ @tmpl2.save
227
+ tmpl2_reloaded = WithTemplateAndUniqueID.get(@tmpl2.id)
228
+ @tmpl2.preset.should == 'not_value'
229
+ tmpl2_reloaded.preset.should == 'not_value'
230
+ end
231
+
232
+ it "shouldn't fill in existing documents" do
233
+ @tmpl2.save
234
+ # If user adds a new default value, shouldn't be retroactively applied to
235
+ # documents upon fetching
236
+ WithTemplateAndUniqueID.set_default({:has_no_default => 'giraffe'})
237
+
238
+ tmpl2_reloaded = WithTemplateAndUniqueID.get(@tmpl2.id)
239
+ @tmpl2.has_no_default.should be_nil
240
+ tmpl2_reloaded.has_no_default.should be_nil
241
+ WithTemplateAndUniqueID.new.has_no_default.should == 'giraffe'
242
+ end
243
+ end
244
+
245
+ describe "getting a model" do
246
+ before(:all) do
247
+ @art = Article.new(:title => 'All About Getting')
248
+ @art.save
249
+ end
250
+ it "should load and instantiate it" do
251
+ foundart = Article.get @art.id
252
+ foundart.title.should == "All About Getting"
253
+ end
254
+ end
255
+
256
+ describe "getting a model with a subobjects array" do
257
+ before(:all) do
258
+ course_doc = {
259
+ "title" => "Metaphysics 200",
260
+ "questions" => [
261
+ {
262
+ "q" => "Carve the ___ of reality at the ___.",
263
+ "a" => ["beast","joints"]
264
+ },{
265
+ "q" => "Who layed the smack down on Leibniz's Law?",
266
+ "a" => "Willard Van Orman Quine"
267
+ }
268
+ ]
269
+ }
270
+ r = Course.database.save course_doc
271
+ @course = Course.get r['id']
272
+ end
273
+ it "should load the course" do
274
+ @course.title.should == "Metaphysics 200"
275
+ end
276
+ it "should instantiate them as such" do
277
+ @course["questions"][0].a[0].should == "beast"
278
+ end
279
+ end
280
+
281
+ describe "finding all instances of a model" do
282
+ before(:all) do
283
+ WithTemplateAndUniqueID.new('important-field' => '1').save
284
+ WithTemplateAndUniqueID.new('important-field' => '2').save
285
+ WithTemplateAndUniqueID.new('important-field' => '3').save
286
+ WithTemplateAndUniqueID.new('important-field' => '4').save
287
+ end
288
+ it "should make the design doc" do
289
+ WithTemplateAndUniqueID.all
290
+ WithTemplateAndUniqueID.all
291
+ d = WithTemplateAndUniqueID.design_doc
292
+ d['views']['all']['map'].should include('WithTemplateAndUniqueID')
293
+ end
294
+ it "should find all" do
295
+ rs = WithTemplateAndUniqueID.all
296
+ rs.length.should == 4
297
+ end
298
+ end
299
+
300
+ describe "finding the first instance of a model" do
301
+ before(:each) do
302
+ @db = reset_test_db!
303
+ WithTemplateAndUniqueID.new('important-field' => '1').save
304
+ WithTemplateAndUniqueID.new('important-field' => '2').save
305
+ WithTemplateAndUniqueID.new('important-field' => '3').save
306
+ WithTemplateAndUniqueID.new('important-field' => '4').save
307
+ end
308
+ it "should make the design doc" do
309
+ WithTemplateAndUniqueID.all
310
+ d = WithTemplateAndUniqueID.design_doc
311
+ d['views']['all']['map'].should include('WithTemplateAndUniqueID')
312
+ end
313
+ it "should find first" do
314
+ rs = WithTemplateAndUniqueID.first
315
+ rs['important-field'].should == "1"
316
+ end
317
+ it "should return nil if no instances are found" do
318
+ WithTemplateAndUniqueID.all.each {|obj| obj.destroy }
319
+ WithTemplateAndUniqueID.first.should be_nil
320
+ end
321
+ end
322
+
323
+ describe "getting a model with a subobject field" do
324
+ before(:all) do
325
+ course_doc = {
326
+ "title" => "Metaphysics 410",
327
+ "professor" => {
328
+ "name" => ["Mark", "Hinchliff"]
329
+ },
330
+ "final_test_at" => "2008/12/19 13:00:00 +0800"
331
+ }
332
+ r = Course.database.save course_doc
333
+ @course = Course.get r['id']
334
+ end
335
+ it "should load the course" do
336
+ @course["professor"]["name"][1].should == "Hinchliff"
337
+ end
338
+ it "should instantiate the professor as a person" do
339
+ @course['professor'].last_name.should == "Hinchliff"
340
+ end
341
+ it "should instantiate the final_test_at as a Time" do
342
+ @course['final_test_at'].should == Time.parse("2008/12/19 13:00:00 +0800")
343
+ end
344
+ end
345
+
346
+ describe "cast keys to any type" do
347
+ before(:all) do
348
+ event_doc = { :subject => "Some event", :occurs_at => Time.now }
349
+ e = Event.database.save event_doc
350
+
351
+ @event = Event.get e['id']
352
+ end
353
+ it "should cast created_at to Time" do
354
+ @event['occurs_at'].should be_an_instance_of(Time)
355
+ end
356
+ end
357
+
358
+ describe "saving a model" do
359
+ before(:all) do
360
+ @obj = Basic.new(:foo => "Bar")
361
+ @obj.save.should == true
362
+ end
363
+
364
+ it "should save the doc" do
365
+ doc = @obj.database.get @obj.id
366
+ doc['_id'].should == @obj.id
367
+ end
368
+
369
+ it "should be set for resaving" do
370
+ rev = @obj.rev
371
+ @obj['another-key'] = "some value"
372
+ @obj.save
373
+ @obj.rev.should_not == rev
374
+ end
375
+
376
+ it "should set the id" do
377
+ @obj.id.should be_an_instance_of(String)
378
+ end
379
+
380
+ it "should set the type" do
381
+ @obj['couchrest-type'].should == 'Basic'
382
+ end
383
+ end
384
+
385
+ describe "creating a model" do
386
+ before(:all) do
387
+ @obj = Basic.create(:foo => "Bar")
388
+ end
389
+
390
+ it "should save the doc" do
391
+ doc = @obj.database.get @obj.id
392
+ doc['_id'].should == @obj.id
393
+ end
394
+
395
+ it "should be set for resaving" do
396
+ rev = @obj.rev
397
+ @obj['another-key'] = "some value"
398
+ @obj.save
399
+ @obj.rev.should_not == rev
400
+ end
401
+
402
+ it "should set the id" do
403
+ @obj.id.should be_an_instance_of(String)
404
+ end
405
+
406
+ it "should set the type" do
407
+ @obj['couchrest-type'].should == 'Basic'
408
+ end
409
+ end
410
+
411
+ describe "saving a model with validation hooks added as extlib" do
412
+ before(:all) do
413
+ @obj = BasicWithValidation.new
414
+ end
415
+
416
+ it "save should return false is the model doesn't save as expected" do
417
+ @obj.save.should be_false
418
+ end
419
+
420
+ it "save! should raise and exception if the model doesn't save" do
421
+ lambda{ @obj.save!}.should raise_error("#{@obj.inspect} failed to save")
422
+ end
423
+ end
424
+
425
+ describe "updating a model with a hook added as extlib" do
426
+
427
+ it "should run the hook method" do
428
+ @obj = BasicWithValidation.new(:name => "Foo")
429
+ @obj.save.should be_true
430
+ @obj.save
431
+ @obj.name.should == "Bar"
432
+ end
433
+
434
+ end
435
+
436
+ describe "saving a model with a unique_id configured" do
437
+ before(:each) do
438
+ @art = Article.new
439
+ @old = Article.database.get('this-is-the-title') rescue nil
440
+ Article.database.delete(@old) if @old
441
+ end
442
+
443
+ it "should be a new document" do
444
+ @art.should be_a_new_document
445
+ @art.title.should be_nil
446
+ end
447
+
448
+ it "should require the title" do
449
+ lambda{@art.save}.should raise_error
450
+ @art.title = 'This is the title'
451
+ @art.save.should == true
452
+ end
453
+
454
+ it "should not change the slug on update" do
455
+ @art.title = 'This is the title'
456
+ @art.save.should == true
457
+ @art.title = 'new title'
458
+ @art.save.should == true
459
+ @art.slug.should == 'this-is-the-title'
460
+ end
461
+
462
+ it "should raise an error when the slug is taken" do
463
+ @art.title = 'This is the title'
464
+ @art.save.should == true
465
+ @art2 = Article.new(:title => 'This is the title!')
466
+ lambda{@art2.save}.should raise_error
467
+ end
468
+
469
+ it "should set the slug" do
470
+ @art.title = 'This is the title'
471
+ @art.save.should == true
472
+ @art.slug.should == 'this-is-the-title'
473
+ end
474
+
475
+ it "should set the id" do
476
+ @art.title = 'This is the title'
477
+ @art.save.should == true
478
+ @art.id.should == 'this-is-the-title'
479
+ end
480
+ end
481
+
482
+ describe "saving a model with a unique_id lambda" do
483
+ before(:each) do
484
+ @templated = WithTemplateAndUniqueID.new
485
+ @old = WithTemplateAndUniqueID.get('very-important') rescue nil
486
+ @old.destroy if @old
487
+ end
488
+
489
+ it "should require the field" do
490
+ lambda{@templated.save}.should raise_error
491
+ @templated['important-field'] = 'very-important'
492
+ @templated.save.should == true
493
+ end
494
+
495
+ it "should save with the id" do
496
+ @templated['important-field'] = 'very-important'
497
+ @templated.save.should == true
498
+ t = WithTemplateAndUniqueID.get('very-important')
499
+ t.should == @templated
500
+ end
501
+
502
+ it "should not change the id on update" do
503
+ @templated['important-field'] = 'very-important'
504
+ @templated.save.should == true
505
+ @templated['important-field'] = 'not-important'
506
+ @templated.save.should == true
507
+ t = WithTemplateAndUniqueID.get('very-important')
508
+ t.should == @templated
509
+ end
510
+
511
+ it "should raise an error when the id is taken" do
512
+ @templated['important-field'] = 'very-important'
513
+ @templated.save.should == true
514
+ lambda{WithTemplateAndUniqueID.new('important-field' => 'very-important').save}.should raise_error
515
+ end
516
+
517
+ it "should set the id" do
518
+ @templated['important-field'] = 'very-important'
519
+ @templated.save.should == true
520
+ @templated.id.should == 'very-important'
521
+ end
522
+ end
523
+
524
+ describe "a model with timestamps" do
525
+ before(:each) do
526
+ oldart = Article.get "saving-this" rescue nil
527
+ oldart.destroy if oldart
528
+ @art = Article.new(:title => "Saving this")
529
+ @art.save
530
+ end
531
+ it "should set the time on create" do
532
+ (Time.now - @art.created_at).should < 2
533
+ foundart = Article.get @art.id
534
+ foundart.created_at.should == foundart.updated_at
535
+ end
536
+ it "should set the time on update" do
537
+ @art.save
538
+ @art.created_at.should < @art.updated_at
539
+ end
540
+ end
541
+
542
+ describe "a model with simple views and a default param" do
543
+ before(:all) do
544
+ written_at = Time.now - 24 * 3600 * 7
545
+ @titles = ["this and that", "also interesting", "more fun", "some junk"]
546
+ @titles.each do |title|
547
+ a = Article.new(:title => title)
548
+ a.date = written_at
549
+ a.save
550
+ written_at += 24 * 3600
551
+ end
552
+ end
553
+
554
+ it "should have a design doc" do
555
+ Article.design_doc["views"]["by_date"].should_not be_nil
556
+ end
557
+
558
+ it "should save the design doc" do
559
+ Article.by_date #rescue nil
560
+ doc = Article.database.get Article.design_doc.id
561
+ doc['views']['by_date'].should_not be_nil
562
+ end
563
+
564
+ it "should return the matching raw view result" do
565
+ view = Article.by_date :raw => true
566
+ view['rows'].length.should == 4
567
+ end
568
+
569
+ it "should not include non-Articles" do
570
+ Article.database.save({"date" => 1})
571
+ view = Article.by_date :raw => true
572
+ view['rows'].length.should == 4
573
+ end
574
+
575
+ it "should return the matching objects (with default argument :descending => true)" do
576
+ articles = Article.by_date
577
+ articles.collect{|a|a.title}.should == @titles.reverse
578
+ end
579
+
580
+ it "should allow you to override default args" do
581
+ articles = Article.by_date :descending => false
582
+ articles.collect{|a|a.title}.should == @titles
583
+ end
584
+ end
585
+
586
+ describe "another model with a simple view" do
587
+ before(:all) do
588
+ Course.database.delete! rescue nil
589
+ @db = @cr.create_db(TESTDB) rescue nil
590
+ %w{aaa bbb ddd eee}.each do |title|
591
+ Course.new(:title => title).save
592
+ end
593
+ end
594
+ it "should make the design doc upon first query" do
595
+ Course.by_title
596
+ doc = Course.design_doc
597
+ doc['views']['all']['map'].should include('Course')
598
+ end
599
+ it "should can query via view" do
600
+ # register methods with method-missing, for local dispatch. method
601
+ # missing lookup table, no heuristics.
602
+ view = Course.view :by_title
603
+ designed = Course.by_title
604
+ view.should == designed
605
+ end
606
+ it "should get them" do
607
+ rs = Course.by_title
608
+ rs.length.should == 4
609
+ end
610
+ it "should yield" do
611
+ courses = []
612
+ rs = Course.by_title # remove me
613
+ Course.view(:by_title) do |course|
614
+ courses << course
615
+ end
616
+ courses[0]["doc"]["title"].should =='aaa'
617
+ end
618
+ end
619
+
620
+ describe "a ducktype view" do
621
+ before(:all) do
622
+ @id = @db.save({:dept => true})['id']
623
+ end
624
+ it "should setup" do
625
+ duck = Course.get(@id) # from a different db
626
+ duck["dept"].should == true
627
+ end
628
+ it "should make the design doc" do
629
+ @as = Course.by_dept
630
+ @doc = Course.design_doc
631
+ @doc["views"]["by_dept"]["map"].should_not include("couchrest")
632
+ end
633
+ it "should not look for class" do |variable|
634
+ @as = Course.by_dept
635
+ @as[0]['_id'].should == @id
636
+ end
637
+ end
638
+
639
+ describe "a model with a compound key view" do
640
+ before(:all) do
641
+ written_at = Time.now - 24 * 3600 * 7
642
+ @titles = ["uniq one", "even more interesting", "less fun", "not junk"]
643
+ @user_ids = ["quentin", "aaron"]
644
+ @titles.each_with_index do |title,i|
645
+ u = i % 2
646
+ a = Article.new(:title => title, :user_id => @user_ids[u])
647
+ a.date = written_at
648
+ a.save
649
+ written_at += 24 * 3600
650
+ end
651
+ end
652
+ it "should create the design doc" do
653
+ Article.by_user_id_and_date rescue nil
654
+ doc = Article.design_doc
655
+ doc['views']['by_date'].should_not be_nil
656
+ end
657
+ it "should sort correctly" do
658
+ articles = Article.by_user_id_and_date
659
+ articles.collect{|a|a['user_id']}.should == ['aaron', 'aaron', 'quentin',
660
+ 'quentin']
661
+ articles[1].title.should == 'not junk'
662
+ end
663
+ it "should be queryable with couchrest options" do
664
+ articles = Article.by_user_id_and_date :limit => 1, :startkey => 'quentin'
665
+ articles.length.should == 1
666
+ articles[0].title.should == "even more interesting"
667
+ end
668
+ end
669
+
670
+ describe "with a custom view" do
671
+ before(:all) do
672
+ @titles = ["very uniq one", "even less interesting", "some fun",
673
+ "really junk", "crazy bob"]
674
+ @tags = ["cool", "lame"]
675
+ @titles.each_with_index do |title,i|
676
+ u = i % 2
677
+ a = Article.new(:title => title, :tags => [@tags[u]])
678
+ a.save
679
+ end
680
+ end
681
+ it "should be available raw" do
682
+ view = Article.by_tags :raw => true
683
+ view['rows'].length.should == 5
684
+ end
685
+
686
+ it "should be default to :reduce => false" do
687
+ ars = Article.by_tags
688
+ ars.first.tags.first.should == 'cool'
689
+ end
690
+
691
+ it "should be raw when reduce is true" do
692
+ view = Article.by_tags :reduce => true, :group => true
693
+ view['rows'].find{|r|r['key'] == 'cool'}['value'].should == 3
694
+ end
695
+ end
696
+
697
+ # TODO: moved to Design, delete
698
+ describe "adding a view" do
699
+ before(:each) do
700
+ Article.by_date
701
+ @design_docs = Article.database.documents :startkey => "_design/",
702
+ :endkey => "_design/\u9999"
703
+ end
704
+ it "should not create a design doc on view definition" do
705
+ Article.view_by :created_at
706
+ newdocs = Article.database.documents :startkey => "_design/",
707
+ :endkey => "_design/\u9999"
708
+ newdocs["rows"].length.should == @design_docs["rows"].length
709
+ end
710
+ it "should create a new design document on view access" do
711
+ Article.view_by :updated_at
712
+ Article.by_updated_at
713
+ newdocs = Article.database.documents :startkey => "_design/",
714
+ :endkey => "_design/\u9999"
715
+ # puts @design_docs.inspect
716
+ # puts newdocs.inspect
717
+ newdocs["rows"].length.should == @design_docs["rows"].length + 1
718
+ end
719
+ end
720
+
721
+ describe "with a lot of designs left around" do
722
+ before(:each) do
723
+ Article.by_date
724
+ Article.view_by :field
725
+ Article.by_field
726
+ end
727
+ it "should clean them up" do
728
+ Article.view_by :stream
729
+ Article.by_stream
730
+ ddocs = Article.all_design_doc_versions
731
+ ddocs["rows"].length.should > 1
732
+ Article.cleanup_design_docs!
733
+ ddocs = Article.all_design_doc_versions
734
+ ddocs["rows"].length.should == 1
735
+ end
736
+ end
737
+
738
+ describe "destroying an instance" do
739
+ before(:each) do
740
+ @obj = Basic.new
741
+ @obj.save.should == true
742
+ end
743
+ it "should return true" do
744
+ result = @obj.destroy
745
+ result.should == true
746
+ end
747
+ it "should be resavable" do
748
+ @obj.destroy
749
+ @obj.rev.should be_nil
750
+ @obj.id.should be_nil
751
+ @obj.save.should == true
752
+ end
753
+ it "should make it go away" do
754
+ @obj.destroy
755
+ lambda{Basic.get(@obj.id)}.should raise_error
756
+ end
757
+ end
758
+
759
+ describe "#has_attachment?" do
760
+ before(:each) do
761
+ @obj = Basic.new
762
+ @obj.save.should == true
763
+ @file = File.open(FIXTURE_PATH + '/attachments/test.html')
764
+ @attachment_name = 'my_attachment'
765
+ @obj.create_attachment(:file => @file, :name => @attachment_name)
766
+ end
767
+
768
+ it 'should return false if there is no attachment' do
769
+ @obj.has_attachment?('bogus').should be_false
770
+ end
771
+
772
+ it 'should return true if there is an attachment' do
773
+ @obj.has_attachment?(@attachment_name).should be_true
774
+ end
775
+
776
+ it 'should return true if an object with an attachment is reloaded' do
777
+ @obj.save.should be_true
778
+ reloaded_obj = Basic.get(@obj.id)
779
+ reloaded_obj.has_attachment?(@attachment_name).should be_true
780
+ end
781
+
782
+ it 'should return false if an attachment has been removed' do
783
+ @obj.delete_attachment(@attachment_name)
784
+ @obj.has_attachment?(@attachment_name).should be_false
785
+ end
786
+ end
787
+
788
+ describe "creating an attachment" do
789
+ before(:each) do
790
+ @obj = Basic.new
791
+ @obj.save.should == true
792
+ @file_ext = File.open(FIXTURE_PATH + '/attachments/test.html')
793
+ @file_no_ext = File.open(FIXTURE_PATH + '/attachments/README')
794
+ @attachment_name = 'my_attachment'
795
+ @content_type = 'media/mp3'
796
+ end
797
+
798
+ it "should create an attachment from file with an extension" do
799
+ @obj.create_attachment(:file => @file_ext, :name => @attachment_name)
800
+ @obj.save.should == true
801
+ reloaded_obj = Basic.get(@obj.id)
802
+ reloaded_obj['_attachments'][@attachment_name].should_not be_nil
803
+ end
804
+
805
+ it "should create an attachment from file without an extension" do
806
+ @obj.create_attachment(:file => @file_no_ext, :name => @attachment_name)
807
+ @obj.save.should == true
808
+ reloaded_obj = Basic.get(@obj.id)
809
+ reloaded_obj['_attachments'][@attachment_name].should_not be_nil
810
+ end
811
+
812
+ it 'should raise ArgumentError if :file is missing' do
813
+ lambda{ @obj.create_attachment(:name => @attachment_name) }.should raise_error
814
+ end
815
+
816
+ it 'should raise ArgumentError if :name is missing' do
817
+ lambda{ @obj.create_attachment(:file => @file_ext) }.should raise_error
818
+ end
819
+
820
+ it 'should set the content-type if passed' do
821
+ @obj.create_attachment(:file => @file_ext, :name => @attachment_name, :content_type => @content_type)
822
+ @obj['_attachments'][@attachment_name]['content-type'].should == @content_type
823
+ end
824
+ end
825
+
826
+ describe 'reading, updating, and deleting an attachment' do
827
+ before(:each) do
828
+ @obj = Basic.new
829
+ @file = File.open(FIXTURE_PATH + '/attachments/test.html')
830
+ @attachment_name = 'my_attachment'
831
+ @obj.create_attachment(:file => @file, :name => @attachment_name)
832
+ @obj.save.should == true
833
+ @file.rewind
834
+ @content_type = 'media/mp3'
835
+ end
836
+
837
+ it 'should read an attachment that exists' do
838
+ @obj.read_attachment(@attachment_name).should == @file.read
839
+ end
840
+
841
+ it 'should update an attachment that exists' do
842
+ file = File.open(FIXTURE_PATH + '/attachments/README')
843
+ @file.should_not == file
844
+ @obj.update_attachment(:file => file, :name => @attachment_name)
845
+ @obj.save
846
+ reloaded_obj = Basic.get(@obj.id)
847
+ file.rewind
848
+ reloaded_obj.read_attachment(@attachment_name).should_not == @file.read
849
+ reloaded_obj.read_attachment(@attachment_name).should == file.read
850
+ end
851
+
852
+ it 'should se the content-type if passed' do
853
+ file = File.open(FIXTURE_PATH + '/attachments/README')
854
+ @file.should_not == file
855
+ @obj.update_attachment(:file => file, :name => @attachment_name, :content_type => @content_type)
856
+ @obj['_attachments'][@attachment_name]['content-type'].should == @content_type
857
+ end
858
+
859
+ it 'should delete an attachment that exists' do
860
+ @obj.delete_attachment(@attachment_name)
861
+ @obj.save
862
+ lambda{Basic.get(@obj.id).read_attachment(@attachment_name)}.should raise_error
863
+ end
864
+ end
865
+
866
+ describe "#attachment_url" do
867
+ before(:each) do
868
+ @obj = Basic.new
869
+ @file = File.open(FIXTURE_PATH + '/attachments/test.html')
870
+ @attachment_name = 'my_attachment'
871
+ @obj.create_attachment(:file => @file, :name => @attachment_name)
872
+ @obj.save.should == true
873
+ end
874
+
875
+ it 'should return nil if attachment does not exist' do
876
+ @obj.attachment_url('bogus').should be_nil
877
+ end
878
+
879
+ it 'should return the attachment URL as specified by CouchDB HttpDocumentApi' do
880
+ @obj.attachment_url(@attachment_name).should == "#{Basic.database}/#{@obj.id}/#{@attachment_name}"
881
+ end
882
+ end
883
+ end