couchrest_model 2.1.0.rc1 → 2.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -1
  3. data/.travis.yml +15 -4
  4. data/Gemfile.activesupport-4.x +4 -0
  5. data/Gemfile.activesupport-5.x +4 -0
  6. data/README.md +2 -0
  7. data/VERSION +1 -1
  8. data/couchrest_model.gemspec +3 -2
  9. data/history.md +14 -1
  10. data/lib/couchrest/model/associations.rb +3 -8
  11. data/lib/couchrest/model/base.rb +15 -7
  12. data/lib/couchrest/model/casted_array.rb +22 -34
  13. data/lib/couchrest/model/configuration.rb +2 -0
  14. data/lib/couchrest/model/design.rb +4 -3
  15. data/lib/couchrest/model/designs/view.rb +37 -32
  16. data/lib/couchrest/model/dirty.rb +93 -19
  17. data/lib/couchrest/model/embeddable.rb +2 -14
  18. data/lib/couchrest/model/extended_attachments.rb +2 -4
  19. data/lib/couchrest/model/persistence.rb +14 -17
  20. data/lib/couchrest/model/properties.rb +46 -54
  21. data/lib/couchrest/model/property.rb +0 -3
  22. data/lib/couchrest/model/proxyable.rb +20 -4
  23. data/lib/couchrest/model/validations/uniqueness.rb +4 -1
  24. data/lib/couchrest_model.rb +2 -2
  25. data/spec/fixtures/models/article.rb +1 -1
  26. data/spec/fixtures/models/card.rb +2 -1
  27. data/spec/fixtures/models/person.rb +1 -0
  28. data/spec/fixtures/models/project.rb +3 -0
  29. data/spec/unit/assocations_spec.rb +73 -73
  30. data/spec/unit/attachment_spec.rb +34 -34
  31. data/spec/unit/base_spec.rb +102 -102
  32. data/spec/unit/casted_array_spec.rb +7 -7
  33. data/spec/unit/casted_spec.rb +7 -7
  34. data/spec/unit/configuration_spec.rb +11 -11
  35. data/spec/unit/connection_spec.rb +30 -30
  36. data/spec/unit/core_extensions/{time_parsing.rb → time_parsing_spec.rb} +21 -21
  37. data/spec/unit/design_spec.rb +38 -38
  38. data/spec/unit/designs/design_mapper_spec.rb +26 -26
  39. data/spec/unit/designs/migrations_spec.rb +13 -13
  40. data/spec/unit/designs/view_spec.rb +319 -274
  41. data/spec/unit/designs_spec.rb +39 -39
  42. data/spec/unit/dirty_spec.rb +188 -103
  43. data/spec/unit/embeddable_spec.rb +119 -117
  44. data/spec/unit/inherited_spec.rb +4 -4
  45. data/spec/unit/persistence_spec.rb +122 -122
  46. data/spec/unit/properties_spec.rb +466 -16
  47. data/spec/unit/property_protection_spec.rb +32 -32
  48. data/spec/unit/property_spec.rb +45 -436
  49. data/spec/unit/proxyable_spec.rb +140 -82
  50. data/spec/unit/subclass_spec.rb +14 -14
  51. data/spec/unit/translations_spec.rb +5 -5
  52. data/spec/unit/typecast_spec.rb +131 -131
  53. data/spec/unit/utils/migrate_spec.rb +2 -2
  54. data/spec/unit/validations_spec.rb +31 -31
  55. metadata +27 -12
  56. data/lib/couchrest/model/casted_hash.rb +0 -84
@@ -3,20 +3,470 @@ require "spec_helper"
3
3
 
4
4
  describe CouchRest::Model::Properties do
5
5
 
