couchrest_extended_document 1.0.0.beta5

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 (71) hide show
  1. data/LICENSE +176 -0
  2. data/README.md +68 -0
  3. data/Rakefile +68 -0
  4. data/THANKS.md +19 -0
  5. data/examples/model/example.rb +144 -0
  6. data/history.txt +159 -0
  7. data/lib/couchrest/casted_array.rb +25 -0
  8. data/lib/couchrest/casted_model.rb +55 -0
  9. data/lib/couchrest/extended_document.rb +323 -0
  10. data/lib/couchrest/mixins/attribute_protection.rb +74 -0
  11. data/lib/couchrest/mixins/callbacks.rb +532 -0
  12. data/lib/couchrest/mixins/class_proxy.rb +120 -0
  13. data/lib/couchrest/mixins/collection.rb +260 -0
  14. data/lib/couchrest/mixins/design_doc.rb +127 -0
  15. data/lib/couchrest/mixins/document_queries.rb +82 -0
  16. data/lib/couchrest/mixins/extended_attachments.rb +73 -0
  17. data/lib/couchrest/mixins/properties.rb +162 -0
  18. data/lib/couchrest/mixins/validation.rb +245 -0
  19. data/lib/couchrest/mixins/views.rb +148 -0
  20. data/lib/couchrest/mixins.rb +11 -0
  21. data/lib/couchrest/property.rb +50 -0
  22. data/lib/couchrest/support/couchrest.rb +19 -0
  23. data/lib/couchrest/support/rails.rb +42 -0
  24. data/lib/couchrest/typecast.rb +175 -0
  25. data/lib/couchrest/validation/auto_validate.rb +156 -0
  26. data/lib/couchrest/validation/contextual_validators.rb +78 -0
  27. data/lib/couchrest/validation/validation_errors.rb +125 -0
  28. data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
  29. data/lib/couchrest/validation/validators/confirmation_validator.rb +107 -0
  30. data/lib/couchrest/validation/validators/format_validator.rb +122 -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 +139 -0
  35. data/lib/couchrest/validation/validators/method_validator.rb +89 -0
  36. data/lib/couchrest/validation/validators/numeric_validator.rb +109 -0
  37. data/lib/couchrest/validation/validators/required_field_validator.rb +114 -0
  38. data/lib/couchrest/validation.rb +245 -0
  39. data/lib/couchrest_extended_document.rb +21 -0
  40. data/spec/couchrest/attribute_protection_spec.rb +150 -0
  41. data/spec/couchrest/casted_extended_doc_spec.rb +79 -0
  42. data/spec/couchrest/casted_model_spec.rb +406 -0
  43. data/spec/couchrest/extended_doc_attachment_spec.rb +148 -0
  44. data/spec/couchrest/extended_doc_inherited_spec.rb +40 -0
  45. data/spec/couchrest/extended_doc_spec.rb +868 -0
  46. data/spec/couchrest/extended_doc_subclass_spec.rb +99 -0
  47. data/spec/couchrest/extended_doc_view_spec.rb +529 -0
  48. data/spec/couchrest/property_spec.rb +648 -0
  49. data/spec/fixtures/attachments/README +3 -0
  50. data/spec/fixtures/attachments/couchdb.png +0 -0
  51. data/spec/fixtures/attachments/test.html +11 -0
  52. data/spec/fixtures/more/article.rb +35 -0
  53. data/spec/fixtures/more/card.rb +22 -0
  54. data/spec/fixtures/more/cat.rb +22 -0
  55. data/spec/fixtures/more/course.rb +25 -0
  56. data/spec/fixtures/more/event.rb +8 -0
  57. data/spec/fixtures/more/invoice.rb +17 -0
  58. data/spec/fixtures/more/person.rb +9 -0
  59. data/spec/fixtures/more/question.rb +6 -0
  60. data/spec/fixtures/more/service.rb +12 -0
  61. data/spec/fixtures/more/user.rb +22 -0
  62. data/spec/fixtures/views/lib.js +3 -0
  63. data/spec/fixtures/views/test_view/lib.js +3 -0
  64. data/spec/fixtures/views/test_view/only-map.js +4 -0
  65. data/spec/fixtures/views/test_view/test-map.js +3 -0
  66. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  67. data/spec/spec.opts +5 -0
  68. data/spec/spec_helper.rb +49 -0
  69. data/utils/remap.rb +27 -0
  70. data/utils/subset.rb +30 -0
  71. metadata +200 -0
