addywaddy-couch_surfer 0.0.1

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