6
- before(:each) do
7
- @obj = WithDefaultValues.new
6
+ context "general functionality" do
7
+
8
+ before(:each) do
9
+ reset_test_db!
10
+ @card = Card.new(:first_name => "matt")
11
+ end
12
+
13
+ it "should be accessible from the object" do
14
+ expect(@card.properties).to be_an_instance_of(Array)
15
+ expect(@card.properties.map{|p| p.name}).to include("first_name")
16
+ end
17
+
18
+ it "should list object properties with values" do
19
+ expect(@card.attributes).to be_an_instance_of(Hash)
20
+ expect(@card.read_attributes).to be_an_instance_of(Hash)
21
+ expect(@card.attributes["first_name"]).to eql("matt")
22
+ end
23
+
24
+ it "should let you access a property value (getter)" do
25
+ expect(@card.first_name).to eql("matt")
26
+ end
27
+
28
+ it "should let you set a property value (setter)" do
29
+ @card.last_name = "Aimonetti"
30
+ expect(@card.last_name).to eq("Aimonetti")
31
+ end
32
+
33
+ it "should not let you set a property value if it's read only" do
34
+ expect{@card.read_only_value = "test"}.to raise_error(NoMethodError)
35
+ end
36
+
37
+ it "should let you use an alias for an attribute" do
38
+ @card.last_name = "Aimonetti"
39
+ expect(@card.family_name).to eq("Aimonetti")
40
+ expect(@card.family_name).to eq(@card.last_name)
41
+ end
42
+
43
+ it "should let you use an alias for a casted attribute" do
44
+ @card.cast_alias = Person.new(:name => ["Aimonetti"])
45
+ expect(@card.cast_alias.name).to eq(["Aimonetti"])
46
+ expect(@card.calias.name).to eq(["Aimonetti"])
47
+ card = Card.new(:first_name => "matt", :cast_alias => {:name => ["Aimonetti"]})
48
+ expect(card.cast_alias.name).to eq(["Aimonetti"])
49
+ expect(card.calias.name).to eq(["Aimonetti"])
50
+ end
51
+
52
+ it "should raise error if property name coincides with model type key" do
53
+ expect { Cat.property(Cat.model_type_key) }.to raise_error(/already used/)
54
+ end
55
+
56
+ it "should not raise error if property name coincides with model type key on non-model" do
57
+ expect { Person.property(Article.model_type_key) }.not_to raise_error
58
+ end
59
+
60
+ it "should be auto timestamped" do
61
+ expect(@card.created_at).to be_nil
62
+ expect(@card.updated_at).to be_nil
63
+ expect(@card.save).to be_truthy
64
+ expect(@card.created_at).not_to be_nil
65
+ expect(@card.updated_at).not_to be_nil
66
+ end
67
+
68
+ describe "#as_couch_json" do
69
+
70
+ it "should provide a simple hash from model" do
71
+ expect(@card.as_couch_json.class).to eql(Hash)
72
+ end
73
+
74
+ it "should remove properties from Hash if value is nil" do
75
+ @card.last_name = nil
76
+ expect(@card.as_couch_json.keys.include?('last_name')).to be_falsey
77
+ end
78
+
79
+ end
80
+
81
+ describe "#as_json" do
82
+
83
+ it "should provide a simple hash from model" do
84
+ expect(@card.as_json.class).to eql(Hash)
85
+ end
86
+
87
+ it "should pass options to Active Support's as_json" do
88
+ @card.last_name = "Aimonetti"
89
+ expect(@card.as_json(:only => 'last_name')).to eql('last_name' => 'Aimonetti')
90
+ end
91
+
92
+ end
93
+
94
+ describe '#read_attribute' do
95
+ it "should let you use read_attribute method" do
96
+ @card.last_name = "Aimonetti"
97
+ expect(@card.read_attribute(:last_name)).to eql('Aimonetti')
98
+ expect(@card.read_attribute('last_name')).to eql('Aimonetti')
99
+ last_name_prop = @card.properties.find{|p| p.name == 'last_name'}
100
+ expect(@card.read_attribute(last_name_prop)).to eql('Aimonetti')
101
+ end
102
+
103
+ it 'should raise an error if the property does not exist' do
104
+ expect { @card.read_attribute(:this_property_should_not_exist) }.to raise_error(ArgumentError)
105
+ end
106
+ end
107
+
108
+ describe '#write_attribute' do
109
+ it "should let you use write_attribute method" do
110
+ @card.write_attribute(:last_name, 'Aimonetti 1')
111
+ expect(@card.last_name).to eql('Aimonetti 1')
112
+ @card.write_attribute('last_name', 'Aimonetti 2')
113
+ expect(@card.last_name).to eql('Aimonetti 2')
114
+ last_name_prop = @card.properties.find{|p| p.name == 'last_name'}
115
+ @card.write_attribute(last_name_prop, 'Aimonetti 3')
116
+ expect(@card.last_name).to eql('Aimonetti 3')
117
+ end
118
+
119
+ it 'should raise an error if the property does not exist' do
120
+ expect { @card.write_attribute(:this_property_should_not_exist, 823) }.to raise_error(ArgumentError)
121
+ end
122
+
123
+ it "should let you use write_attribute on readonly properties" do
124
+ expect {
125
+ @card.read_only_value = "foo"
126
+ }.to raise_error(NoMethodError)
127
+ @card.write_attribute(:read_only_value, "foo")
128
+ expect(@card.read_only_value).to eq('foo')
129
+ end
130
+
131
+ it "should cast via write_attribute" do
132
+ @card.write_attribute(:cast_alias, {:name => ["Sam", "Lown"]})
133
+ expect(@card.cast_alias.class).to eql(Person)
134
+ expect(@card.cast_alias.name.last).to eql("Lown")
135
+ end
136
+
137
+ it "should not cast via write_attribute if property not casted" do
138
+ @card.write_attribute(:first_name, {:name => "Sam"})
139
+ expect(@card.first_name.class).to eql(Hash)
140
+ expect(@card.first_name[:name]).to eql("Sam")
141
+ end
142
+ end
143
+
144
+ # These tests are light as functionality is covered elsewhere
145
+ describe "#write_attributes" do
146
+
147
+ let :obj do
148
+ Card.new(:first_name => "matt")
149
+ end
150
+
151
+ it "should update attributes" do
152
+ obj.write_attributes( :last_name => 'foo' )
153
+ expect(obj.last_name).to eql('foo')
154
+ end
155
+
156
+ it "should not update protected attributes" do
157
+ obj.write_attributes(:bg_color => '#000000')
158
+ expect(obj.bg_color).to_not eql('#000000')
159
+ end
160
+
161
+ it "should not update read_only attributes" do
162
+ obj.write_attributes(:read_only_value => 'bar')
163
+ expect(obj.read_only_value).to_not eql('bar')
164
+ end
165
+
166
+ it "should have an #attributes= alias" do
167
+ expect {
168
+ obj.attributes = { :last_name => 'foo' }
169
+ }.to_not raise_error
170
+ end
171
+
172
+ end
173
+
174
+ # These tests are light as functionality is covered elsewhere
175
+ describe "#write_all_attributes" do
176
+
177
+ let :obj do
178
+ Card.new(:first_name => "matt")
179
+ end
180
+
181
+ it "should set regular properties" do
182
+ obj.write_all_attributes(:last_name => 'foo')
183
+ expect(obj.last_name).to eql('foo')
184
+ end
185
+
186
+ it "should set read-only and protected properties" do
187
+ obj.write_all_attributes(
188
+ :read_only_value => 'foo',
189
+ :bg_color => '#111111'
190
+ )
191
+ expect(obj.read_only_value).to eql('foo')
192
+ expect(obj.bg_color).to eql('#111111')
193
+ end
194
+
195
+ end
196
+
197
+
198
+ describe "mass updating attributes without property" do
199
+
200
+ describe "when mass_assign_any_attribute false" do
201
+
202
+ it "should not allow them to be set" do
203
+ @card.attributes = {:test => 'fooobar'}
204
+ expect(@card['test']).to be_nil
205
+ end
206
+
207
+ it 'should not allow them to be updated with update_attributes' do
208
+ @card.update_attributes(:test => 'fooobar')
209
+ expect(@card['test']).to be_nil
210
+ end
211
+
212
+ it 'should not have a different revision after update_attributes' do
213
+ @card.save
214
+ rev = @card.rev
215
+ @card.update_attributes(:test => 'fooobar')
216
+ expect(@card.rev).to eql(rev)
217
+ end
218
+
219
+ it 'should not have a different revision after save' do
220
+ @card.save
221
+ rev = @card.rev
222
+ @card.attributes = {:test => 'fooobar'}
223
+ @card.save
224
+ expect(@card.rev).to eql(rev)
225
+ end
226
+
227
+ end
228
+
229
+ describe "when mass_assign_any_attribute true" do
230
+ before(:each) do
231
+ # dup Card class so that no other tests are effected
232
+ card_class = Card.dup
233
+ card_class.class_eval do
234
+ mass_assign_any_attribute true
235
+ end
236
+ @card = card_class.new(:first_name => 'Sam')
237
+ end
238
+
239
+ it 'should allow them to be updated' do
240
+ @card.attributes = {:testing => 'fooobar'}
241
+ expect(@card['testing']).to eql('fooobar')
242
+ end
243
+
244
+ it 'should allow them to be updated with update_attributes' do
245
+ @card.update_attributes(:testing => 'fooobar')
246
+ expect(@card['testing']).to eql('fooobar')
247
+ end
248
+
249
+ it 'should have a different revision after update_attributes' do
250
+ @card.save
251
+ rev = @card.rev
252
+ @card.update_attributes(:testing => 'fooobar')
253
+ expect(@card.rev).not_to eql(rev)
254
+ end
255
+
256
+ it 'should have a different revision after save' do
257
+ @card.save
258
+ rev = @card.rev
259
+ @card.attributes = {:testing => 'fooobar'}
260
+ @card.save
261
+ expect(@card.rev).not_to eql(rev)
262
+ end
263
+
264
+ end
265
+ end
266
+
267
+ describe "mass assignment protection" do
268
+
269
+ it "should not store protected attribute using mass assignment" do
270
+ cat_toy = CatToy.new(:name => "Zorro")
271
+ cat = Cat.create(:name => "Helena", :toys => [cat_toy], :favorite_toy => cat_toy, :number => 1)
272
+ expect(cat.number).to be_nil
273
+ cat.number = 1
274
+ cat.save
275
+ expect(cat.number).to eq(1)
276
+ end
277
+
278
+ it "should not store protected attribute when 'declare accessible poperties, assume all the rest are protected'" do
279
+ user = User.create(:name => "Marcos Tapajós", :admin => true)
280
+ expect(user.admin).to be_nil
281
+ end
282
+
283
+ it "should not store protected attribute when 'declare protected properties, assume all the rest are accessible'" do
284
+ user = SpecialUser.create(:name => "Marcos Tapajós", :admin => true)
285
+ expect(user.admin).to be_nil
286
+ end
287
+
288
+ end
289
+
290
+ describe "validation" do
291
+ before(:each) do
292
+ @invoice = Invoice.new(:client_name => "matt", :employee_name => "Chris", :location => "San Diego, CA")
293
+ end
294
+
295
+ it "should be able to be validated" do
296
+ expect(@card.valid?).to eq(true)
297
+ end
298
+
299
+ it "should let you validate the presence of an attribute" do
300
+ @card.first_name = nil
301
+ expect(@card).not_to be_valid
302
+ expect(@card.errors).not_to be_empty
303
+ expect(@card.errors[:first_name]).to eq(["can't be blank"])
304
+ end
305
+
306
+ it "should let you look up errors for a field by a string name" do
307
+ @card.first_name = nil
308
+ expect(@card).not_to be_valid
309
+ expect(@card.errors['first_name']).to eq(["can't be blank"])
310
+ end
311
+
312
+ it "should validate the presence of 2 attributes" do
313
+ @invoice.clear
314
+ expect(@invoice).not_to be_valid
315
+ expect(@invoice.errors).not_to be_empty
316
+ expect(@invoice.errors[:client_name]).to eq(["can't be blank"])
317
+ expect(@invoice.errors[:employee_name]).not_to be_empty
318
+ end
319
+
320
+ it "should let you set an error message" do
321
+ @invoice.location = nil
322
+ @invoice.valid?
323
+ expect(@invoice.errors[:location]).to eq(["Hey stupid!, you forgot the location"])
324
+ end
325
+
326
+ it "should validate before saving" do
327
+ @invoice.location = nil
328
+ expect(@invoice).not_to be_valid
329
+ expect(@invoice.save).to be_falsey
330
+ expect(@invoice).to be_new
331
+ end
332
+ end
333
+
8
334
  end
