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,790 @@
1
+ # encoding: utf-8
2
+ require File.expand_path('../../spec_helper', __FILE__)
3
+ require File.join(FIXTURE_PATH, 'more', 'person')
4
+ require File.join(FIXTURE_PATH, 'more', 'card')
5
+ require File.join(FIXTURE_PATH, 'more', 'invoice')
6
+ require File.join(FIXTURE_PATH, 'more', 'service')
7
+ require File.join(FIXTURE_PATH, 'more', 'event')
8
+ require File.join(FIXTURE_PATH, 'more', 'cat')
9
+ require File.join(FIXTURE_PATH, 'more', 'user')
10
+ require File.join(FIXTURE_PATH, 'more', 'course')
11
+
12
+
13
+ describe "ExtendedDocument properties" do
14
+
15
+ before(:each) do
16
+ reset_test_db!
17
+ @card = Card.new(:first_name => "matt")
18
+ end
19
+
20
+ it "should be accessible from the object" do
21
+ @card.properties.should be_an_instance_of(Array)
22
+ @card.properties.map{|p| p.name}.should include("first_name")
23
+ end
24
+
25
+ it "should let you access a property value (getter)" do
26
+ @card.first_name.should == "matt"
27
+ end
28
+
29
+ it "should let you set a property value (setter)" do
30
+ @card.last_name = "Aimonetti"
31
+ @card.last_name.should == "Aimonetti"
32
+ end
33
+
34
+ it "should not let you set a property value if it's read only" do
35
+ lambda{@card.read_only_value = "test"}.should raise_error
36
+ end
37
+
38
+ it "should let you use an alias for an attribute" do
39
+ @card.last_name = "Aimonetti"
40
+ @card.family_name.should == "Aimonetti"
41
+ @card.family_name.should == @card.last_name
42
+ end
43
+
44
+ it "should let you use an alias for a casted attribute" do
45
+ @card.cast_alias = Person.new(:name => "Aimonetti")
46
+ @card.cast_alias.name.should == ["Aimonetti"]
47
+ @card.calias.name.should == ["Aimonetti"]
48
+ card = Card.new(:first_name => "matt", :cast_alias => {:name => "Aimonetti"})
49
+ card.cast_alias.name.should == ["Aimonetti"]
50
+ card.calias.name.should == ["Aimonetti"]
51
+ end
52
+
53
+ it "should be auto timestamped" do
54
+ @card.created_at.should be_nil
55
+ @card.updated_at.should be_nil
56
+ @card.save.should be_true
57
+ @card.created_at.should_not be_nil
58
+ @card.updated_at.should_not be_nil
59
+ end
60
+
61
+ it "should let you use read_attribute method" do
62
+ @card.last_name = "Aimonetti"
63
+ @card.read_attribute(:last_name).should eql('Aimonetti')
64
+ @card.read_attribute('last_name').should eql('Aimonetti')
65
+ last_name_prop = @card.properties.find{|p| p.name == 'last_name'}
66
+ @card.read_attribute(last_name_prop).should eql('Aimonetti')
67
+ end
68
+
69
+ it "should let you use write_attribute method" do
70
+ @card.write_attribute(:last_name, 'Aimonetti 1')
71
+ @card.last_name.should eql('Aimonetti 1')
72
+ @card.write_attribute('last_name', 'Aimonetti 2')
73
+ @card.last_name.should eql('Aimonetti 2')
74
+ last_name_prop = @card.properties.find{|p| p.name == 'last_name'}
75
+ @card.write_attribute(last_name_prop, 'Aimonetti 3')
76
+ @card.last_name.should eql('Aimonetti 3')
77
+ end
78
+
79
+ it "should let you use write_attribute on readonly properties" do
80
+ lambda {
81
+ @card.read_only_value = "foo"
82
+ }.should raise_error
83
+ @card.write_attribute(:read_only_value, "foo")
84
+ @card.read_only_value.should == 'foo'
85
+ end
86
+
87
+ it "should cast via write_attribute" do
88
+ @card.write_attribute(:cast_alias, {:name => ["Sam", "Lown"]})
89
+ @card.cast_alias.class.should eql(Person)
90
+ @card.cast_alias.name.last.should eql("Lown")
91
+ end
92
+
93
+ it "should not cast via write_attribute if property not casted" do
94
+ @card.write_attribute(:first_name, {:name => "Sam"})
95
+ @card.first_name.class.should eql(Hash)
96
+ @card.first_name[:name].should eql("Sam")
97
+ end
98
+
99
+
100
+ describe "mass assignment protection" do
101
+
102
+ it "should not store protected attribute using mass assignment" do
103
+ cat_toy = CatToy.new(:name => "Zorro")
104
+ cat = Cat.create(:name => "Helena", :toys => [cat_toy], :favorite_toy => cat_toy, :number => 1)
105
+ cat.number.should be_nil
106
+ cat.number = 1
107
+ cat.save
108
+ cat.number.should == 1
109
+ end
110
+
111
+ it "should not store protected attribute when 'declare accessible poperties, assume all the rest are protected'" do
112
+ user = User.create(:name => "Marcos Tapajós", :admin => true)
113
+ user.admin.should be_nil
114
+ end
115
+
116
+ it "should not store protected attribute when 'declare protected properties, assume all the rest are accessible'" do
117
+ user = SpecialUser.create(:name => "Marcos Tapajós", :admin => true)
118
+ user.admin.should be_nil
119
+ end
120
+
121
+ end
122
+
123
+ describe "validation" do
124
+ before(:each) do
125
+ @invoice = Invoice.new(:client_name => "matt", :employee_name => "Chris", :location => "San Diego, CA")
126
+ end
127
+
128
+ it "should be able to be validated" do
129
+ @card.valid?.should == true
130
+ end
131
+
132
+ it "should let you validate the presence of an attribute" do
133
+ @card.first_name = nil
134
+ @card.should_not be_valid
135
+ @card.errors.should_not be_empty
136
+ @card.errors.on(:first_name).should == ["First name must not be blank"]
137
+ end
138
+
139
+ it "should let you look up errors for a field by a string name" do
140
+ @card.first_name = nil
141
+ @card.should_not be_valid
142
+ @card.errors.on('first_name').should == ["First name must not be blank"]
143
+ end
144
+
145
+ it "should validate the presence of 2 attributes" do
146
+ @invoice.clear
147
+ @invoice.should_not be_valid
148
+ @invoice.errors.should_not be_empty
149
+ @invoice.errors.on(:client_name).first.should == "Client name must not be blank"
150
+ @invoice.errors.on(:employee_name).should_not be_empty
151
+ end
152
+
153
+ it "should let you set an error message" do
154
+ @invoice.location = nil
155
+ @invoice.valid?
156
+ @invoice.errors.on(:location).should == ["Hey stupid!, you forgot the location"]
157
+ end
158
+
159
+ it "should validate before saving" do
160
+ @invoice.location = nil
161
+ @invoice.should_not be_valid
162
+ @invoice.save.should be_false
163
+ @invoice.should be_new
164
+ end
165
+ end
166
+
167
+ describe "autovalidation" do
168
+ before(:each) do
169
+ @service = Service.new(:name => "Coumpound analysis", :price => 3_000)
170
+ end
171
+
172
+ it "should be valid" do
173
+ @service.should be_valid
174
+ end
175
+
176
+ it "should not respond to properties not setup" do
177
+ @service.respond_to?(:client_name).should be_false
178
+ end
179
+
180
+ describe "property :name, :length => 4...20" do
181
+
182
+ it "should autovalidate the presence when length is set" do
183
+ @service.name = nil
184
+ @service.should_not be_valid
185
+ @service.errors.should_not be_nil
186
+ @service.errors.on(:name).first.should == "Name must be between 4 and 19 characters long"
187
+ end
188
+
189
+ it "should autovalidate the correct length" do
190
+ @service.name = "a"
191
+ @service.should_not be_valid
192
+ @service.errors.should_not be_nil
193
+ @service.errors.on(:name).first.should == "Name must be between 4 and 19 characters long"
194
+ end
195
+ end
196
+ end
197
+
198
+ describe "casting" do
199
+ before(:each) do
200
+ @course = Course.new(:title => 'Relaxation')
201
+ end
202
+
203
+ describe "when value is nil" do
204
+ it "leaves the value unchanged" do
205
+ @course.title = nil
206
+ @course['title'].should == nil
207
+ end
208
+ end
209
+
210
+ describe "when type primitive is an Object" do
211
+ it "it should not cast given value" do
212
+ @course.participants = [{}, 'q', 1]
213
+ @course['participants'].should == [{}, 'q', 1]
214
+ end
215
+
216
+ it "should cast started_on to Date" do
217
+ @course.started_on = Date.today
218
+ @course['started_on'].should be_an_instance_of(Date)
219
+ end
220
+ end
221
+
222
+ describe "when type primitive is a String" do
223
+ it "keeps string value unchanged" do
224
+ value = "1.0"
225
+ @course.title = value
226
+ @course['title'].should equal(value)
227
+ end
228
+
229
+ it "it casts to string representation of the value" do
230
+ @course.title = 1.0
231
+ @course['title'].should eql("1.0")
232
+ end
233
+ end
234
+
235
+ describe 'when type primitive is a Float' do
236
+ it 'returns same value if a float' do
237
+ value = 24.0
238
+ @course.estimate = value
239
+ @course['estimate'].should equal(value)
240
+ end
241
+
242
+ it 'returns float representation of a zero string integer' do
243
+ @course.estimate = '0'
244
+ @course['estimate'].should eql(0.0)
245
+ end
246
+
247
+ it 'returns float representation of a positive string integer' do
248
+ @course.estimate = '24'
249
+ @course['estimate'].should eql(24.0)
250
+ end
251
+
252
+ it 'returns float representation of a negative string integer' do
253
+ @course.estimate = '-24'
254
+ @course['estimate'].should eql(-24.0)
255
+ end
256
+
257
+ it 'returns float representation of a zero string float' do
258
+ @course.estimate = '0.0'
259
+ @course['estimate'].should eql(0.0)
260
+ end
261
+
262
+ it 'returns float representation of a positive string float' do
263
+ @course.estimate = '24.35'
264
+ @course['estimate'].should eql(24.35)
265
+ end
266
+
267
+ it 'returns float representation of a negative string float' do
268
+ @course.estimate = '-24.35'
269
+ @course['estimate'].should eql(-24.35)
270
+ end
271
+
272
+ it 'returns float representation of a zero string float, with no leading digits' do
273
+ @course.estimate = '.0'
274
+ @course['estimate'].should eql(0.0)
275
+ end
276
+
277
+ it 'returns float representation of a positive string float, with no leading digits' do
278
+ @course.estimate = '.41'
279
+ @course['estimate'].should eql(0.41)
280
+ end
281
+
282
+ it 'returns float representation of a zero integer' do
283
+ @course.estimate = 0
284
+ @course['estimate'].should eql(0.0)
285
+ end
286
+
287
+ it 'returns float representation of a positive integer' do
288
+ @course.estimate = 24
289
+ @course['estimate'].should eql(24.0)
290
+ end
291
+
292
+ it 'returns float representation of a negative integer' do
293
+ @course.estimate = -24
294
+ @course['estimate'].should eql(-24.0)
295
+ end
296
+
297
+ it 'returns float representation of a zero decimal' do
298
+ @course.estimate = BigDecimal('0.0')
299
+ @course['estimate'].should eql(0.0)
300
+ end
301
+
302
+ it 'returns float representation of a positive decimal' do
303
+ @course.estimate = BigDecimal('24.35')
304
+ @course['estimate'].should eql(24.35)
305
+ end
306
+
307
+ it 'returns float representation of a negative decimal' do
308
+ @course.estimate = BigDecimal('-24.35')
309
+ @course['estimate'].should eql(-24.35)
310
+ end
311
+
312
+ [ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value|
313
+ it "does not typecast non-numeric value #{value.inspect}" do
314
+ @course.estimate = value
315
+ @course['estimate'].should equal(value)
316
+ end
317
+ end
318
+ end
319
+
320
+ describe 'when type primitive is a Integer' do
321
+ it 'returns same value if an integer' do
322
+ value = 24
323
+ @course.hours = value
324
+ @course['hours'].should equal(value)
325
+ end
326
+
327
+ it 'returns integer representation of a zero string integer' do
328
+ @course.hours = '0'
329
+ @course['hours'].should eql(0)
330
+ end
331
+
332
+ it 'returns integer representation of a positive string integer' do
333
+ @course.hours = '24'
334
+ @course['hours'].should eql(24)
335
+ end
336
+
337
+ it 'returns integer representation of a negative string integer' do
338
+ @course.hours = '-24'
339
+ @course['hours'].should eql(-24)
340
+ end
341
+
342
+ it 'returns integer representation of a zero string float' do
343
+ @course.hours = '0.0'
344
+ @course['hours'].should eql(0)
345
+ end
346
+
347
+ it 'returns integer representation of a positive string float' do
348
+ @course.hours = '24.35'
349
+ @course['hours'].should eql(24)
350
+ end
351
+
352
+ it 'returns integer representation of a negative string float' do
353
+ @course.hours = '-24.35'
354
+ @course['hours'].should eql(-24)
355
+ end
356
+
357
+ it 'returns integer representation of a zero string float, with no leading digits' do
358
+ @course.hours = '.0'
359
+ @course['hours'].should eql(0)
360
+ end
361
+
362
+ it 'returns integer representation of a positive string float, with no leading digits' do
363
+ @course.hours = '.41'
364
+ @course['hours'].should eql(0)
365
+ end
366
+
367
+ it 'returns integer representation of a zero float' do
368
+ @course.hours = 0.0
369
+ @course['hours'].should eql(0)
370
+ end
371
+
372
+ it 'returns integer representation of a positive float' do
373
+ @course.hours = 24.35
374
+ @course['hours'].should eql(24)
375
+ end
376
+
377
+ it 'returns integer representation of a negative float' do
378
+ @course.hours = -24.35
379
+ @course['hours'].should eql(-24)
380
+ end
381
+
382
+ it 'returns integer representation of a zero decimal' do
383
+ @course.hours = '0.0'
384
+ @course['hours'].should eql(0)
385
+ end
386
+
387
+ it 'returns integer representation of a positive decimal' do
388
+ @course.hours = '24.35'
389
+ @course['hours'].should eql(24)
390
+ end
391
+
392
+ it 'returns integer representation of a negative decimal' do
393
+ @course.hours = '-24.35'
394
+ @course['hours'].should eql(-24)
395
+ end
396
+
397
+ [ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value|
398
+ it "does not typecast non-numeric value #{value.inspect}" do
399
+ @course.hours = value
400
+ @course['hours'].should equal(value)
401
+ end
402
+ end
403
+ end
404
+
405
+ describe 'when type primitive is a BigDecimal' do
406
+ it 'returns same value if a decimal' do
407
+ value = BigDecimal('24.0')
408
+ @course.profit = value
409
+ @course['profit'].should equal(value)
410
+ end
411
+
412
+ it 'returns decimal representation of a zero string integer' do
413
+ @course.profit = '0'
414
+ @course['profit'].should eql(BigDecimal('0.0'))
415
+ end
416
+
417
+ it 'returns decimal representation of a positive string integer' do
418
+ @course.profit = '24'
419
+ @course['profit'].should eql(BigDecimal('24.0'))
420
+ end
421
+
422
+ it 'returns decimal representation of a negative string integer' do
423
+ @course.profit = '-24'
424
+ @course['profit'].should eql(BigDecimal('-24.0'))
425
+ end
426
+
427
+ it 'returns decimal representation of a zero string float' do
428
+ @course.profit = '0.0'
429
+ @course['profit'].should eql(BigDecimal('0.0'))
430
+ end
431
+
432
+ it 'returns decimal representation of a positive string float' do
433
+ @course.profit = '24.35'
434
+ @course['profit'].should eql(BigDecimal('24.35'))
435
+ end
436
+
437
+ it 'returns decimal representation of a negative string float' do
438
+ @course.profit = '-24.35'
439
+ @course['profit'].should eql(BigDecimal('-24.35'))
440
+ end
441
+
442
+ it 'returns decimal representation of a zero string float, with no leading digits' do
443
+ @course.profit = '.0'
444
+ @course['profit'].should eql(BigDecimal('0.0'))
445
+ end
446
+
447
+ it 'returns decimal representation of a positive string float, with no leading digits' do
448
+ @course.profit = '.41'
449
+ @course['profit'].should eql(BigDecimal('0.41'))
450
+ end
451
+
452
+ it 'returns decimal representation of a zero integer' do
453
+ @course.profit = 0
454
+ @course['profit'].should eql(BigDecimal('0.0'))
455
+ end
456
+
457
+ it 'returns decimal representation of a positive integer' do
458
+ @course.profit = 24
459
+ @course['profit'].should eql(BigDecimal('24.0'))
460
+ end
461
+
462
+ it 'returns decimal representation of a negative integer' do
463
+ @course.profit = -24
464
+ @course['profit'].should eql(BigDecimal('-24.0'))
465
+ end
466
+
467
+ it 'returns decimal representation of a zero float' do
468
+ @course.profit = 0.0
469
+ @course['profit'].should eql(BigDecimal('0.0'))
470
+ end
471
+
472
+ it 'returns decimal representation of a positive float' do
473
+ @course.profit = 24.35
474
+ @course['profit'].should eql(BigDecimal('24.35'))
475
+ end
476
+
477
+ it 'returns decimal representation of a negative float' do
478
+ @course.profit = -24.35
479
+ @course['profit'].should eql(BigDecimal('-24.35'))
480
+ end
481
+
482
+ [ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value|
483
+ it "does not typecast non-numeric value #{value.inspect}" do
484
+ @course.profit = value
485
+ @course['profit'].should equal(value)
486
+ end
487
+ end
488
+ end
489
+
490
+ describe 'when type primitive is a DateTime' do
491
+ describe 'and value given as a hash with keys like :year, :month, etc' do
492
+ it 'builds a DateTime instance from hash values' do
493
+ @course.updated_at = {
494
+ :year => '2006',
495
+ :month => '11',
496
+ :day => '23',
497
+ :hour => '12',
498
+ :min => '0',
499
+ :sec => '0'
500
+ }
501
+ result = @course['updated_at']
502
+
503
+ result.should be_kind_of(DateTime)
504
+ result.year.should eql(2006)
505
+ result.month.should eql(11)
506
+ result.day.should eql(23)
507
+ result.hour.should eql(12)
508
+ result.min.should eql(0)
509
+ result.sec.should eql(0)
510
+ end
511
+ end
512
+
513
+ describe 'and value is a string' do
514
+ it 'parses the string' do
515
+ @course.updated_at = 'Dec, 2006'
516
+ @course['updated_at'].month.should == 12
517
+ end
518
+ end
519
+
520
+ it 'does not typecast non-datetime values' do
521
+ @course.updated_at = 'not-datetime'
522
+ @course['updated_at'].should eql('not-datetime')
523
+ end
524
+ end
525
+
526
+ describe 'when type primitive is a Date' do
527
+ describe 'and value given as a hash with keys like :year, :month, etc' do
528
+ it 'builds a Date instance from hash values' do
529
+ @course.started_on = {
530
+ :year => '2007',
531
+ :month => '3',
532
+ :day => '25'
533
+ }
534
+ result = @course['started_on']
535
+
536
+ result.should be_kind_of(Date)
537
+ result.year.should eql(2007)
538
+ result.month.should eql(3)
539
+ result.day.should eql(25)
540
+ end
541
+ end
542
+
543
+ describe 'and value is a string' do
544
+ it 'parses the string' do
545
+ @course.started_on = 'Dec 20th, 2006'
546
+ @course.started_on.month.should == 12
547
+ @course.started_on.day.should == 20
548
+ @course.started_on.year.should == 2006
549
+ end
550
+ end
551
+
552
+ it 'does not typecast non-date values' do
553
+ @course.started_on = 'not-date'
554
+ @course['started_on'].should eql('not-date')
555
+ end
556
+ end
557
+
558
+ describe 'when type primitive is a Time' do
559
+ describe 'and value given as a hash with keys like :year, :month, etc' do
560
+ it 'builds a Time instance from hash values' do
561
+ @course.ends_at = {
562
+ :year => '2006',
563
+ :month => '11',
564
+ :day => '23',
565
+ :hour => '12',
566
+ :min => '0',
567
+ :sec => '0'
568
+ }
569
+ result = @course['ends_at']
570
+
571
+ result.should be_kind_of(Time)
572
+ result.year.should eql(2006)
573
+ result.month.should eql(11)
574
+ result.day.should eql(23)
575
+ result.hour.should eql(12)
576
+ result.min.should eql(0)
577
+ result.sec.should eql(0)
578
+ end
579
+ end
580
+
581
+ describe 'and value is a string' do
582
+ it 'parses the string' do
583
+ t = Time.now
584
+ @course.ends_at = t.strftime('%Y/%m/%d %H:%M:%S %z')
585
+ @course['ends_at'].year.should eql(t.year)
586
+ @course['ends_at'].month.should eql(t.month)
587
+ @course['ends_at'].day.should eql(t.day)
588
+ @course['ends_at'].hour.should eql(t.hour)
589
+ @course['ends_at'].min.should eql(t.min)
590
+ @course['ends_at'].sec.should eql(t.sec)
591
+ end
592
+ end
593
+
594
+ it 'does not typecast non-time values' do
595
+ @course.ends_at = 'not-time'
596
+ @course['ends_at'].should eql('not-time')
597
+ end
598
+ end
599
+
600
+ describe 'when type primitive is a Class' do
601
+ it 'returns same value if a class' do
602
+ value = Course
603
+ @course.klass = value
604
+ @course['klass'].should equal(value)
605
+ end
606
+
607
+ it 'returns the class if found' do
608
+ @course.klass = 'Course'
609
+ @course['klass'].should eql(Course)
610
+ end
611
+
612
+ it 'does not typecast non-class values' do
613
+ @course.klass = 'NoClass'
614
+ @course['klass'].should eql('NoClass')
615
+ end
616
+ end
617
+
618
+ describe 'when type primitive is a Boolean' do
619
+
620
+ [ true, 'true', 'TRUE', '1', 1, 't', 'T' ].each do |value|
621
+ it "returns true when value is #{value.inspect}" do
622
+ @course.active = value
623
+ @course['active'].should be_true
624
+ end
625
+ end
626
+
627
+ [ false, 'false', 'FALSE', '0', 0, 'f', 'F' ].each do |value|
628
+ it "returns false when value is #{value.inspect}" do
629
+ @course.active = value
630
+ @course['active'].should be_false
631
+ end
632
+ end
633
+
634
+ [ 'string', 2, 1.0, BigDecimal('1.0'), DateTime.now, Time.now, Date.today, Class, Object.new, ].each do |value|
635
+ it "does not typecast value #{value.inspect}" do
636
+ @course.active = value
637
+ @course['active'].should equal(value)
638
+ end
639
+ end
640
+
641
+ it "should respond to requests with ? modifier" do
642
+ @course.active = nil
643
+ @course.active?.should be_false
644
+ @course.active = false
645
+ @course.active?.should be_false
646
+ @course.active = true
647
+ @course.active?.should be_true
648
+ end
649
+
650
+ it "should respond to requests with ? modifier on TrueClass" do
651
+ @course.very_active = nil
652
+ @course.very_active?.should be_false
653
+ @course.very_active = false
654
+ @course.very_active?.should be_false
655
+ @course.very_active = true
656
+ @course.very_active?.should be_true
657
+ end
658
+ end
659
+
660
+ end
661
+ end
662
+
663
+ describe "a casted model retrieved from the database" do
664
+ before(:each) do
665
+ reset_test_db!
666
+ @cat = Cat.new(:name => 'Stimpy')
667
+ @cat.favorite_toy = CatToy.new(:name => 'Stinky')
668
+ @cat.toys << CatToy.new(:name => 'Feather')
669
+ @cat.toys << CatToy.new(:name => 'Mouse')
670
+ @cat.save
671
+ @cat = Cat.get(@cat.id)
672
+ end
673
+
674
+ describe "as a casted property" do
675
+ it "should already be casted_by its parent" do
676
+ @cat.favorite_toy.casted_by.should === @cat
677
+ end
678
+ end
679
+
680
+ describe "from a casted collection" do
681
+ it "should already be casted_by its parent" do
682
+ @cat.toys[0].casted_by.should === @cat
683
+ @cat.toys[1].casted_by.should === @cat
684
+ end
685
+ end
686
+ end
687
+
688
+ describe "Property Class" do
689
+
690
+ it "should provide name as string" do
691
+ property = CouchRest::Property.new(:test, String)
692
+ property.name.should eql('test')
693
+ property.to_s.should eql('test')
694
+ end
695
+
696
+ it "should provide class from type" do
697
+ property = CouchRest::Property.new(:test, String)
698
+ property.type_class.should eql(String)
699
+ end
700
+
701
+ it "should provide base class from type in array" do
702
+ property = CouchRest::Property.new(:test, [String])
703
+ property.type_class.should eql(String)
704
+ end
705
+
706
+ it "should leave type as string if requested" do
707
+ property = CouchRest::Property.new(:test, 'String')
708
+ property.type.should eql('String')
709
+ property.type_class.should eql(String)
710
+ end
711
+
712
+ it "should leave type nil and return string by default" do
713
+ property = CouchRest::Property.new(:test, nil)
714
+ property.type.should be_nil
715
+ # Type cast should never be used on non-casted property!
716
+ property.type_class.should eql(String)
717
+ end
718
+
719
+ it "should convert empty type array to [String]" do
720
+ property = CouchRest::Property.new(:test, [])
721
+ property.type_class.should eql(String)
722
+ end
723
+
724
+ it "should convert boolean text-type TrueClass" do
725
+ property = CouchRest::Property.new(:test, 'boolean')
726
+ property.type.should eql('boolean') # no change
727
+ property.type_class.should eql(TrueClass)
728
+ end
729
+
730
+ it "should set init method option or leave as 'new'" do
731
+ # (bad example! Time already typecast)
732
+ property = CouchRest::Property.new(:test, Time)
733
+ property.init_method.should eql('new')
734
+ property = CouchRest::Property.new(:test, Time, :init_method => 'parse')
735
+ property.init_method.should eql('parse')
736
+ end
737
+
738
+ ## Property Casting method. More thoroughly tested earlier.
739
+
740
+ describe "casting" do
741
+ it "should cast a value" do
742
+ property = CouchRest::Property.new(:test, Date)
743
+ parent = mock("FooObject")
744
+ property.cast(parent, "2010-06-16").should eql(Date.new(2010, 6, 16))
745
+ property.cast_value(parent, "2010-06-16").should eql(Date.new(2010, 6, 16))
746
+ end
747
+
748
+ it "should cast an array of values" do
749
+ property = CouchRest::Property.new(:test, [Date])
750
+ parent = mock("FooObject")
751
+ property.cast(parent, ["2010-06-01", "2010-06-02"]).should eql([Date.new(2010, 6, 1), Date.new(2010, 6, 2)])
752
+ end
753
+
754
+ it "should set a CastedArray on array of Objects" do
755
+ property = CouchRest::Property.new(:test, [Object])
756
+ parent = mock("FooObject")
757
+ property.cast(parent, ["2010-06-01", "2010-06-02"]).class.should eql(::CouchRest::CastedArray)
758
+ end
759
+
760
+ it "should not set a CastedArray on array of Strings" do
761
+ property = CouchRest::Property.new(:test, [String])
762
+ parent = mock("FooObject")
763
+ property.cast(parent, ["2010-06-01", "2010-06-02"]).class.should_not eql(::CouchRest::CastedArray)
764
+ end
765
+
766
+ it "should raise and error if value is array when type is not" do
767
+ property = CouchRest::Property.new(:test, Date)
768
+ parent = mock("FooClass")
769
+ lambda {
770
+ cast = property.cast(parent, [Date.new(2010, 6, 1)])
771
+ }.should raise_error
772
+ end
773
+
774
+
775
+ it "should set parent as casted_by object in CastedArray" do
776
+ property = CouchRest::Property.new(:test, [Object])
777
+ parent = mock("FooObject")
778
+ property.cast(parent, ["2010-06-01", "2010-06-02"]).casted_by.should eql(parent)
779
+ end
780
+
781
+ it "should set casted_by on new value" do
782
+ property = CouchRest::Property.new(:test, CatToy)
783
+ parent = mock("CatObject")
784
+ cast = property.cast(parent, {:name => 'catnip'})
785
+ cast.casted_by.should eql(parent)
786
+ end
787
+
788
+ end
789
+
790
+ end