davber_couchrest_extended_document 1.0.0

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 +250 -0
  3. data/Rakefile +69 -0
  4. data/THANKS.md +22 -0
  5. data/examples/model/example.rb +144 -0
  6. data/history.txt +165 -0
  7. data/lib/couchrest/casted_array.rb +39 -0
  8. data/lib/couchrest/casted_model.rb +53 -0
  9. data/lib/couchrest/extended_document.rb +262 -0
  10. data/lib/couchrest/mixins.rb +12 -0
  11. data/lib/couchrest/mixins/attribute_protection.rb +74 -0
  12. data/lib/couchrest/mixins/attributes.rb +75 -0
  13. data/lib/couchrest/mixins/callbacks.rb +534 -0
  14. data/lib/couchrest/mixins/class_proxy.rb +120 -0
  15. data/lib/couchrest/mixins/collection.rb +260 -0
  16. data/lib/couchrest/mixins/design_doc.rb +159 -0
  17. data/lib/couchrest/mixins/document_queries.rb +82 -0
  18. data/lib/couchrest/mixins/extended_attachments.rb +73 -0
  19. data/lib/couchrest/mixins/properties.rb +130 -0
  20. data/lib/couchrest/mixins/typecast.rb +174 -0
  21. data/lib/couchrest/mixins/views.rb +148 -0
  22. data/lib/couchrest/property.rb +96 -0
  23. data/lib/couchrest/support/couchrest.rb +19 -0
  24. data/lib/couchrest/support/rails.rb +42 -0
  25. data/lib/couchrest/validation.rb +246 -0
  26. data/lib/couchrest/validation/auto_validate.rb +156 -0
  27. data/lib/couchrest/validation/contextual_validators.rb +78 -0
  28. data/lib/couchrest/validation/validation_errors.rb +125 -0
  29. data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
  30. data/lib/couchrest/validation/validators/confirmation_validator.rb +107 -0
  31. data/lib/couchrest/validation/validators/format_validator.rb +122 -0
  32. data/lib/couchrest/validation/validators/formats/email.rb +66 -0
  33. data/lib/couchrest/validation/validators/formats/url.rb +43 -0
  34. data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
  35. data/lib/couchrest/validation/validators/length_validator.rb +139 -0
  36. data/lib/couchrest/validation/validators/method_validator.rb +89 -0
  37. data/lib/couchrest/validation/validators/numeric_validator.rb +109 -0
  38. data/lib/couchrest/validation/validators/required_field_validator.rb +114 -0
  39. data/lib/couchrest_extended_document.rb +23 -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 +424 -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 +869 -0
  46. data/spec/couchrest/extended_doc_subclass_spec.rb +101 -0
  47. data/spec/couchrest/extended_doc_view_spec.rb +529 -0
  48. data/spec/couchrest/property_spec.rb +790 -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 +7 -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 +225 -0