335
+
336
+ context "casting" do
337
+
338
+ describe "properties of hash of casted models" do
339
+ it "should be able to assign a casted hash to a hash property" do
340
+ chain = KeyChain.new
341
+ keys = {"House" => "8==$", "Office" => "<>==U"}
342
+ chain.keys = keys
343
+ chain.keys = chain.keys
344
+ expect(chain.keys).to eq(keys)
345
+ end
346
+ end
347
+
348
+ describe "properties of array of casted models" do
349
+
350
+ before(:each) do
351
+ @course = Course.new :title => 'Test Course'
352
+ end
353
+
354
+ it "should allow attribute to be set from an array of objects" do
355
+ @course.questions = [Question.new(:q => "works?"), Question.new(:q => "Meaning of Life?")]
356
+ expect(@course.questions.length).to eql(2)
357
+ end
358
+
359
+ it "should allow attribute to be set from an array of hashes" do
360
+ @course.questions = [{:q => "works?"}, {:q => "Meaning of Life?"}]
361
+ expect(@course.questions.length).to eql(2)
362
+ expect(@course.questions.last.q).to eql("Meaning of Life?")
363
+ expect(@course.questions.last.class).to eql(Question) # typecasting
364
+ end
365
+
366
+ it "should allow attribute to be set from hash with ordered keys and objects" do
367
+ @course.questions = { '0' => Question.new(:q => "Test1"), '1' => Question.new(:q => 'Test2') }
368
+ expect(@course.questions.length).to eql(2)
369
+ expect(@course.questions.last.q).to eql('Test2')
370
+ expect(@course.questions.last.class).to eql(Question)
371
+ end
372
+
373
+ it "should allow attribute to be set from hash with ordered keys and sub-hashes" do
374
+ @course.questions = { '10' => {:q => 'Test10'}, '0' => {:q => "Test1"}, '1' => {:q => 'Test2'} }
375
+ expect(@course.questions.length).to eql(3)
376
+ expect(@course.questions.last.q).to eql('Test10')
377
+ expect(@course.questions.last.class).to eql(Question)
378
+ end
379
+
380
+ it "should allow attribute to be set from hash with ordered keys and HashWithIndifferentAccess" do
381
+ # This is similar to what you'd find in an HTML POST parameters
382
+ hash = HashWithIndifferentAccess.new({ '0' => {:q => "Test1"}, '1' => {:q => 'Test2'} })
383
+ @course.questions = hash
384
+ expect(@course.questions.length).to eql(2)
385
+ expect(@course.questions.last.q).to eql('Test2')
386
+ expect(@course.questions.last.class).to eql(Question)
387
+ end
388
+
389
+ it "should allow attribute to be set from Hash subclass with ordered keys" do
390
+ ourhash = Class.new(HashWithIndifferentAccess)
391
+ hash = ourhash.new({ '0' => {:q => "Test1"}, '1' => {:q => 'Test2'} })
392
+ @course.questions = hash
393
+ expect(@course.questions.length).to eql(2)
394
+ expect(@course.questions.last.q).to eql('Test2')
395
+ expect(@course.questions.last.class).to eql(Question)
396
+ end
397
+
398
+ it "should raise an error if attempting to set single value for array type" do
399
+ expect {
400
+ @course.questions = Question.new(:q => 'test1')
401
+ }.to raise_error(/Expecting an array/)
402
+ end
403
+
404
+
405
+ end
406
+
407
+ describe "a casted model retrieved from the database" do
408
+ before(:each) do
409
+ reset_test_db!
410
+ @cat = Cat.new(:name => 'Stimpy')
411
+ @cat.favorite_toy = CatToy.new(:name => 'Stinky')
412
+ @cat.toys << CatToy.new(:name => 'Feather')
413
+ @cat.toys << CatToy.new(:name => 'Mouse')
414
+ @cat.save
415
+ @cat = Cat.get(@cat.id)
416
+ end
417
+
418
+ describe "as a casted property" do
419
+ it "should already be casted_by its parent" do
420
+ expect(@cat.favorite_toy.casted_by).to be === @cat
421
+ end
422
+ end
423
+
424
+ describe "from a casted collection" do
425
+ it "should already be casted_by its parent" do
426
+ expect(@cat.toys[0].casted_by).to be === @cat
427
+ expect(@cat.toys[1].casted_by).to be === @cat
428
+ end
429
+ end
430
+ end
431
+
432
+ describe "nested models (not casted)" do
433
+ before(:each) do
434
+ reset_test_db!
435
+ @cat = ChildCat.new(:name => 'Stimpy')
436
+ @cat.mother = {:name => 'Stinky'}
437
+ @cat.siblings = [{:name => 'Feather'}, {:name => 'Felix'}]
438
+ @cat.save
439
+ @cat = ChildCat.get(@cat.id)
440
+ end
441
+
442
+ it "should correctly save single relation" do
443
+ expect(@cat.mother.name).to eql('Stinky')
444
+ expect(@cat.mother.casted_by).to eql(@cat)
445
+ end
446
+
447
+ it "should correctly save collection" do
448
+ expect(@cat.siblings.first.name).to eql("Feather")
449
+ expect(@cat.siblings.last.casted_by).to eql(@cat)
450
+ end
451
+ end
452
+
453
+ end
454
+
455
+ context "multipart attributes" do
456
+
457
+ before(:each) do
458
+ @obj = WithDefaultValues.new
459
+ end
9
460
 
