couchrest_model-radiant 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 (73) hide show
  1. data/LICENSE +176 -0
  2. data/README.md +19 -0
  3. data/Rakefile +74 -0
  4. data/THANKS.md +21 -0
  5. data/history.txt +207 -0
  6. data/lib/couchrest/model.rb +10 -0
  7. data/lib/couchrest/model/associations.rb +223 -0
  8. data/lib/couchrest/model/base.rb +111 -0
  9. data/lib/couchrest/model/callbacks.rb +27 -0
  10. data/lib/couchrest/model/casted_array.rb +39 -0
  11. data/lib/couchrest/model/casted_model.rb +68 -0
  12. data/lib/couchrest/model/class_proxy.rb +122 -0
  13. data/lib/couchrest/model/collection.rb +263 -0
  14. data/lib/couchrest/model/configuration.rb +51 -0
  15. data/lib/couchrest/model/design_doc.rb +123 -0
  16. data/lib/couchrest/model/document_queries.rb +83 -0
  17. data/lib/couchrest/model/errors.rb +23 -0
  18. data/lib/couchrest/model/extended_attachments.rb +77 -0
  19. data/lib/couchrest/model/persistence.rb +155 -0
  20. data/lib/couchrest/model/properties.rb +208 -0
  21. data/lib/couchrest/model/property.rb +97 -0
  22. data/lib/couchrest/model/property_protection.rb +71 -0
  23. data/lib/couchrest/model/support/couchrest.rb +19 -0
  24. data/lib/couchrest/model/support/hash.rb +9 -0
  25. data/lib/couchrest/model/typecast.rb +175 -0
  26. data/lib/couchrest/model/validations.rb +68 -0
  27. data/lib/couchrest/model/validations/casted_model.rb +14 -0
  28. data/lib/couchrest/model/validations/locale/en.yml +5 -0
  29. data/lib/couchrest/model/validations/uniqueness.rb +44 -0
  30. data/lib/couchrest/model/views.rb +160 -0
  31. data/lib/couchrest/railtie.rb +12 -0
  32. data/lib/couchrest_model.rb +62 -0
  33. data/lib/rails/generators/couchrest_model.rb +16 -0
  34. data/lib/rails/generators/couchrest_model/model/model_generator.rb +27 -0
  35. data/lib/rails/generators/couchrest_model/model/templates/model.rb +2 -0
  36. data/spec/couchrest/assocations_spec.rb +196 -0
  37. data/spec/couchrest/attachment_spec.rb +176 -0
  38. data/spec/couchrest/base_spec.rb +463 -0
  39. data/spec/couchrest/casted_model_spec.rb +438 -0
  40. data/spec/couchrest/casted_spec.rb +75 -0
  41. data/spec/couchrest/class_proxy_spec.rb +132 -0
  42. data/spec/couchrest/configuration_spec.rb +78 -0
  43. data/spec/couchrest/inherited_spec.rb +40 -0
  44. data/spec/couchrest/persistence_spec.rb +415 -0
  45. data/spec/couchrest/property_protection_spec.rb +192 -0
  46. data/spec/couchrest/property_spec.rb +871 -0
  47. data/spec/couchrest/subclass_spec.rb +99 -0
  48. data/spec/couchrest/validations.rb +85 -0
  49. data/spec/couchrest/view_spec.rb +463 -0
  50. data/spec/fixtures/attachments/README +3 -0
  51. data/spec/fixtures/attachments/couchdb.png +0 -0
  52. data/spec/fixtures/attachments/test.html +11 -0
  53. data/spec/fixtures/base.rb +139 -0
  54. data/spec/fixtures/more/article.rb +35 -0
  55. data/spec/fixtures/more/card.rb +17 -0
  56. data/spec/fixtures/more/cat.rb +19 -0
  57. data/spec/fixtures/more/client.rb +6 -0
  58. data/spec/fixtures/more/course.rb +25 -0
  59. data/spec/fixtures/more/event.rb +8 -0
  60. data/spec/fixtures/more/invoice.rb +14 -0
  61. data/spec/fixtures/more/person.rb +9 -0
  62. data/spec/fixtures/more/question.rb +7 -0
  63. data/spec/fixtures/more/sale_entry.rb +9 -0
  64. data/spec/fixtures/more/sale_invoice.rb +13 -0
  65. data/spec/fixtures/more/service.rb +10 -0
  66. data/spec/fixtures/more/user.rb +22 -0
  67. data/spec/fixtures/views/lib.js +3 -0
  68. data/spec/fixtures/views/test_view/lib.js +3 -0
  69. data/spec/fixtures/views/test_view/only-map.js +4 -0
  70. data/spec/fixtures/views/test_view/test-map.js +3 -0
  71. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  72. data/spec/spec_helper.rb +48 -0
  73. metadata +263 -0
