samflores-couchrest 0.2.1 → 0.12.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. data/README.md +10 -34
  2. data/Rakefile +5 -2
  3. data/bin/couchdir +20 -0
  4. data/examples/model/example.rb +13 -19
  5. data/examples/word_count/word_count.rb +24 -3
  6. data/examples/word_count/word_count_query.rb +6 -7
  7. data/lib/couchrest/core/database.rb +49 -126
  8. data/lib/couchrest/core/document.rb +25 -58
  9. data/lib/couchrest/core/model.rb +612 -0
  10. data/lib/couchrest/core/server.rb +10 -47
  11. data/lib/couchrest/core/validations.rb +328 -0
  12. data/lib/couchrest/monkeypatches.rb +0 -95
  13. data/lib/couchrest.rb +10 -57
  14. data/spec/couchrest/core/database_spec.rb +68 -183
  15. data/spec/couchrest/core/design_spec.rb +1 -1
  16. data/spec/couchrest/core/document_spec.rb +104 -285
  17. data/spec/couchrest/core/model_spec.rb +855 -0
  18. data/spec/couchrest/helpers/pager_spec.rb +1 -1
  19. data/spec/spec_helper.rb +7 -13
  20. metadata +17 -83
  21. data/examples/word_count/word_count_views.rb +0 -26
  22. data/lib/couchrest/core/response.rb +0 -16
  23. data/lib/couchrest/mixins/attachments.rb +0 -31
  24. data/lib/couchrest/mixins/callbacks.rb +0 -483
  25. data/lib/couchrest/mixins/design_doc.rb +0 -64
  26. data/lib/couchrest/mixins/document_queries.rb +0 -48
  27. data/lib/couchrest/mixins/extended_attachments.rb +0 -68
  28. data/lib/couchrest/mixins/extended_document_mixins.rb +0 -6
  29. data/lib/couchrest/mixins/properties.rb +0 -125
  30. data/lib/couchrest/mixins/validation.rb +0 -234
  31. data/lib/couchrest/mixins/views.rb +0 -168
  32. data/lib/couchrest/mixins.rb +0 -4
  33. data/lib/couchrest/more/casted_model.rb +0 -28
  34. data/lib/couchrest/more/extended_document.rb +0 -217
  35. data/lib/couchrest/more/property.rb +0 -40
  36. data/lib/couchrest/support/blank.rb +0 -42
  37. data/lib/couchrest/support/class.rb +0 -191
  38. data/lib/couchrest/validation/auto_validate.rb +0 -163
  39. data/lib/couchrest/validation/contextual_validators.rb +0 -78
  40. data/lib/couchrest/validation/validation_errors.rb +0 -118
  41. data/lib/couchrest/validation/validators/absent_field_validator.rb +0 -74
  42. data/lib/couchrest/validation/validators/confirmation_validator.rb +0 -99
  43. data/lib/couchrest/validation/validators/format_validator.rb +0 -117
  44. data/lib/couchrest/validation/validators/formats/email.rb +0 -66
  45. data/lib/couchrest/validation/validators/formats/url.rb +0 -43
  46. data/lib/couchrest/validation/validators/generic_validator.rb +0 -120
  47. data/lib/couchrest/validation/validators/length_validator.rb +0 -134
  48. data/lib/couchrest/validation/validators/method_validator.rb +0 -89
  49. data/lib/couchrest/validation/validators/numeric_validator.rb +0 -104
  50. data/lib/couchrest/validation/validators/required_field_validator.rb +0 -109
  51. data/spec/couchrest/core/server_spec.rb +0 -35
  52. data/spec/couchrest/more/casted_extended_doc_spec.rb +0 -40
  53. data/spec/couchrest/more/casted_model_spec.rb +0 -98
  54. data/spec/couchrest/more/extended_doc_attachment_spec.rb +0 -130
  55. data/spec/couchrest/more/extended_doc_spec.rb +0 -509
  56. data/spec/couchrest/more/extended_doc_view_spec.rb +0 -207
  57. data/spec/couchrest/more/property_spec.rb +0 -130
  58. data/spec/couchrest/support/class_spec.rb +0 -59
  59. data/spec/fixtures/more/article.rb +0 -34
  60. data/spec/fixtures/more/card.rb +0 -20
  61. data/spec/fixtures/more/course.rb +0 -14
  62. data/spec/fixtures/more/event.rb +0 -6
  63. data/spec/fixtures/more/invoice.rb +0 -17
  64. data/spec/fixtures/more/person.rb +0 -8
  65. data/spec/fixtures/more/question.rb +0 -6
  66. data/spec/fixtures/more/service.rb +0 -12
@@ -0,0 +1,855 @@
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? and title
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