10
- describe "multipart attributes" do
11
461
  context "with valid params" do
12
462
  it "should parse a legal date" do
13
463
  valid_date_params = { "exec_date(1i)"=>"2011",
14
464
  "exec_date(2i)"=>"10",
15
465
  "exec_date(3i)"=>"18"}
16
466
  @obj = WithDateAndTime.new valid_date_params
17
- @obj.exec_date.should_not be_nil
18
- @obj.exec_date.should be_kind_of(Date)
19
- @obj.exec_date.should == Date.new(2011, 10 ,18)
467
+ expect(@obj.exec_date).not_to be_nil
468
+ expect(@obj.exec_date).to be_kind_of(Date)
469
+ expect(@obj.exec_date).to eq(Date.new(2011, 10 ,18))
20
470
  end
21
471
 
22
472
  it "should parse a legal time" do
@@ -27,9 +477,9 @@ describe CouchRest::Model::Properties do
27
477
  "exec_time(5i)"=>"15",
28
478
  "exec_time(6i)"=>"15",}
29
479
  @obj = WithDateAndTime.new valid_time_params
30
- @obj.exec_time.should_not be_nil
31
- @obj.exec_time.should be_kind_of(Time)
32
- @obj.exec_time.should == Time.utc(2011, 10 ,18, 15, 15, 15)
480
+ expect(@obj.exec_time).not_to be_nil
481
+ expect(@obj.exec_time).to be_kind_of(Time)
482
+ expect(@obj.exec_time).to eq(Time.utc(2011, 10 ,18, 15, 15, 15))
33
483
  end