@@ -0,0 +1,438 @@
1
+ # encoding: utf-8
2
+
3
+ require File.expand_path('../../spec_helper', __FILE__)
4
+ require File.join(FIXTURE_PATH, 'more', 'cat')
5
+ require File.join(FIXTURE_PATH, 'more', 'person')
6
+ require File.join(FIXTURE_PATH, 'more', 'card')
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::Model::CastedModel
13
+ property :name
14
+ property :no_value
15
+ property :details, Object, :default => {}
16
+ property :casted_attribute, WithCastedModelMixin
17
+ end
18
+
19
+ class DummyModel < CouchRest::Model::Base
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 WithCastedCallBackModel < Hash
30
+ include CouchRest::Model::CastedModel
31
+ property :name
32
+ property :run_before_validate
33
+ property :run_after_validate
34
+
35
+ before_validate do |object|
36
+ object.run_before_validate = true
37
+ end
38
+ after_validate do |object|
39
+ object.run_after_validate = true
40
+ end
41
+ end
42
+
43
+ class CastedCallbackDoc < CouchRest::Model::Base
44
+ use_database TEST_SERVER.default_database
45
+ raise "Default DB not set" if TEST_SERVER.default_database.nil?
46
+ property :callback_model, WithCastedCallBackModel
47
+ end
48
+
49
+ describe CouchRest::Model::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
+
138
+ it "should raise an error if save or update_attributes called" do
139
+ expect { @casted_obj.casted_attribute.save }.to raise_error(NoMethodError)
140
+ expect { @casted_obj.casted_attribute.update_attributes(:name => "Fubar") }.to raise_error(NoMethodError)
141
+ end
142
+ end
143
+
144
+ describe "casted as an array of a different type" do
145
+ before(:each) do
146
+ @obj = DummyModel.new(:keywords => ['couch', 'sofa', 'relax', 'canapé'])
147
+ end
148
+
149
+ it "should cast the array properly" do
150
+ @obj.keywords.should be_an_instance_of(Array)
151
+ @obj.keywords.first.should == 'couch'
152
+ end
153
+ end
154
+
155
+ describe "update attributes without saving" do
156
+ before(:each) do
157
+ @question = Question.new(:q => "What is your quest?", :a => "To seek the Holy Grail")
158
+ end
159
+ it "should work for attribute= methods" do
160
+ @question.q.should == "What is your quest?"
161
+ @question['a'].should == "To seek the Holy Grail"
162
+ @question.update_attributes_without_saving(:q => "What is your favorite color?", 'a' => "Blue")
163
+ @question['q'].should == "What is your favorite color?"
164
+ @question.a.should == "Blue"
165
+ end
166
+
167
+ it "should also work for attributes= alias" do
168
+ @question.respond_to?(:attributes=).should be_true
169
+ @question.attributes = {:q => "What is your favorite color?", 'a' => "Blue"}
170
+ @question['q'].should == "What is your favorite color?"
171
+ @question.a.should == "Blue"
172
+ end
173
+
174
+ it "should flip out if an attribute= method is missing" do
175
+ lambda {
176
+ @q.update_attributes_without_saving('foo' => "something", :a => "No green")
177
+ }.should raise_error(NoMethodError)
178
+ end
179
+
180
+ it "should not change any attributes if there is an error" do
181
+ lambda {
182
+ @q.update_attributes_without_saving('foo' => "something", :a => "No green")
183
+ }.should raise_error(NoMethodError)
184
+ @question.q.should == "What is your quest?"
185
+ @question.a.should == "To seek the Holy Grail"
186
+ end
187
+
188
+ end
189
+
190
+ describe "saved document with casted models" do
191
+ before(:each) do
192
+ reset_test_db!
193
+ @obj = DummyModel.new(:casted_attribute => {:name => 'whatever'})
194
+ @obj.save.should be_true
195
+ @obj = DummyModel.get(@obj.id)
196
+ end
197
+
198
+ it "should be able to load with the casted models" do
199
+ casted_obj = @obj.casted_attribute
200
+ casted_obj.should_not be_nil
201
+ casted_obj.should be_an_instance_of(WithCastedModelMixin)
202
+ end
203
+
204
+ it "should have defined getters for the casted model" do
205
+ casted_obj = @obj.casted_attribute
206
+ casted_obj.name.should == "whatever"
207
+ end
208
+
209
+ it "should have defined setters for the casted model" do
210
+ casted_obj = @obj.casted_attribute
211
+ casted_obj.name = "test"
212
+ casted_obj.name.should == "test"
213
+ end
214
+
215
+ it "should retain an override of a casted model attribute's default" do
216
+ casted_obj = @obj.casted_attribute
217
+ casted_obj.details['color'] = 'orange'
218
+ @obj.save
219
+ casted_obj = DummyModel.get(@obj.id).casted_attribute
220
+ casted_obj.details['color'].should == 'orange'
221
+ end
222
+
223
+ end
224
+
225
+ describe "saving document with array of casted models and validation" do
226
+ before :each do
227
+ @cat = Cat.new :name => "felix"
228
+ @cat.save
229
+ end
230
+
231
+ it "should save" do
232
+ toy = CatToy.new :name => "Mouse"
233
+ @cat.toys.push(toy)
234
+ @cat.save.should be_true
235
+ @cat = Cat.get @cat.id
236
+ @cat.toys.class.should == CouchRest::Model::CastedArray
237
+ @cat.toys.first.class.should == CatToy
238
+ @cat.toys.first.should === toy
239
+ end
240
+
241
+ it "should fail because name is not present" do
242
+ toy = CatToy.new
243
+ @cat.toys.push(toy)
244
+ @cat.should_not be_valid
245
+ @cat.save.should be_false
246
+ end
247
+
248
+ it "should not fail if the casted model doesn't have validation" do
249
+ Cat.property :masters, [Person], :default => []
250
+ Cat.validates_presence_of :name
251
+ cat = Cat.new(:name => 'kitty')
252
+ cat.should be_valid
253
+ cat.masters.push Person.new
254
+ cat.should be_valid
255
+ end
256
+ end
257
+
258
+ describe "calling valid?" do
259
+ before :each do
260
+ @cat = Cat.new
261
+ @toy1 = CatToy.new
262
+ @toy2 = CatToy.new
263
+ @toy3 = CatToy.new
264
+ @cat.favorite_toy = @toy1
265
+ @cat.toys << @toy2
266
+ @cat.toys << @toy3
267
+ end
268
+
269
+ describe "on the top document" do
270
+ it "should put errors on all invalid casted models" do
271
+ @cat.should_not be_valid
272
+ @cat.errors.should_not be_empty
273
+ @toy1.errors.should_not be_empty
274
+ @toy2.errors.should_not be_empty
275
+ @toy3.errors.should_not be_empty
276
+ end
277
+
278
+ it "should not put errors on valid casted models" do
279
+ @toy1.name = "Feather"
280
+ @toy2.name = "Twine"
281
+ @cat.should_not be_valid
282
+ @cat.errors.should_not be_empty
283
+ @toy1.errors.should be_empty
284
+ @toy2.errors.should be_empty
285
+ @toy3.errors.should_not be_empty
286
+ end
287
+ end
288
+
289
+ describe "on a casted model property" do
290
+ it "should only validate itself" do
291
+ @toy1.should_not be_valid
292
+ @toy1.errors.should_not be_empty
293
+ @cat.errors.should be_empty
294
+ @toy2.errors.should be_empty
295
+ @toy3.errors.should be_empty
296
+ end
297
+ end
298
+
299
+ describe "on a casted model inside a casted collection" do
300
+ it "should only validate itself" do
301
+ @toy2.should_not be_valid
302
+ @toy2.errors.should_not be_empty
303
+ @cat.errors.should be_empty
304
+ @toy1.errors.should be_empty
305
+ @toy3.errors.should be_empty
306
+ end
307
+ end
308
+ end
309
+
310
+ describe "calling new? on a casted model" do
311
+ before :each do
312
+ reset_test_db!
313
+ @cat = Cat.new(:name => 'Sockington')
314
+ @favorite_toy = CatToy.new(:name => 'Catnip Ball')
315
+ @cat.favorite_toy = @favorite_toy
316
+ @cat.toys << CatToy.new(:name => 'Fuzzy Stick')
317
+ end
318
+
319
+ it "should be true on new" do
320
+ CatToy.new.should be_new
321
+ CatToy.new.new_record?.should be_true
322
+ end
323
+
324
+ it "should be true after assignment" do
325
+ @cat.should be_new
326
+ @cat.favorite_toy.should be_new
327
+ @cat.toys.first.should be_new
328
+ end
329
+
330
+ it "should not be true after create or save" do
331
+ @cat.create
332
+ @cat.save
333
+ @cat.favorite_toy.should_not be_new
334
+ @cat.toys.first.casted_by.should eql(@cat)
335
+ @cat.toys.first.should_not be_new
336
+ end
337
+
338
+ it "should not be true after get from the database" do
339
+ @cat.save
340
+ @cat = Cat.get(@cat.id)
341
+ @cat.favorite_toy.should_not be_new
342
+ @cat.toys.first.should_not be_new
343
+ end
344
+
345
+ it "should still be true after a failed create or save" do
346
+ @cat.name = nil
347
+ @cat.create.should be_false
348
+ @cat.save.should be_false
349
+ @cat.favorite_toy.should be_new
350
+ @cat.toys.first.should be_new
351
+ end
352
+ end
353
+
354
+ describe "calling base_doc from a nested casted model" do
355
+ before :each do
356
+ @course = Course.new(:title => 'Science 101')
357
+ @professor = Person.new(:name => ['Professor', 'Plum'])
358
+ @cat = Cat.new(:name => 'Scratchy')
359
+ @toy1 = CatToy.new
360
+ @toy2 = CatToy.new
361
+ @course.professor = @professor
362
+ @professor.pet = @cat
363
+ @cat.favorite_toy = @toy1
364
+ @cat.toys << @toy2
365
+ end
366
+
367
+ it 'should let you copy over casted arrays' do
368
+ question = Question.new
369
+ @course.questions << question
370
+ new_course = Course.new
371
+ new_course.questions = @course.questions
372
+ new_course.questions.should include(question)
373
+ end
374
+
375
+ it "should reference the top document for" do
376
+ @course.base_doc.should === @course
377
+ @professor.casted_by.should === @course
378
+ @professor.base_doc.should === @course
379
+ @cat.base_doc.should === @course
380
+ @toy1.base_doc.should === @course
381
+ @toy2.base_doc.should === @course
382
+ end
383
+
384
+ it "should call setter on top document" do
385
+ @toy1.base_doc.should_not be_nil
386
+ @toy1.base_doc.title = 'Tom Foolery'
387
+ @course.title.should == 'Tom Foolery'
388
+ end
389
+
390
+ it "should return nil if not yet casted" do
391
+ person = Person.new
392
+ person.base_doc.should == nil
393
+ end
394
+ end
395
+
396
+ describe "calling base_doc.save from a nested casted model" do
397
+ before :each do
398
+ reset_test_db!
399
+ @cat = Cat.new(:name => 'Snowball')
400
+ @toy = CatToy.new
401
+ @cat.favorite_toy = @toy
402
+ end
403
+
404
+ it "should not save parent document when casted model is invalid" do
405
+ @toy.should_not be_valid
406
+ @toy.base_doc.save.should be_false
407
+ lambda{@toy.base_doc.save!}.should raise_error
408
+ end
409
+
410
+ it "should save parent document when nested casted model is valid" do
411
+ @toy.name = "Mr Squeaks"
412
+ @toy.should be_valid
413
+ @toy.base_doc.save.should be_true
414
+ lambda{@toy.base_doc.save!}.should_not raise_error
415
+ end
416
+ end
417
+
418
+ describe "callbacks" do
419
+ before(:each) do
420
+ @doc = CastedCallbackDoc.new
421
+ @model = WithCastedCallBackModel.new
422
+ @doc.callback_model = @model
423
+ end
424
+
425
+ describe "validate" do
426
+ it "should run before_validate before validating" do
427
+ @model.run_before_validate.should be_nil
428
+ @model.should be_valid
429
+ @model.run_before_validate.should be_true
430
+ end
431
+ it "should run after_validate after validating" do
432
+ @model.run_after_validate.should be_nil
433
+ @model.should be_valid
434
+ @model.run_after_validate.should be_true
435
+ end
436
+ end
437
+ end
438
+ end
@@ -0,0 +1,75 @@
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::Model::Base
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::Model::Base
15
+ use_database TEST_SERVER.default_database
16
+
17
+ property :name
18
+ property :driver, Driver
19
+ end
20
+
21
+ describe "casting an extended document" do
22
+
23
+ before(:each) do
24
+ @driver = Driver.new(:name => 'Matt')
25
+ @car = Car.new(:name => 'Renault 306', :driver => @driver)
26
+ end
27
+
28
+ it "should retain all properties of the casted attribute" do
29
+ @car.driver.should == @driver
30
+ end
31
+
32
+ it "should let the casted document know who casted it" do
33
+ @car.driver.casted_by.should == @car
34
+ end
35
+ end
36
+
37
+ describe "assigning a value to casted attribute after initializing an object" do
38
+
39
+ before(:each) do
40
+ @car = Car.new(:name => 'Renault 306')
41
+ @driver = Driver.new(:name => 'Matt')
42
+ end
43
+
44
+ it "should not create an empty casted object" do
45
+ @car.driver.should be_nil
46
+ end
47
+
48
+ it "should let you assign the value" do
49
+ @car.driver = @driver
50
+ @car.driver.name.should == 'Matt'
51
+ end
52
+
53
+ it "should cast attribute" do
54
+ @car.driver = JSON.parse(@driver.to_json)
55
+ @car.driver.should be_instance_of(Driver)
56
+ end
57
+
58
+ end
59
+
60
+ describe "casting a model from parsed JSON" do
61
+
62
+ before(:each) do
63
+ @driver = Driver.new(:name => 'Matt')
64
+ @car = Car.new(:name => 'Renault 306', :driver => @driver)
65
+ @new_car = Car.new(JSON.parse(@car.to_json))
66
+ end
67
+
68
+ it "should cast casted attribute" do
69
+ @new_car.driver.should be_instance_of(Driver)
70
+ end
71
+
72
+ it "should retain all properties of the casted attribute" do
73
+ @new_car.driver.should == @driver
74
+ end
75
+ end