jchris-couchrest 0.12.6 → 0.16

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