davber_couchrest_extended_document 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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