34
484
  end
35
485
 
@@ -41,15 +491,15 @@ describe CouchRest::Model::Properties do
41
491
  end
42
492
  it "should still create a model if there are invalid attributes" do
43
493
  @obj = WithDateAndTime.new @invalid_date_params
44
- @obj.should_not be_nil
45
- @obj.should be_kind_of(WithDateAndTime)
494
+ expect(@obj).not_to be_nil
495
+ expect(@obj).to be_kind_of(WithDateAndTime)
46
496
  end
47
497
  it "should not crash because of an empty value" do
48
498
  @invalid_date_params["exec_date(2i)"] = ""
49
499
  @obj = WithDateAndTime.new @invalid_date_params
50
- @obj.should_not be_nil
51
- @obj.exec_date.should_not be_kind_of(Date)
52
- @obj.should be_kind_of(WithDateAndTime)
500
+ expect(@obj).not_to be_nil
501
+ expect(@obj.exec_date).not_to be_kind_of(Date)
502
+ expect(@obj).to be_kind_of(WithDateAndTime)
53
503
  end
54
504
  end
55
505
 
@@ -64,9 +514,9 @@ describe CouchRest::Model::Properties do
64
514
  end
65
515
 
66
516
  it "should be accepted" do
67
- lambda {
517
+ expect {
68
518
  @obj = klass.new(:name => 'test (object)')
69
- }.should_not raise_error
519
+ }.not_to raise_error
70
520
  end
71
521
 
72
522
  end