openlogic-couchrest_model 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. data/.gitignore +11 -0
  2. data/.rspec +4 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +176 -0
  5. data/README.md +137 -0
  6. data/Rakefile +38 -0
  7. data/THANKS.md +21 -0
  8. data/VERSION +1 -0
  9. data/benchmarks/dirty.rb +118 -0
  10. data/couchrest_model.gemspec +36 -0
  11. data/history.md +309 -0
  12. data/init.rb +1 -0
  13. data/lib/couchrest/model.rb +10 -0
  14. data/lib/couchrest/model/associations.rb +231 -0
  15. data/lib/couchrest/model/base.rb +129 -0
  16. data/lib/couchrest/model/callbacks.rb +28 -0
  17. data/lib/couchrest/model/casted_array.rb +83 -0
  18. data/lib/couchrest/model/casted_by.rb +33 -0
  19. data/lib/couchrest/model/casted_hash.rb +84 -0
  20. data/lib/couchrest/model/class_proxy.rb +135 -0
  21. data/lib/couchrest/model/collection.rb +273 -0
  22. data/lib/couchrest/model/configuration.rb +67 -0
  23. data/lib/couchrest/model/connection.rb +70 -0
  24. data/lib/couchrest/model/core_extensions/hash.rb +9 -0
  25. data/lib/couchrest/model/core_extensions/time_parsing.rb +66 -0
  26. data/lib/couchrest/model/design_doc.rb +128 -0
  27. data/lib/couchrest/model/designs.rb +91 -0
  28. data/lib/couchrest/model/designs/view.rb +513 -0
  29. data/lib/couchrest/model/dirty.rb +39 -0
  30. data/lib/couchrest/model/document_queries.rb +99 -0
  31. data/lib/couchrest/model/embeddable.rb +78 -0
  32. data/lib/couchrest/model/errors.rb +25 -0
  33. data/lib/couchrest/model/extended_attachments.rb +83 -0
  34. data/lib/couchrest/model/persistence.rb +178 -0
  35. data/lib/couchrest/model/properties.rb +228 -0
  36. data/lib/couchrest/model/property.rb +114 -0
  37. data/lib/couchrest/model/property_protection.rb +71 -0
  38. data/lib/couchrest/model/proxyable.rb +183 -0
  39. data/lib/couchrest/model/support/couchrest_database.rb +13 -0
  40. data/lib/couchrest/model/support/couchrest_design.rb +33 -0
  41. data/lib/couchrest/model/typecast.rb +154 -0
  42. data/lib/couchrest/model/validations.rb +80 -0
  43. data/lib/couchrest/model/validations/casted_model.rb +16 -0
  44. data/lib/couchrest/model/validations/locale/en.yml +5 -0
  45. data/lib/couchrest/model/validations/uniqueness.rb +69 -0
  46. data/lib/couchrest/model/views.rb +151 -0
  47. data/lib/couchrest/railtie.rb +24 -0
  48. data/lib/couchrest_model.rb +66 -0
  49. data/lib/rails/generators/couchrest_model.rb +16 -0
  50. data/lib/rails/generators/couchrest_model/config/config_generator.rb +18 -0
  51. data/lib/rails/generators/couchrest_model/config/templates/couchdb.yml +21 -0
  52. data/lib/rails/generators/couchrest_model/model/model_generator.rb +27 -0
  53. data/lib/rails/generators/couchrest_model/model/templates/model.rb +2 -0
  54. data/spec/.gitignore +1 -0
  55. data/spec/fixtures/attachments/README +3 -0
  56. data/spec/fixtures/attachments/couchdb.png +0 -0
  57. data/spec/fixtures/attachments/test.html +11 -0
  58. data/spec/fixtures/config/couchdb.yml +10 -0
  59. data/spec/fixtures/models/article.rb +36 -0
  60. data/spec/fixtures/models/base.rb +164 -0
  61. data/spec/fixtures/models/card.rb +19 -0
  62. data/spec/fixtures/models/cat.rb +23 -0
  63. data/spec/fixtures/models/client.rb +6 -0
  64. data/spec/fixtures/models/course.rb +27 -0
  65. data/spec/fixtures/models/event.rb +8 -0
  66. data/spec/fixtures/models/invoice.rb +14 -0
  67. data/spec/fixtures/models/key_chain.rb +5 -0
  68. data/spec/fixtures/models/membership.rb +4 -0
  69. data/spec/fixtures/models/person.rb +11 -0
  70. data/spec/fixtures/models/project.rb +6 -0
  71. data/spec/fixtures/models/question.rb +7 -0
  72. data/spec/fixtures/models/sale_entry.rb +9 -0
  73. data/spec/fixtures/models/sale_invoice.rb +14 -0
  74. data/spec/fixtures/models/service.rb +10 -0
  75. data/spec/fixtures/models/user.rb +22 -0
  76. data/spec/fixtures/views/lib.js +3 -0
  77. data/spec/fixtures/views/test_view/lib.js +3 -0
  78. data/spec/fixtures/views/test_view/only-map.js +4 -0
  79. data/spec/fixtures/views/test_view/test-map.js +3 -0
  80. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  81. data/spec/functional/validations_spec.rb +8 -0
  82. data/spec/spec_helper.rb +60 -0
  83. data/spec/unit/active_model_lint_spec.rb +30 -0
  84. data/spec/unit/assocations_spec.rb +242 -0
  85. data/spec/unit/attachment_spec.rb +176 -0
  86. data/spec/unit/base_spec.rb +537 -0
  87. data/spec/unit/casted_spec.rb +72 -0
  88. data/spec/unit/class_proxy_spec.rb +167 -0
  89. data/spec/unit/collection_spec.rb +86 -0
  90. data/spec/unit/configuration_spec.rb +77 -0
  91. data/spec/unit/connection_spec.rb +148 -0
  92. data/spec/unit/core_extensions/time_parsing.rb +77 -0
  93. data/spec/unit/design_doc_spec.rb +241 -0
  94. data/spec/unit/designs/view_spec.rb +831 -0
  95. data/spec/unit/designs_spec.rb +134 -0
  96. data/spec/unit/dirty_spec.rb +436 -0
  97. data/spec/unit/embeddable_spec.rb +498 -0
  98. data/spec/unit/inherited_spec.rb +33 -0
  99. data/spec/unit/persistence_spec.rb +481 -0
  100. data/spec/unit/property_protection_spec.rb +192 -0
  101. data/spec/unit/property_spec.rb +481 -0
  102. data/spec/unit/proxyable_spec.rb +376 -0
  103. data/spec/unit/subclass_spec.rb +85 -0
  104. data/spec/unit/typecast_spec.rb +521 -0
  105. data/spec/unit/validations_spec.rb +140 -0
  106. data/spec/unit/view_spec.rb +367 -0
  107. metadata +301 -0
