couchrest_model 2.1.0.rc1 → 2.2.0.beta1

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