@@ -0,0 +1,79 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+ require File.join(FIXTURE_PATH, 'more', 'cat')
3
+ require File.join(FIXTURE_PATH, 'more', 'person')
4
+ require File.join(FIXTURE_PATH, 'more', 'card')
5
+
6
+ class Driver < CouchRest::ExtendedDocument
7
+ use_database TEST_SERVER.default_database
8
+ # You have to add a casted_by accessor if you want to reach a casted extended doc parent
9
+ attr_accessor :casted_by
10
+
11
+ property :name
12
+ end
13
+
14
+ class Car < CouchRest::ExtendedDocument
15
+ use_database TEST_SERVER.default_database
16
+
17
+ property :name
18
+ property :driver, :cast_as => 'Driver'
19
+ property :backseat_driver, :cast_as => Driver
20
+ end
21
+
22
+ describe "casting an extended document" do
23
+
24
+ before(:each) do
25
+ @driver = Driver.new(:name => 'Matt')
26
+ @car = Car.new(:name => 'Renault 306', :driver => @driver)
27
+ @car2 = Car.new(:name => 'Renault 306', :backseat_driver => @driver.dup)
28
+ end
29
+
30
+ it "should retain all properties of the casted attribute" do
31
+ @car.driver.should == @driver
32
+ @car2.backseat_driver.should == @driver
33
+ end
34
+
35
+ it "should let the casted document know who casted it" do
36
+ @car.driver.casted_by.should == @car
37
+ @car2.backseat_driver.casted_by.should == @car2
38
+ end
39
+ end
40
+
41
+ describe "assigning a value to casted attribute after initializing an object" do
42
+
43
+ before(:each) do
44
+ @car = Car.new(:name => 'Renault 306')
45
+ @driver = Driver.new(:name => 'Matt')
46
+ end
47
+
48
+ it "should not create an empty casted object" do
49
+ @car.driver.should be_nil
50
+ end
51
+
52
+ it "should let you assign the value" do
53
+ @car.driver = @driver
54
+ @car.driver.name.should == 'Matt'
55
+ end
56
+
57
+ it "should cast attribute" do
58
+ @car.driver = JSON.parse(@driver.to_json)
59
+ @car.driver.should be_instance_of(Driver)
60
+ end
61
+
62
+ end
63
+
64
+ describe "casting an extended document from parsed JSON" do
65
+
66
+ before(:each) do
67
+ @driver = Driver.new(:name => 'Matt')
68
+ @car = Car.new(:name => 'Renault 306', :driver => @driver)
69
+ @new_car = Car.new(JSON.parse(@car.to_json))
70
+ end
71
+
72
+ it "should cast casted attribute" do
73
+ @new_car.driver.should be_instance_of(Driver)
74
+ end
75
+
76
+ it "should retain all properties of the casted attribute" do
77
+ @new_car.driver.should == @driver
78
+ end
79
+ end
@@ -0,0 +1,424 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path('../../spec_helper', __FILE__)
4
+ require File.join(FIXTURE_PATH, 'more', 'person')
5
+ require File.join(FIXTURE_PATH, 'more', 'card')
6
+ require File.join(FIXTURE_PATH, 'more', 'cat')
7
+ require File.join(FIXTURE_PATH, 'more', 'question')
8
+ require File.join(FIXTURE_PATH, 'more', 'course')
9
+
10
+
11
+ class WithCastedModelMixin < Hash
12
+ include CouchRest::CastedModel
13
+ property :name
14
+ property :no_value
15
+ property :details, :type => 'Object', :default => {}
16
+ property :casted_attribute, :cast_as => 'WithCastedModelMixin'
17
+ end
18
+
19
+ class DummyModel < CouchRest::ExtendedDocument
20
+ use_database TEST_SERVER.default_database
21
+ raise "Default DB not set" if TEST_SERVER.default_database.nil?
22
+ property :casted_attribute, WithCastedModelMixin
23
+ property :keywords, ["String"]
24
+ property :sub_models do |child|
25
+ child.property :title
26
+ end
27
+ end
28
+
29
+ class CastedCallbackDoc < CouchRest::ExtendedDocument
30
+ use_database TEST_SERVER.default_database
31
+ raise "Default DB not set" if TEST_SERVER.default_database.nil?
32
+ property :callback_model, :cast_as => 'WithCastedCallBackModel'
33
+ end
34
+ class WithCastedCallBackModel < Hash
35
+ include CouchRest::CastedModel
36
+ include CouchRest::Validation
37
+ property :name
38
+ property :run_before_validate
39
+ property :run_after_validate
40
+
41
+ before_validate do |object|
42
+ object.run_before_validate = true
43
+ end
44
+ after_validate do |object|
45
+ object.run_after_validate = true
46
+ end
47
+ end
48
+
49
+ describe CouchRest::CastedModel do
50
+
51
+ describe "A non hash class including CastedModel" do
52
+ it "should fail raising and include error" do
53
+ lambda do
54
+ class NotAHashButWithCastedModelMixin
55
+ include CouchRest::CastedModel
56
+ property :name
57
+ end
58
+
59
+ end.should raise_error
60
+ end
61
+ end
62
+
63
+ describe "isolated" do
64
+ before(:each) do
65
+ @obj = WithCastedModelMixin.new
66
+ end
67
+ it "should automatically include the property mixin and define getters and setters" do
68
+ @obj.name = 'Matt'
69
+ @obj.name.should == 'Matt'
70
+ end
71
+
72
+ it "should allow override of default" do
73
+ @obj = WithCastedModelMixin.new(:name => 'Eric', :details => {'color' => 'orange'})
74
+ @obj.name.should == 'Eric'
75
+ @obj.details['color'].should == 'orange'
76
+ end
77
+ end
78
+
79
+ describe "casted as an attribute, but without a value" do
80
+ before(:each) do
81
+ @obj = DummyModel.new
82
+ @casted_obj = @obj.casted_attribute
83
+ end
84
+ it "should be nil" do
85
+ @casted_obj.should == nil
86
+ end
87
+ end
88
+
89
+ describe "anonymous sub casted models" do
90
+ before :each do
91
+ @obj = DummyModel.new
92
+ end
93
+ it "should be empty initially" do
94
+ @obj.sub_models.should_not be_nil
95
+ @obj.sub_models.should be_empty
96
+ end
97
+ it "should be updatable using a hash" do
98
+ @obj.sub_models << {:title => 'test'}
99
+ @obj.sub_models.first.title.should eql('test')
100
+ end
101
+ end
102
+
103
+ describe "casted as attribute" do
104
+ before(:each) do
105
+ casted = {:name => 'not whatever'}
106
+ @obj = DummyModel.new(:casted_attribute => {:name => 'whatever', :casted_attribute => casted})
107
+ @casted_obj = @obj.casted_attribute
108
+ end
109
+
110
+ it "should be available from its parent" do
111
+ @casted_obj.should be_an_instance_of(WithCastedModelMixin)
112
+ end
113
+
114
+ it "should have the getters defined" do
115
+ @casted_obj.name.should == 'whatever'
116
+ end
117
+
118
+ it "should know who casted it" do
119
+ @casted_obj.casted_by.should == @obj
120
+ end
121
+
122
+ it "should return nil for the 'no_value' attribute" do
123
+ @casted_obj.no_value.should be_nil
124
+ end
125
+
126
+ it "should return nil for the unknown attribute" do
127
+ @casted_obj["unknown"].should be_nil
128
+ end
129
+
130
+ it "should return {} for the hash attribute" do
131
+ @casted_obj.details.should == {}
132
+ end
133
+
134
+ it "should cast its own attributes" do
135
+ @casted_obj.casted_attribute.should be_instance_of(WithCastedModelMixin)
136
+ end
137
+ end
138
+
139
+ describe "casted as an array of a different type" do
140
+ before(:each) do
141
+ @obj = DummyModel.new(:keywords => ['couch', 'sofa', 'relax', 'canapé'])
142
+ end
143
+
144
+ it "should cast the array properly" do
145
+ @obj.keywords.should be_an_instance_of(Array)
146
+ @obj.keywords.first.should == 'couch'
147
+ end
148
+ end
149
+
150
+ describe "update attributes without saving" do
151
+ before(:each) do
152
+ @question = Question.new(:q => "What is your quest?", :a => "To seek the Holy Grail")
153
+ end
154
+ it "should work for attribute= methods" do
155
+ @question.q.should == "What is your quest?"
156
+ @question['a'].should == "To seek the Holy Grail"
157
+ @question.update_attributes_without_saving(:q => "What is your favorite color?", 'a' => "Blue")
158
+ @question['q'].should == "What is your favorite color?"
159
+ @question.a.should == "Blue"
160
+ end
161
+
162
+ it "should also work for attributes= alias" do
163
+ @question.respond_to?(:attributes=).should be_true
164
+ @question.attributes = {:q => "What is your favorite color?", 'a' => "Blue"}
165
+ @question['q'].should == "What is your favorite color?"
166
+ @question.a.should == "Blue"
167
+ end
168
+
169
+ it "should flip out if an attribute= method is missing" do
170
+ lambda {
171
+ @q.update_attributes_without_saving('foo' => "something", :a => "No green")
172
+ }.should raise_error(NoMethodError)
173
+ end
174
+
175
+ it "should not change any attributes if there is an error" do
176
+ lambda {
177
+ @q.update_attributes_without_saving('foo' => "something", :a => "No green")
178
+ }.should raise_error(NoMethodError)
179
+ @question.q.should == "What is your quest?"
180
+ @question.a.should == "To seek the Holy Grail"
181
+ end
182
+ end
183
+
184
+ describe "saved document with casted models" do
185
+ before(:each) do
186
+ reset_test_db!
187
+ @obj = DummyModel.new(:casted_attribute => {:name => 'whatever'})
188
+ @obj.save.should be_true
189
+ @obj = DummyModel.get(@obj.id)
190
+ end
191
+
192
+ it "should be able to load with the casted models" do
193
+ casted_obj = @obj.casted_attribute
194
+ casted_obj.should_not be_nil
195
+ casted_obj.should be_an_instance_of(WithCastedModelMixin)
196
+ end
197
+
198
+ it "should have defined getters for the casted model" do
199
+ casted_obj = @obj.casted_attribute
200
+ casted_obj.name.should == "whatever"
201
+ end
202
+
203
+ it "should have defined setters for the casted model" do
204
+ casted_obj = @obj.casted_attribute
205
+ casted_obj.name = "test"
206
+ casted_obj.name.should == "test"
207
+ end
208
+
209
+ it "should retain an override of a casted model attribute's default" do
210
+ casted_obj = @obj.casted_attribute
211
+ casted_obj.details['color'] = 'orange'
212
+ @obj.save
213
+ casted_obj = DummyModel.get(@obj.id).casted_attribute
214
+ casted_obj.details['color'].should == 'orange'
215
+ end
216
+
217
+ end
218
+
219
+ describe "saving document with array of casted models and validation" do
220
+ before :each do
221
+ @cat = Cat.new :name => "felix"
222
+ @cat.save
223
+ end
224
+
225
+ it "should save" do
226
+ toy = CatToy.new :name => "Mouse"
227
+ @cat.toys.push(toy)
228
+ @cat.save.should be_true
229
+ @cat = Cat.get @cat.id
230
+ @cat.toys.class.should == CouchRest::CastedArray
231
+ @cat.toys.first.class.should == CatToy
232
+ @cat.toys.first.should === toy
233
+ end
234
+
235
+ it "should fail because name is not present" do
236
+ toy = CatToy.new
237
+ @cat.toys.push(toy)
238
+ @cat.should_not be_valid
239
+ @cat.save.should be_false
240
+ end
241
+
242
+ it "should not fail if the casted model doesn't have validation" do
243
+ Cat.property :masters, :cast_as => ['Person'], :default => []
244
+ Cat.validates_presence_of :name
245
+ cat = Cat.new(:name => 'kitty')
246
+ cat.should be_valid
247
+ cat.masters.push Person.new
248
+ cat.should be_valid
249
+ end
250
+ end
251
+
252
+ describe "calling valid?" do
253
+ before :each do
254
+ @cat = Cat.new
255
+ @toy1 = CatToy.new
256
+ @toy2 = CatToy.new
257
+ @toy3 = CatToy.new
258
+ @cat.favorite_toy = @toy1
259
+ @cat.toys << @toy2
260
+ @cat.toys << @toy3
261
+ end
262
+
263
+ describe "on the top document" do
264
+ it "should put errors on all invalid casted models" do
265
+ @cat.should_not be_valid
266
+ @cat.errors.should_not be_empty
267
+ @toy1.errors.should_not be_empty
268
+ @toy2.errors.should_not be_empty
269
+ @toy3.errors.should_not be_empty
270
+ end
271
+
272
+ it "should not put errors on valid casted models" do
273
+ @toy1.name = "Feather"
274
+ @toy2.name = "Twine"
275
+ @cat.should_not be_valid
276
+ @cat.errors.should_not be_empty
277
+ @toy1.errors.should be_empty
278
+ @toy2.errors.should be_empty
279
+ @toy3.errors.should_not be_empty
280
+ end
281
+ end
282
+
283
+ describe "on a casted model property" do
284
+ it "should only validate itself" do
285
+ @toy1.should_not be_valid
286
+ @toy1.errors.should_not be_empty
287
+ @cat.errors.should be_empty
288
+ @toy2.errors.should be_empty
289
+ @toy3.errors.should be_empty
290
+ end
291
+ end
292
+
293
+ describe "on a casted model inside a casted collection" do
294
+ it "should only validate itself" do
295
+ @toy2.should_not be_valid
296
+ @toy2.errors.should_not be_empty
297
+ @cat.errors.should be_empty
298
+ @toy1.errors.should be_empty
299
+ @toy3.errors.should be_empty
300
+ end
301
+ end
302
+ end
303
+
304
+ describe "calling new? on a casted model" do
305
+ before :each do
306
+ reset_test_db!
307
+ @cat = Cat.new(:name => 'Sockington')
308
+ @favorite_toy = CatToy.new(:name => 'Catnip Ball')
309
+ @cat.favorite_toy = @favorite_toy
310
+ @cat.toys << CatToy.new(:name => 'Fuzzy Stick')
311
+ end
312
+
313
+ it "should be true on new" do
314
+ CatToy.new.should be_new
315
+ CatToy.new.new_record?.should be_true
316
+ end
317
+
318
+ it "should be true after assignment" do
319
+ @cat.should be_new
320
+ @cat.favorite_toy.should be_new
321
+ @cat.toys.first.should be_new
322
+ end
323
+
324
+ it "should not be true after create or save" do
325
+ @cat.create
326
+ @cat.save
327
+ @cat.favorite_toy.should_not be_new
328
+ @cat.toys.first.casted_by.should eql(@cat)
329
+ @cat.toys.first.should_not be_new
330
+ end
331
+
332
+ it "should not be true after get from the database" do
333
+ @cat.save
334
+ @cat = Cat.get(@cat.id)
335
+ @cat.favorite_toy.should_not be_new
336
+ @cat.toys.first.should_not be_new
337
+ end
338
+
339
+ it "should still be true after a failed create or save" do
340
+ @cat.name = nil
341
+ @cat.create.should be_false
342
+ @cat.save.should be_false
343
+ @cat.favorite_toy.should be_new
344
+ @cat.toys.first.should be_new
345
+ end
346
+ end
347
+
348
+ describe "calling base_doc from a nested casted model" do
349
+ before :each do
350
+ @course = Course.new(:title => 'Science 101')
351
+ @professor = Person.new(:name => 'Professor Plum')
352
+ @cat = Cat.new(:name => 'Scratchy')
353
+ @toy1 = CatToy.new
354
+ @toy2 = CatToy.new
355
+ @course.professor = @professor
356
+ @professor.pet = @cat
357
+ @cat.favorite_toy = @toy1
358
+ @cat.toys << @toy2
359
+ end
360
+
361
+ it "should reference the top document for" do
362
+ @course.base_doc.should === @course
363
+ @professor.casted_by.should === @course
364
+ @professor.base_doc.should === @course
365
+ @cat.base_doc.should === @course
366
+ @toy1.base_doc.should === @course
367
+ @toy2.base_doc.should === @course
368
+ end
369
+
370
+ it "should call setter on top document" do
371
+ @toy1.base_doc.should_not be_nil
372
+ @toy1.base_doc.title = 'Tom Foolery'
373
+ @course.title.should == 'Tom Foolery'
374
+ end
375
+
376
+ it "should return nil if not yet casted" do
377
+ person = Person.new
378
+ person.base_doc.should == nil
379
+ end
380
+ end
381
+
382
+ describe "calling base_doc.save from a nested casted model" do
383
+ before :each do
384
+ reset_test_db!
385
+ @cat = Cat.new(:name => 'Snowball')
386
+ @toy = CatToy.new
387
+ @cat.favorite_toy = @toy
388
+ end
389
+
390
+ it "should not save parent document when casted model is invalid" do
391
+ @toy.should_not be_valid
392
+ @toy.base_doc.save.should be_false
393
+ lambda{@toy.base_doc.save!}.should raise_error
394
+ end
395
+
396
+ it "should save parent document when nested casted model is valid" do
397
+ @toy.name = "Mr Squeaks"
398
+ @toy.should be_valid
399
+ @toy.base_doc.save.should be_true
400
+ lambda{@toy.base_doc.save!}.should_not raise_error
401
+ end
402
+ end
403
+
404
+ describe "callbacks" do
405
+ before(:each) do
406
+ @doc = CastedCallbackDoc.new
407
+ @model = WithCastedCallBackModel.new
408
+ @doc.callback_model = @model
409
+ end
410
+
411
+ describe "validate" do
412
+ it "should run before_validate before validating" do
413
+ @model.run_before_validate.should be_nil
414
+ @model.should be_valid
415
+ @model.run_before_validate.should be_true
416
+ end
417
+ it "should run after_validate after validating" do
418
+ @model.run_after_validate.should be_nil
419
+ @model.should be_valid
420
+ @model.run_after_validate.should be_true
421
+ end
422
+ end
423
+ end
424
+ end