couchrest_model 1.0.0.beta7

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 (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