@@ -0,0 +1,498 @@
1
+ # encoding: utf-8
2
+ require "spec_helper"
3
+
4
+ class WithCastedModelMixin
5
+ include CouchRest::Model::Embeddable
6
+ property :name
7
+ property :no_value
8
+ property :details, Object, :default => {}
9
+ property :casted_attribute, WithCastedModelMixin
10
+ end
11
+
12
+ class OldFashionedMixin < Hash
13
+ include CouchRest::Model::CastedModel
14
+ property :name
15
+ end
16
+
17
+ class DummyModel < CouchRest::Model::Base
18
+ use_database TEST_SERVER.default_database
19
+ raise "Default DB not set" if TEST_SERVER.default_database.nil?
20
+ property :casted_attribute, WithCastedModelMixin
21
+ property :keywords, [String]
22
+ property :old_casted_attribute, OldFashionedMixin
23
+ property :sub_models do |child|
24
+ child.property :title
25
+ end
26
+ property :param_free_sub_models do
27
+ property :title
28
+ end
29
+ end
30
+
31
+ class WithCastedCallBackModel
32
+ include CouchRest::Model::Embeddable
33
+ property :name
34
+ property :run_before_validation
35
+ property :run_after_validation
36
+
37
+ validates_presence_of :run_before_validation
38
+
39
+ before_validation do |object|
40
+ object.run_before_validation = true
41
+ end
42
+ after_validation do |object|
43
+ object.run_after_validation = true
44
+ end
45
+ end
46
+
47
+ class CastedCallbackDoc < CouchRest::Model::Base
48
+ use_database TEST_SERVER.default_database
49
+ raise "Default DB not set" if TEST_SERVER.default_database.nil?
50
+ property :callback_model, WithCastedCallBackModel
51
+ end
52
+
53
+ describe CouchRest::Model::Embeddable do
54
+
55
+ describe "isolated" do
56
+ before(:each) do
57
+ @obj = WithCastedModelMixin.new
58
+ end
59
+ it "should automatically include the property mixin and define getters and setters" do
60
+ @obj.name = 'Matt'
61
+ @obj.name.should == 'Matt'
62
+ end
63
+
64
+ it "should allow override of default" do
65
+ @obj = WithCastedModelMixin.new(:name => 'Eric', :details => {'color' => 'orange'})
66
+ @obj.name.should == 'Eric'
67
+ @obj.details['color'].should == 'orange'
68
+ end
69
+ it "should always return base_doc? as false" do
70
+ @obj.base_doc?.should be_false
71
+ end
72
+ it "should call after_initialize callback if available" do
73
+ klass = Class.new do
74
+ include CouchRest::Model::CastedModel
75
+ after_initialize :set_name
76
+ property :name
77
+ def set_name; self.name = "foobar"; end
78
+ end
79
+ @obj = klass.new
80
+ @obj.name.should eql("foobar")
81
+ end
82
+ it "should allow override of initialize with super" do
83
+ klass = Class.new do
84
+ include CouchRest::Model::Embeddable
85
+ after_initialize :set_name
86
+ property :name
87
+ def set_name; self.name = "foobar"; end
88
+ def initialize(attrs = {}); super(); end
89
+ end
90
+ @obj = klass.new
91
+ @obj.name.should eql("foobar")
92
+ end
93
+ end
94
+
95
+ describe "casted as an attribute, but without a value" do
96
+ before(:each) do
97
+ @obj = DummyModel.new
98
+ @casted_obj = @obj.casted_attribute
99
+ end
100
+ it "should be nil" do
101
+ @casted_obj.should == nil
102
+ end
103
+ end
104
+
105
+ describe "anonymous sub casted models" do
106
+ before :each do
107
+ @obj = DummyModel.new
108
+ end
109
+ it "should be empty initially" do
110
+ @obj.sub_models.should_not be_nil
111
+ @obj.sub_models.should be_empty
112
+ end
113
+ it "should be updatable using a hash" do
114
+ @obj.sub_models << {:title => 'test'}
115
+ @obj.sub_models.first.title.should eql('test')
116
+ end
117
+ it "should be empty intitally (without params)" do
118
+ @obj.param_free_sub_models.should_not be_nil
119
+ @obj.param_free_sub_models.should be_empty
120
+ end
121
+ it "should be updatable using a hash (without params)" do
122
+ @obj.param_free_sub_models << {:title => 'test'}
123
+ @obj.param_free_sub_models.first.title.should eql('test')
124
+ end
125
+ end
126
+
127
+ describe "casted as attribute" do
128
+ before(:each) do
129
+ casted = {:name => 'not whatever'}
130
+ @obj = DummyModel.new(:casted_attribute => {:name => 'whatever', :casted_attribute => casted})
131
+ @casted_obj = @obj.casted_attribute
132
+ end
133
+
134
+ it "should be available from its parent" do
135
+ @casted_obj.should be_an_instance_of(WithCastedModelMixin)
136
+ end
137
+
138
+ it "should have the getters defined" do
139
+ @casted_obj.name.should == 'whatever'
140
+ end
141
+
142
+ it "should know who casted it" do
143
+ @casted_obj.casted_by.should == @obj
144
+ end
145
+
146
+ it "should know which property casted it" do
147
+ @casted_obj.casted_by_property.should == @obj.properties.detect{|p| p.to_s == 'casted_attribute'}
148
+ end
149
+
150
+ it "should return nil for the 'no_value' attribute" do
151
+ @casted_obj.no_value.should be_nil
152
+ end
153
+
154
+ it "should return nil for the unknown attribute" do
155
+ @casted_obj["unknown"].should be_nil
156
+ end
157
+
158
+ it "should return {} for the hash attribute" do
159
+ @casted_obj.details.should == {}
160
+ end
161
+
162
+ it "should cast its own attributes" do
163
+ @casted_obj.casted_attribute.should be_instance_of(WithCastedModelMixin)
164
+ end
165
+
166
+ it "should raise an error if save or update_attributes called" do
167
+ expect { @casted_obj.casted_attribute.save }.to raise_error(NoMethodError)
168
+ expect { @casted_obj.casted_attribute.update_attributes(:name => "Fubar") }.to raise_error(NoMethodError)
169
+ end
170
+ end
171
+
172
+ # Basic testing for an old fashioned casted hash
173
+ describe "old hash casted as attribute" do
174
+ before :each do
175
+ @obj = DummyModel.new(:old_casted_attribute => {:name => 'Testing'})
176
+ @casted_obj = @obj.old_casted_attribute
177
+ end
178
+ it "should be available from its parent" do
179
+ @casted_obj.should be_an_instance_of(OldFashionedMixin)
180
+ end
181
+
182
+ it "should have the getters defined" do
183
+ @casted_obj.name.should == 'Testing'
184
+ end
185
+
186
+ it "should know who casted it" do
187
+ @casted_obj.casted_by.should == @obj
188
+ end
189
+
190
+ it "should know which property casted it" do
191
+ @casted_obj.casted_by_property.should == @obj.properties.detect{|p| p.to_s == 'old_casted_attribute'}
192
+ end
193
+
194
+ it "should return nil for the unknown attribute" do
195
+ @casted_obj["unknown"].should be_nil
196
+ end
197
+ end
198
+
199
+ describe "casted as an array of a different type" do
200
+ before(:each) do
201
+ @obj = DummyModel.new(:keywords => ['couch', 'sofa', 'relax', 'canapé'])
202
+ end
203
+
204
+ it "should cast the array properly" do
205
+ @obj.keywords.should be_kind_of(Array)
206
+ @obj.keywords.first.should == 'couch'
207
+ end
208
+ end
209
+
210
+ describe "update attributes without saving" do
211
+ before(:each) do
212
+ @question = Question.new(:q => "What is your quest?", :a => "To seek the Holy Grail")
213
+ end
214
+ it "should work for attribute= methods" do
215
+ @question.q.should == "What is your quest?"
216
+ @question['a'].should == "To seek the Holy Grail"
217
+ @question.update_attributes_without_saving(:q => "What is your favorite color?", 'a' => "Blue")
218
+ @question['q'].should == "What is your favorite color?"
219
+ @question.a.should == "Blue"
220
+ end
221
+
222
+ it "should also work for attributes= alias" do
223
+ @question.respond_to?(:attributes=).should be_true
224
+ @question.attributes = {:q => "What is your favorite color?", 'a' => "Blue"}
225
+ @question['q'].should == "What is your favorite color?"
226
+ @question.a.should == "Blue"
227
+ end
228
+
229
+ it "should flip out if an attribute= method is missing" do
230
+ lambda {
231
+ @q.update_attributes_without_saving('foo' => "something", :a => "No green")
232
+ }.should raise_error(NoMethodError)
233
+ end
234
+
235
+ it "should not change any attributes if there is an error" do
236
+ lambda {
237
+ @q.update_attributes_without_saving('foo' => "something", :a => "No green")
238
+ }.should raise_error(NoMethodError)
239
+ @question.q.should == "What is your quest?"
240
+ @question.a.should == "To seek the Holy Grail"
241
+ end
242
+
243
+ end
244
+
245
+ describe "saved document with casted models" do
246
+ before(:each) do
247
+ reset_test_db!
248
+ @obj = DummyModel.new(:casted_attribute => {:name => 'whatever'})
249
+ @obj.save.should be_true
250
+ @obj = DummyModel.get(@obj.id)
251
+ end
252
+
253
+ it "should be able to load with the casted models" do
254
+ casted_obj = @obj.casted_attribute
255
+ casted_obj.should_not be_nil
256
+ casted_obj.should be_an_instance_of(WithCastedModelMixin)
257
+ end
258
+
259
+ it "should have defined getters for the casted model" do
260
+ casted_obj = @obj.casted_attribute
261
+ casted_obj.name.should == "whatever"
262
+ end
263
+
264
+ it "should have defined setters for the casted model" do
265
+ casted_obj = @obj.casted_attribute
266
+ casted_obj.name = "test"
267
+ casted_obj.name.should == "test"
268
+ end
269
+
270
+ it "should retain an override of a casted model attribute's default" do
271
+ casted_obj = @obj.casted_attribute
272
+ casted_obj.details['color'] = 'orange'
273
+ @obj.save
274
+ casted_obj = DummyModel.get(@obj.id).casted_attribute
275
+ casted_obj.details['color'].should == 'orange'
276
+ end
277
+
278
+ end
279
+
280
+ describe "saving document with array of casted models and validation" do
281
+ before :each do
282
+ @cat = Cat.new :name => "felix"
283
+ @cat.save
284
+ end
285
+
286
+ it "should save" do
287
+ toy = CatToy.new :name => "Mouse"
288
+ @cat.toys.push(toy)
289
+ @cat.save.should be_true
290
+ @cat = Cat.get @cat.id
291
+ @cat.toys.class.should == CouchRest::Model::CastedArray
292
+ @cat.toys.first.class.should == CatToy
293
+ @cat.toys.first.should === toy
294
+ end
295
+
296
+ it "should fail because name is not present" do
297
+ toy = CatToy.new
298
+ @cat.toys.push(toy)
299
+ @cat.should_not be_valid
300
+ @cat.save.should be_false
301
+ end
302
+
303
+ it "should not fail if the casted model doesn't have validation" do
304
+ Cat.property :masters, [Person], :default => []
305
+ Cat.validates_presence_of :name
306
+ cat = Cat.new(:name => 'kitty')
307
+ cat.should be_valid
308
+ cat.masters.push Person.new
309
+ cat.should be_valid
310
+ end
311
+ end
312
+
313
+ describe "calling valid?" do
314
+ before :each do
315
+ @cat = Cat.new
316
+ @toy1 = CatToy.new
317
+ @toy2 = CatToy.new
318
+ @toy3 = CatToy.new
319
+ @cat.favorite_toy = @toy1
320
+ @cat.toys << @toy2
321
+ @cat.toys << @toy3
322
+ end
323
+
324
+ describe "on the top document" do
325
+ it "should put errors on all invalid casted models" do
326
+ @cat.should_not be_valid
327
+ @cat.errors.should_not be_empty
328
+ @toy1.errors.should_not be_empty
329
+ @toy2.errors.should_not be_empty
330
+ @toy3.errors.should_not be_empty
331
+ end
332
+
333
+ it "should not put errors on valid casted models" do
334
+ @toy1.name = "Feather"
335
+ @toy2.name = "Twine"
336
+ @cat.should_not be_valid
337
+ @cat.errors.should_not be_empty
338
+ @toy1.errors.should be_empty
339
+ @toy2.errors.should be_empty
340
+ @toy3.errors.should_not be_empty
341
+ end
342
+
343
+ it "should not use dperecated ActiveModel options" do
344
+ ActiveSupport::Deprecation.should_not_receive(:warn)
345
+ @cat.should_not be_valid
346
+ end
347
+ end
348
+
349
+ describe "on a casted model property" do
350
+ it "should only validate itself" do
351
+ @toy1.should_not be_valid
352
+ @toy1.errors.should_not be_empty
353
+ @cat.errors.should be_empty
354
+ @toy2.errors.should be_empty
355
+ @toy3.errors.should be_empty
356
+ end
357
+ end
358
+
359
+ describe "on a casted model inside a casted collection" do
360
+ it "should only validate itself" do
361
+ @toy2.should_not be_valid
362
+ @toy2.errors.should_not be_empty
363
+ @cat.errors.should be_empty
364
+ @toy1.errors.should be_empty
365
+ @toy3.errors.should be_empty
366
+ end
367
+ end
368
+ end
369
+
370
+ describe "calling new? on a casted model" do
371
+ before :each do
372
+ reset_test_db!
373
+ @cat = Cat.new(:name => 'Sockington')
374
+ @favorite_toy = CatToy.new(:name => 'Catnip Ball')
375
+ @cat.favorite_toy = @favorite_toy
376
+ @cat.toys << CatToy.new(:name => 'Fuzzy Stick')
377
+ end
378
+
379
+ it "should be true on new" do
380
+ CatToy.new.should be_new
381
+ CatToy.new.new_record?.should be_true
382
+ end
383
+
384
+ it "should be true after assignment" do
385
+ @cat.should be_new
386
+ @cat.favorite_toy.should be_new
387
+ @cat.toys.first.should be_new
388
+ end
389
+
390
+ it "should not be true after create or save" do
391
+ @cat.create
392
+ @cat.save
393
+ @cat.favorite_toy.should_not be_new
394
+ @cat.toys.first.casted_by.should eql(@cat)
395
+ @cat.toys.first.should_not be_new
396
+ end
397
+
398
+ it "should not be true after get from the database" do
399
+ @cat.save
400
+ @cat = Cat.get(@cat.id)
401
+ @cat.favorite_toy.should_not be_new
402
+ @cat.toys.first.should_not be_new
403
+ end
404
+
405
+ it "should still be true after a failed create or save" do
406
+ @cat.name = nil
407
+ @cat.create.should be_false
408
+ @cat.save.should be_false
409
+ @cat.favorite_toy.should be_new
410
+ @cat.toys.first.should be_new
411
+ end
412
+ end
413
+
414
+ describe "calling base_doc from a nested casted model" do
415
+ before :each do
416
+ @course = Course.new(:title => 'Science 101')
417
+ @professor = Person.new(:name => ['Professor', 'Plum'])
418
+ @cat = Cat.new(:name => 'Scratchy')
419
+ @toy1 = CatToy.new
420
+ @toy2 = CatToy.new
421
+ @course.professor = @professor
422
+ @professor.pet = @cat
423
+ @cat.favorite_toy = @toy1
424
+ @cat.toys << @toy2
425
+ end
426
+
427
+ it 'should let you copy over casted arrays' do
428
+ question = Question.new
429
+ @course.questions << question
430
+ new_course = Course.new
431
+ new_course.questions = @course.questions
432
+ new_course.questions.should include(question)
433
+ end
434
+
435
+ it "should reference the top document for" do
436
+ @course.base_doc.should === @course
437
+ @professor.casted_by.should === @course
438
+ @professor.base_doc.should === @course
439
+ @cat.base_doc.should === @course
440
+ @toy1.base_doc.should === @course
441
+ @toy2.base_doc.should === @course
442
+ end
443
+
444
+ it "should call setter on top document" do
445
+ @toy1.base_doc.should_not be_nil
446
+ @toy1.base_doc.title = 'Tom Foolery'
447
+ @course.title.should == 'Tom Foolery'
448
+ end
449
+
450
+ it "should return nil if not yet casted" do
451
+ person = Person.new
452
+ person.base_doc.should == nil
453
+ end
454
+ end
455
+
456
+ describe "calling base_doc.save from a nested casted model" do
457
+ before :each do
458
+ reset_test_db!
459
+ @cat = Cat.new(:name => 'Snowball')
460
+ @toy = CatToy.new
461
+ @cat.favorite_toy = @toy
462
+ end
463
+
464
+ it "should not save parent document when casted model is invalid" do
465
+ @toy.should_not be_valid
466
+ @toy.base_doc.save.should be_false
467
+ lambda{@toy.base_doc.save!}.should raise_error
468
+ end
469
+
470
+ it "should save parent document when nested casted model is valid" do
471
+ @toy.name = "Mr Squeaks"
472
+ @toy.should be_valid
473
+ @toy.base_doc.save.should be_true
474
+ lambda{@toy.base_doc.save!}.should_not raise_error
475
+ end
476
+ end
477
+
478
+ describe "callbacks" do
479
+ before(:each) do
480
+ @doc = CastedCallbackDoc.new
481
+ @model = WithCastedCallBackModel.new
482
+ @doc.callback_model = @model
483
+ end
484
+
485
+ describe "validate" do
486
+ it "should run before_validation before validating" do
487
+ @model.run_before_validation.should be_nil
488
+ @model.should be_valid
489
+ @model.run_before_validation.should be_true
490
+ end
491
+ it "should run after_validation after validating" do
492
+ @model.run_after_validation.should be_nil
493
+ @model.should be_valid
494
+ @model.run_after_validation.should be_true
495
+ end
496
+ end
497
+ end
498
+ end