couchrest_model 1.0.0.beta7

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