@@ -0,0 +1,868 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path("../../spec_helper", __FILE__)
4
+ require File.join(FIXTURE_PATH, 'more', 'article')
5
+ require File.join(FIXTURE_PATH, 'more', 'course')
6
+ require File.join(FIXTURE_PATH, 'more', 'card')
7
+ require File.join(FIXTURE_PATH, 'more', 'cat')
8
+
9
+ describe "ExtendedDocument" do
10
+
11
+ class WithDefaultValues < CouchRest::ExtendedDocument
12
+ use_database TEST_SERVER.default_database
13
+ property :preset, :type => 'Object', :default => {:right => 10, :top_align => false}
14
+ property :set_by_proc, :default => Proc.new{Time.now}, :cast_as => 'Time'
15
+ property :tags, :type => ['String'], :default => []
16
+ property :read_only_with_default, :default => 'generic', :read_only => true
17
+ property :default_false, :type => 'Boolean', :default => false
18
+ property :name
19
+ timestamps!
20
+ end
21
+
22
+ class WithSimplePropertyType < CouchRest::ExtendedDocument
23
+ use_database TEST_SERVER.default_database
24
+ property :name, String
25
+ property :preset, String, :default => 'none'
26
+ property :tags, [String]
27
+ timestamps!
28
+ end
29
+
30
+ class WithCallBacks < CouchRest::ExtendedDocument
31
+ include ::CouchRest::Validation
32
+ use_database TEST_SERVER.default_database
33
+ property :name
34
+ property :run_before_validate
35
+ property :run_after_validate
36
+ property :run_before_save
37
+ property :run_after_save
38
+ property :run_before_create
39
+ property :run_after_create
40
+ property :run_before_update
41
+ property :run_after_update
42
+
43
+ before_validate do |object|
44
+ object.run_before_validate = true
45
+ end
46
+ after_validate do |object|
47
+ object.run_after_validate = true
48
+ end
49
+ before_save do |object|
50
+ object.run_before_save = true
51
+ end
52
+ after_save do |object|
53
+ object.run_after_save = true
54
+ end
55
+ before_create do |object|
56
+ object.run_before_create = true
57
+ end
58
+ after_create do |object|
59
+ object.run_after_create = true
60
+ end
61
+ before_update do |object|
62
+ object.run_before_update = true
63
+ end
64
+ after_update do |object|
65
+ object.run_after_update = true
66
+ end
67
+
68
+ property :run_one
69
+ property :run_two
70
+ property :run_three
71
+
72
+ before_save :run_one_method, :run_two_method do |object|
73
+ object.run_three = true
74
+ end
75
+ def run_one_method
76
+ self.run_one = true
77
+ end
78
+ def run_two_method
79
+ self.run_two = true
80
+ end
81
+
82
+ attr_accessor :run_it
83
+ property :conditional_one
84
+ property :conditional_two
85
+
86
+ before_save :conditional_one_method, :conditional_two_method, :if => proc { self.run_it }
87
+ def conditional_one_method
88
+ self.conditional_one = true
89
+ end
90
+ def conditional_two_method
91
+ self.conditional_two = true
92
+ end
93
+ end
94
+
95
+ class WithTemplateAndUniqueID < CouchRest::ExtendedDocument
96
+ use_database TEST_SERVER.default_database
97
+ unique_id do |model|
98
+ model['important-field']
99
+ end
100
+ property :preset, :default => 'value'
101
+ property :has_no_default
102
+ end
103
+
104
+ class WithGetterAndSetterMethods < CouchRest::ExtendedDocument
105
+ use_database TEST_SERVER.default_database
106
+
107
+ property :other_arg
108
+ def arg
109
+ other_arg
110
+ end
111
+
112
+ def arg=(value)
113
+ self.other_arg = "foo-#{value}"
114
+ end
115
+ end
116
+
117
+ class WithAfterInitializeMethod < CouchRest::ExtendedDocument
118
+ use_database TEST_SERVER.default_database
119
+
120
+ property :some_value
121
+
122
+ def after_initialize
123
+ self.some_value ||= "value"
124
+ end
125
+
126
+ end
127
+
128
+
129
+ before(:each) do
130
+ @obj = WithDefaultValues.new
131
+ end
132
+
133
+ describe "instance database connection" do
134
+ it "should use the default database" do
135
+ @obj.database.name.should == 'couchrest-test'
136
+ end
137
+
138
+ it "should override the default db" do
139
+ @obj.database = TEST_SERVER.database!('couchrest-extendedmodel-test')
140
+ @obj.database.name.should == 'couchrest-extendedmodel-test'
141
+ @obj.database.delete!
142
+ end
143
+ end
144
+
145
+ describe "a new model" do
146
+ it "should be a new document" do
147
+ @obj = Basic.new
148
+ @obj.rev.should be_nil
149
+ @obj.should be_new
150
+ @obj.should be_new_document
151
+ @obj.should be_new_record
152
+ end
153
+
154
+ it "should not failed on a nil value in argument" do
155
+ @obj = Basic.new(nil)
156
+ @obj.should == { 'couchrest-type' => 'Basic' }
157
+ end
158
+ end
159
+
160
+ describe "creating a new document" do
161
+ it "should instantialize and save a document" do
162
+ article = Article.create(:title => 'my test')
163
+ article.title.should == 'my test'
164
+ article.should_not be_new
165
+ end
166
+
167
+ it "should trigger the create callbacks" do
168
+ doc = WithCallBacks.create(:name => 'my other test')
169
+ doc.run_before_create.should be_true
170
+ doc.run_after_create.should be_true
171
+ doc.run_before_save.should be_true
172
+ doc.run_after_save.should be_true
173
+ end
174
+ end
175
+
176
+ describe "creating a new document from database" do
177
+
178
+ it "should instantialize" do
179
+ doc = Article.create_from_database({'_id' => 'testitem1', '_rev' => 123, 'couchrest-type' => 'Article', 'name' => 'my test'})
180
+ doc.class.should eql(Article)
181
+ end
182
+
183
+ it "should instantialize of same class if no couchrest-type included from DB" do
184
+ doc = Article.create_from_database({'_id' => 'testitem1', '_rev' => 123, 'name' => 'my test'})
185
+ doc.class.should eql(Article)
186
+ end
187
+
188
+ it "should instantialize document of different type" do
189
+ doc = Article.create_from_database({'_id' => 'testitem2', '_rev' => 123, 'couchrest-type' => 'WithCallBacks', 'name' => 'my test'})
190
+ doc.class.should eql(WithCallBacks)
191
+ end
192
+
193
+ end
194
+
195
+ describe "update attributes without saving" do
196
+ before(:each) do
197
+ a = Article.get "big-bad-danger" rescue nil
198
+ a.destroy if a
199
+ @art = Article.new(:title => "big bad danger")
200
+ @art.save
201
+ end
202
+ it "should work for attribute= methods" do
203
+ @art['title'].should == "big bad danger"
204
+ @art.update_attributes_without_saving('date' => Time.now, :title => "super danger")
205
+ @art['title'].should == "super danger"
206
+ end
207
+ it "should silently ignore _id" do
208
+ @art.update_attributes_without_saving('_id' => 'foobar')
209
+ @art['_id'].should_not == 'foobar'
210
+ end
211
+ it "should silently ignore _rev" do
212
+ @art.update_attributes_without_saving('_rev' => 'foobar')
213
+ @art['_rev'].should_not == 'foobar'
214
+ end
215
+ it "should silently ignore created_at" do
216
+ @art.update_attributes_without_saving('created_at' => 'foobar')
217
+ @art['created_at'].should_not == 'foobar'
218
+ end
219
+ it "should silently ignore updated_at" do
220
+ @art.update_attributes_without_saving('updated_at' => 'foobar')
221
+ @art['updated_at'].should_not == 'foobar'
222
+ end
223
+ it "should also work using attributes= alias" do
224
+ @art.respond_to?(:attributes=).should be_true
225
+ @art.attributes = {'date' => Time.now, :title => "something else"}
226
+ @art['title'].should == "something else"
227
+ end
228
+
229
+ it "should flip out if an attribute= method is missing" do
230
+ lambda {
231
+ @art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
232
+ }.should raise_error
233
+ end
234
+
235
+ it "should not change other attributes if there is an error" do
236
+ lambda {
237
+ @art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
238
+ }.should raise_error
239
+ @art['title'].should == "big bad danger"
240
+ end
241
+ end
242
+
243
+ describe "update attributes" do
244
+ before(:each) do
245
+ a = Article.get "big-bad-danger" rescue nil
246
+ a.destroy if a
247
+ @art = Article.new(:title => "big bad danger")
248
+ @art.save
249
+ end
250
+ it "should save" do
251
+ @art['title'].should == "big bad danger"
252
+ @art.update_attributes('date' => Time.now, :title => "super danger")
253
+ loaded = Article.get(@art.id)
254
+ loaded['title'].should == "super danger"
255
+ end
256
+ end
257
+
258
+ describe "with default" do
259
+ it "should have the default value set at initalization" do
260
+ @obj.preset.should == {:right => 10, :top_align => false}
261
+ end
262
+
263
+ it "should have the default false value explicitly assigned" do
264
+ @obj.default_false.should == false
265
+ end
266
+
267
+ it "should automatically call a proc default at initialization" do
268
+ @obj.set_by_proc.should be_an_instance_of(Time)
269
+ @obj.set_by_proc.should == @obj.set_by_proc
270
+ @obj.set_by_proc.should < Time.now
271
+ end
272
+
273
+ it "should let you overwrite the default values" do
274
+ obj = WithDefaultValues.new(:preset => 'test')
275
+ obj.preset = 'test'
276
+ end
277
+
278
+ it "should work with a default empty array" do
279
+ obj = WithDefaultValues.new(:tags => ['spec'])
280
+ obj.tags.should == ['spec']
281
+ end
282
+
283
+ it "should set default value of read-only property" do
284
+ obj = WithDefaultValues.new
285
+ obj.read_only_with_default.should == 'generic'
286
+ end
287
+ end
288
+
289
+ describe "simplified way of setting property types" do
290
+ it "should set defaults" do
291
+ obj = WithSimplePropertyType.new
292
+ obj.preset.should eql('none')
293
+ end
294
+
295
+ it "should handle arrays" do
296
+ obj = WithSimplePropertyType.new(:tags => ['spec'])
297
+ obj.tags.should == ['spec']
298
+ end
299
+ end
300
+
301
+ describe "a doc with template values (CR::Model spec)" do
302
+ before(:all) do
303
+ WithTemplateAndUniqueID.all.map{|o| o.destroy(true)}
304
+ WithTemplateAndUniqueID.database.bulk_delete
305
+ @tmpl = WithTemplateAndUniqueID.new
306
+ @tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'important-field' => '1')
307
+ end
308
+ it "should have fields set when new" do
309
+ @tmpl.preset.should == 'value'
310
+ end
311
+ it "shouldn't override explicitly set values" do
312
+ @tmpl2.preset.should == 'not_value'
313
+ end
314
+ it "shouldn't override existing documents" do
315
+ @tmpl2.save
316
+ tmpl2_reloaded = WithTemplateAndUniqueID.get(@tmpl2.id)
317
+ @tmpl2.preset.should == 'not_value'
318
+ tmpl2_reloaded.preset.should == 'not_value'
319
+ end
320
+ end
321
+
322
+ describe "getting a model" do
323
+ before(:all) do
324
+ @art = Article.new(:title => 'All About Getting')
325
+ @art.save
326
+ end
327
+ it "should load and instantiate it" do
328
+ foundart = Article.get @art.id
329
+ foundart.title.should == "All About Getting"
330
+ end
331
+ it "should load and instantiate with find" do
332
+ foundart = Article.find @art.id
333
+ foundart.title.should == "All About Getting"
334
+ end
335
+ it "should return nil if `get` is used and the document doesn't exist" do
336
+ foundart = Article.get 'matt aimonetti'
337
+ foundart.should be_nil
338
+ end
339
+ it "should raise an error if `get!` is used and the document doesn't exist" do
340
+ lambda{foundart = Article.get!('matt aimonetti')}.should raise_error
341
+ end
342
+ it "should raise an error if `find!` is used and the document doesn't exist" do
343
+ lambda{foundart = Article.find!('matt aimonetti')}.should raise_error
344
+ end
345
+ end
346
+
347
+ describe "getting a model with a subobjects array" do
348
+ before(:all) do
349
+ course_doc = {
350
+ "title" => "Metaphysics 200",
351
+ "questions" => [
352
+ {
353
+ "q" => "Carve the ___ of reality at the ___.",
354
+ "a" => ["beast","joints"]
355
+ },{
356
+ "q" => "Who layed the smack down on Leibniz's Law?",
357
+ "a" => "Willard Van Orman Quine"
358
+ }
359
+ ]
360
+ }
361
+ r = Course.database.save_doc course_doc
362
+ @course = Course.get r['id']
363
+ end
364
+ it "should load the course" do
365
+ @course.title.should == "Metaphysics 200"
366
+ end
367
+ it "should instantiate them as such" do
368
+ @course["questions"][0].a[0].should == "beast"
369
+ end
370
+ end
371
+
372
+ describe "finding all instances of a model" do
373
+ before(:all) do
374
+ WithTemplateAndUniqueID.req_design_doc_refresh
375
+ WithTemplateAndUniqueID.all.map{|o| o.destroy(true)}
376
+ WithTemplateAndUniqueID.database.bulk_delete
377
+ WithTemplateAndUniqueID.new('important-field' => '1').save
378
+ WithTemplateAndUniqueID.new('important-field' => '2').save
379
+ WithTemplateAndUniqueID.new('important-field' => '3').save
380
+ WithTemplateAndUniqueID.new('important-field' => '4').save
381
+ end
382
+ it "should make the design doc" do
383
+ WithTemplateAndUniqueID.all
384
+ d = WithTemplateAndUniqueID.design_doc
385
+ d['views']['all']['map'].should include('WithTemplateAndUniqueID')
386
+ end
387
+ it "should find all" do
388
+ rs = WithTemplateAndUniqueID.all
389
+ rs.length.should == 4
390
+ end
391
+ end
392
+
393
+ describe "counting all instances of a model" do
394
+ before(:each) do
395
+ @db = reset_test_db!
396
+ WithTemplateAndUniqueID.req_design_doc_refresh
397
+ end
398
+
399
+ it ".count should return 0 if there are no docuemtns" do
400
+ WithTemplateAndUniqueID.count.should == 0
401
+ end
402
+
403
+ it ".count should return the number of documents" do
404
+ WithTemplateAndUniqueID.new('important-field' => '1').save
405
+ WithTemplateAndUniqueID.new('important-field' => '2').save
406
+ WithTemplateAndUniqueID.new('important-field' => '3').save
407
+
408
+ WithTemplateAndUniqueID.count.should == 3
409
+ end
410
+ end
411
+
412
+ describe "finding the first instance of a model" do
413
+ before(:each) do
414
+ @db = reset_test_db!
415
+ # WithTemplateAndUniqueID.req_design_doc_refresh # Removed by Sam Lown, design doc should be loaded automatically
416
+ WithTemplateAndUniqueID.new('important-field' => '1').save
417
+ WithTemplateAndUniqueID.new('important-field' => '2').save
418
+ WithTemplateAndUniqueID.new('important-field' => '3').save
419
+ WithTemplateAndUniqueID.new('important-field' => '4').save
420
+ end
421
+ it "should make the design doc" do
422
+ WithTemplateAndUniqueID.all
423
+ d = WithTemplateAndUniqueID.design_doc
424
+ d['views']['all']['map'].should include('WithTemplateAndUniqueID')
425
+ end
426
+ it "should find first" do
427
+ rs = WithTemplateAndUniqueID.first
428
+ rs['important-field'].should == "1"
429
+ end
430
+ it "should return nil if no instances are found" do
431
+ WithTemplateAndUniqueID.all.each {|obj| obj.destroy }
432
+ WithTemplateAndUniqueID.first.should be_nil
433
+ end
434
+ end
435
+
436
+ describe "lazily refreshing the design document" do
437
+ before(:all) do
438
+ @db = reset_test_db!
439
+ WithTemplateAndUniqueID.new('important-field' => '1').save
440
+ end
441
+ it "should not save the design doc twice" do
442
+ WithTemplateAndUniqueID.all
443
+ WithTemplateAndUniqueID.req_design_doc_refresh
444
+ WithTemplateAndUniqueID.refresh_design_doc
445
+ rev = WithTemplateAndUniqueID.design_doc['_rev']
446
+ WithTemplateAndUniqueID.req_design_doc_refresh
447
+ WithTemplateAndUniqueID.refresh_design_doc
448
+ WithTemplateAndUniqueID.design_doc['_rev'].should eql(rev)
449
+ end
450
+ end
451
+
452
+ describe "getting a model with a subobject field" do
453
+ before(:all) do
454
+ course_doc = {
455
+ "title" => "Metaphysics 410",
456
+ "professor" => {
457
+ "name" => ["Mark", "Hinchliff"]
458
+ },
459
+ "ends_at" => "2008/12/19 13:00:00 +0800"
460
+ }
461
+ r = Course.database.save_doc course_doc
462
+ @course = Course.get r['id']
463
+ end
464
+ it "should load the course" do
465
+ @course["professor"]["name"][1].should == "Hinchliff"
466
+ end
467
+ it "should instantiate the professor as a person" do
468
+ @course['professor'].last_name.should == "Hinchliff"
469
+ end
470
+ it "should instantiate the ends_at as a Time" do
471
+ @course['ends_at'].should == Time.parse("2008/12/19 13:00:00 +0800")
472
+ end
473
+ end
474
+
475
+ describe "timestamping" do
476
+ before(:each) do
477
+ oldart = Article.get "saving-this" rescue nil
478
+ oldart.destroy if oldart
479
+ @art = Article.new(:title => "Saving this")
480
+ @art.save
481
+ end
482
+
483
+ it "should define the updated_at and created_at getters and set the values" do
484
+ @obj.save
485
+ obj = WithDefaultValues.get(@obj.id)
486
+ obj.should be_an_instance_of(WithDefaultValues)
487
+ obj.created_at.should be_an_instance_of(Time)
488
+ obj.updated_at.should be_an_instance_of(Time)
489
+ obj.created_at.to_s.should == @obj.updated_at.to_s
490
+ end
491
+
492
+ it "should not change created_at on update" do
493
+ 2.times do
494
+ lambda do
495
+ @art.save
496
+ end.should_not change(@art, :created_at)
497
+ end
498
+ end
499
+
500
+ it "should set the time on create" do
501
+ (Time.now - @art.created_at).should < 2
502
+ foundart = Article.get @art.id
503
+ foundart.created_at.should == foundart.updated_at
504
+ end
505
+ it "should set the time on update" do
506
+ @art.save
507
+ @art.created_at.should < @art.updated_at
508
+ end
509
+ end
510
+
511
+ describe "basic saving and retrieving" do
512
+ it "should work fine" do
513
+ @obj.name = "should be easily saved and retrieved"
514
+ @obj.save
515
+ saved_obj = WithDefaultValues.get(@obj.id)
516
+ saved_obj.should_not be_nil
517
+ end
518
+
519
+ it "should parse the Time attributes automatically" do
520
+ @obj.name = "should parse the Time attributes automatically"
521
+ @obj.set_by_proc.should be_an_instance_of(Time)
522
+ @obj.save
523
+ @obj.set_by_proc.should be_an_instance_of(Time)
524
+ saved_obj = WithDefaultValues.get(@obj.id)
525
+ saved_obj.set_by_proc.should be_an_instance_of(Time)
526
+ end
527
+ end
528
+
529
+ describe "saving a model" do
530
+ before(:all) do
531
+ @sobj = Basic.new
532
+ @sobj.save.should == true
533
+ end
534
+
535
+ it "should save the doc" do
536
+ doc = Basic.get(@sobj.id)
537
+ doc['_id'].should == @sobj.id
538
+ end
539
+
540
+ it "should be set for resaving" do
541
+ rev = @obj.rev
542
+ @sobj['another-key'] = "some value"
543
+ @sobj.save
544
+ @sobj.rev.should_not == rev
545
+ end
546
+
547
+ it "should set the id" do
548
+ @sobj.id.should be_an_instance_of(String)
549
+ end
550
+
551
+ it "should set the type" do
552
+ @sobj['couchrest-type'].should == 'Basic'
553
+ end
554
+
555
+ describe "save!" do
556
+
557
+ before(:each) do
558
+ @sobj = Card.new(:first_name => "Marcos", :last_name => "Tapajós")
559
+ end
560
+
561
+ it "should return true if save the document" do
562
+ @sobj.save!.should == true
563
+ end
564
+
565
+ it "should raise error if don't save the document" do
566
+ @sobj.first_name = nil
567
+ lambda { @sobj.save!.should == true }.should raise_error(RuntimeError)
568
+ end
569
+
570
+ end
571
+
572
+
573
+ end
574
+
575
+ describe "saving a model with a unique_id configured" do
576
+ before(:each) do
577
+ @art = Article.new
578
+ @old = Article.database.get('this-is-the-title') rescue nil
579
+ Article.database.delete_doc(@old) if @old
580
+ end
581
+
582
+ it "should be a new document" do
583
+ @art.should be_new
584
+ @art.title.should be_nil
585
+ end
586
+
587
+ it "should require the title" do
588
+ lambda{@art.save}.should raise_error
589
+ @art.title = 'This is the title'
590
+ @art.save.should == true
591
+ end
592
+
593
+ it "should not change the slug on update" do
594
+ @art.title = 'This is the title'
595
+ @art.save.should == true
596
+ @art.title = 'new title'
597
+ @art.save.should == true
598
+ @art.slug.should == 'this-is-the-title'
599
+ end
600
+
601
+ it "should raise an error when the slug is taken" do
602
+ @art.title = 'This is the title'
603
+ @art.save.should == true
604
+ @art2 = Article.new(:title => 'This is the title!')
605
+ lambda{@art2.save}.should raise_error
606
+ end
607
+
608
+ it "should set the slug" do
609
+ @art.title = 'This is the title'
610
+ @art.save.should == true
611
+ @art.slug.should == 'this-is-the-title'
612
+ end
613
+
614
+ it "should set the id" do
615
+ @art.title = 'This is the title'
616
+ @art.save.should == true
617
+ @art.id.should == 'this-is-the-title'
618
+ end
619
+ end
620
+
621
+ describe "saving a model with a unique_id lambda" do
622
+ before(:each) do
623
+ @templated = WithTemplateAndUniqueID.new
624
+ @old = WithTemplateAndUniqueID.get('very-important') rescue nil
625
+ @old.destroy if @old
626
+ end
627
+
628
+ it "should require the field" do
629
+ lambda{@templated.save}.should raise_error
630
+ @templated['important-field'] = 'very-important'
631
+ @templated.save.should == true
632
+ end
633
+
634
+ it "should save with the id" do
635
+ @templated['important-field'] = 'very-important'
636
+ @templated.save.should == true
637
+ t = WithTemplateAndUniqueID.get('very-important')
638
+ t.should == @templated
639
+ end
640
+
641
+ it "should not change the id on update" do
642
+ @templated['important-field'] = 'very-important'
643
+ @templated.save.should == true
644
+ @templated['important-field'] = 'not-important'
645
+ @templated.save.should == true
646
+ t = WithTemplateAndUniqueID.get('very-important')
647
+ t.should == @templated
648
+ end
649
+
650
+ it "should raise an error when the id is taken" do
651
+ @templated['important-field'] = 'very-important'
652
+ @templated.save.should == true
653
+ lambda{WithTemplateAndUniqueID.new('important-field' => 'very-important').save}.should raise_error
654
+ end
655
+
656
+ it "should set the id" do
657
+ @templated['important-field'] = 'very-important'
658
+ @templated.save.should == true
659
+ @templated.id.should == 'very-important'
660
+ end
661
+ end
662
+
663
+ describe "destroying an instance" do
664
+ before(:each) do
665
+ @dobj = Basic.new
666
+ @dobj.save.should == true
667
+ end
668
+ it "should return true" do
669
+ result = @dobj.destroy
670
+ result.should == true
671
+ end
672
+ it "should be resavable" do
673
+ @dobj.destroy
674
+ @dobj.rev.should be_nil
675
+ @dobj.id.should be_nil
676
+ @dobj.save.should == true
677
+ end
678
+ it "should make it go away" do
679
+ @dobj.destroy
680
+ lambda{Basic.get!(@dobj.id)}.should raise_error
681
+ end
682
+ end
683
+
684
+
685
+ describe "callbacks" do
686
+
687
+ before(:each) do
688
+ @doc = WithCallBacks.new
689
+ end
690
+
691
+
692
+ describe "validate" do
693
+ it "should run before_validate before validating" do
694
+ @doc.run_before_validate.should be_nil
695
+ @doc.should be_valid
696
+ @doc.run_before_validate.should be_true
697
+ end
698
+ it "should run after_validate after validating" do
699
+ @doc.run_after_validate.should be_nil
700
+ @doc.should be_valid
701
+ @doc.run_after_validate.should be_true
702
+ end
703
+ end
704
+
705
+ describe "save" do
706
+ it "should run the after filter after saving" do
707
+ @doc.run_after_save.should be_nil
708
+ @doc.save.should be_true
709
+ @doc.run_after_save.should be_true
710
+ end
711
+ it "should run the grouped callbacks before saving" do
712
+ @doc.run_one.should be_nil
713
+ @doc.run_two.should be_nil
714
+ @doc.run_three.should be_nil
715
+ @doc.save.should be_true
716
+ @doc.run_one.should be_true
717
+ @doc.run_two.should be_true
718
+ @doc.run_three.should be_true
719
+ end
720
+ it "should not run conditional callbacks" do
721
+ @doc.run_it = false
722
+ @doc.save.should be_true
723
+ @doc.conditional_one.should be_nil
724
+ @doc.conditional_two.should be_nil
725
+ end
726
+ it "should run conditional callbacks" do
727
+ @doc.run_it = true
728
+ @doc.save.should be_true
729
+ @doc.conditional_one.should be_true
730
+ @doc.conditional_two.should be_true
731
+ end
732
+ end
733
+ describe "create" do
734
+ it "should run the before save filter when creating" do
735
+ @doc.run_before_save.should be_nil
736
+ @doc.create.should_not be_nil
737
+ @doc.run_before_save.should be_true
738
+ end
739
+ it "should run the before create filter" do
740
+ @doc.run_before_create.should be_nil
741
+ @doc.create.should_not be_nil
742
+ @doc.create
743
+ @doc.run_before_create.should be_true
744
+ end
745
+ it "should run the after create filter" do
746
+ @doc.run_after_create.should be_nil
747
+ @doc.create.should_not be_nil
748
+ @doc.create
749
+ @doc.run_after_create.should be_true
750
+ end
751
+ end
752
+ describe "update" do
753
+
754
+ before(:each) do
755
+ @doc.save
756
+ end
757
+ it "should run the before update filter when updating an existing document" do
758
+ @doc.run_before_update.should be_nil
759
+ @doc.update
760
+ @doc.run_before_update.should be_true
761
+ end
762
+ it "should run the after update filter when updating an existing document" do
763
+ @doc.run_after_update.should be_nil
764
+ @doc.update
765
+ @doc.run_after_update.should be_true
766
+ end
767
+ it "should run the before update filter when saving an existing document" do
768
+ @doc.run_before_update.should be_nil
769
+ @doc.save
770
+ @doc.run_before_update.should be_true
771
+ end
772
+
773
+ end
774
+ end
775
+
776
+ describe "getter and setter methods" do
777
+ it "should try to call the arg= method before setting :arg in the hash" do
778
+ @doc = WithGetterAndSetterMethods.new(:arg => "foo")
779
+ @doc['arg'].should be_nil
780
+ @doc[:arg].should be_nil
781
+ @doc.other_arg.should == "foo-foo"
782
+ end
783
+ end
784
+
785
+ describe "initialization" do
786
+ it "should call after_initialize method if available" do
787
+ @doc = WithAfterInitializeMethod.new
788
+ @doc['some_value'].should eql('value')
789
+ end
790
+ end
791
+
792
+ describe "recursive validation on an extended document" do
793
+ before :each do
794
+ reset_test_db!
795
+ @cat = Cat.new(:name => 'Sockington')
796
+ end
797
+
798
+ it "should not save if a nested casted model is invalid" do
799
+ @cat.favorite_toy = CatToy.new
800
+ @cat.should_not be_valid
801
+ @cat.save.should be_false
802
+ lambda{@cat.save!}.should raise_error
803
+ end
804
+
805
+ it "should save when nested casted model is valid" do
806
+ @cat.favorite_toy = CatToy.new(:name => 'Squeaky')
807
+ @cat.should be_valid
808
+ @cat.save.should be_true
809
+ lambda{@cat.save!}.should_not raise_error
810
+ end
811
+
812
+ it "should not save when nested collection contains an invalid casted model" do
813
+ @cat.toys = [CatToy.new(:name => 'Feather'), CatToy.new]
814
+ @cat.should_not be_valid
815
+ @cat.save.should be_false
816
+ lambda{@cat.save!}.should raise_error
817
+ end
818
+
819
+ it "should save when nested collection contains valid casted models" do
820
+ @cat.toys = [CatToy.new(:name => 'feather'), CatToy.new(:name => 'ball-o-twine')]
821
+ @cat.should be_valid
822
+ @cat.save.should be_true
823
+ lambda{@cat.save!}.should_not raise_error
824
+ end
825
+
826
+ it "should not fail if the nested casted model doesn't have validation" do
827
+ Cat.property :trainer, :cast_as => 'Person'
828
+ Cat.validates_presence_of :name
829
+ cat = Cat.new(:name => 'Mr Bigglesworth')
830
+ cat.trainer = Person.new
831
+ cat.trainer.validatable?.should be_false
832
+ cat.should be_valid
833
+ cat.save.should be_true
834
+ end
835
+ end
836
+
837
+ describe "searching the contents of an extended document" do
838
+ before :each do
839
+ @db = reset_test_db!
840
+
841
+ names = ["Fuzzy", "Whiskers", "Mr Bigglesworth", "Sockington", "Smitty", "Sammy", "Samson", "Simon"]
842
+ names.each { |name| Cat.create(:name => name) }
843
+
844
+ search_function = { 'defaults' => {'store' => 'no', 'index' => 'analyzed_no_norms'},
845
+ 'index' => "function(doc) { ret = new Document(); ret.add(doc['name'], {'field':'name'}); return ret; }" }
846
+ @db.save_doc({'_id' => '_design/search', 'fulltext' => {'cats' => search_function}})
847
+ end
848
+
849
+ it "should be able to paginate through a large set of search results" do
850
+ if couchdb_lucene_available?
851
+ names = []
852
+ Cat.paginated_each(:design_doc => "search", :view_name => "cats",
853
+ :q => 'name:S*', :search => true, :include_docs => true, :per_page => 3) do |cat|
854
+ cat.should_not be_nil
855
+ names << cat.name
856
+ end
857
+
858
+ names.size.should == 5
859
+ names.should include('Sockington')
860
+ names.should include('Smitty')
861
+ names.should include('Sammy')
862
+ names.should include('Samson')
863
+ names.should include('Simon')
864
+ end
865
+ end
866
+ end
867
+
868
+ end