couchrest 0.33 → 0.34

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 (45) hide show
  1. data/README.md +8 -127
  2. data/Rakefile +20 -36
  3. data/THANKS.md +2 -1
  4. data/history.txt +25 -0
  5. data/lib/couchrest.rb +5 -4
  6. data/lib/couchrest/core/database.rb +26 -21
  7. data/lib/couchrest/core/document.rb +4 -3
  8. data/lib/couchrest/helper/streamer.rb +11 -4
  9. data/lib/couchrest/mixins/attribute_protection.rb +74 -0
  10. data/lib/couchrest/mixins/callbacks.rb +187 -138
  11. data/lib/couchrest/mixins/collection.rb +3 -16
  12. data/lib/couchrest/mixins/extended_attachments.rb +1 -1
  13. data/lib/couchrest/mixins/extended_document_mixins.rb +1 -0
  14. data/lib/couchrest/mixins/properties.rb +71 -44
  15. data/lib/couchrest/mixins/validation.rb +18 -29
  16. data/lib/couchrest/more/casted_model.rb +29 -1
  17. data/lib/couchrest/more/extended_document.rb +73 -25
  18. data/lib/couchrest/more/property.rb +20 -1
  19. data/lib/couchrest/support/class.rb +81 -67
  20. data/lib/couchrest/support/rails.rb +12 -5
  21. data/lib/couchrest/validation/auto_validate.rb +5 -9
  22. data/lib/couchrest/validation/validators/confirmation_validator.rb +11 -3
  23. data/lib/couchrest/validation/validators/format_validator.rb +8 -3
  24. data/lib/couchrest/validation/validators/length_validator.rb +10 -5
  25. data/lib/couchrest/validation/validators/numeric_validator.rb +6 -1
  26. data/lib/couchrest/validation/validators/required_field_validator.rb +8 -3
  27. data/spec/couchrest/core/couchrest_spec.rb +48 -2
  28. data/spec/couchrest/core/database_spec.rb +22 -10
  29. data/spec/couchrest/core/document_spec.rb +9 -1
  30. data/spec/couchrest/helpers/streamer_spec.rb +31 -2
  31. data/spec/couchrest/more/attribute_protection_spec.rb +94 -0
  32. data/spec/couchrest/more/casted_extended_doc_spec.rb +2 -4
  33. data/spec/couchrest/more/casted_model_spec.rb +230 -1
  34. data/spec/couchrest/more/extended_doc_attachment_spec.rb +2 -2
  35. data/spec/couchrest/more/extended_doc_spec.rb +173 -15
  36. data/spec/couchrest/more/extended_doc_view_spec.rb +17 -10
  37. data/spec/couchrest/more/property_spec.rb +97 -3
  38. data/spec/fixtures/more/article.rb +4 -3
  39. data/spec/fixtures/more/card.rb +1 -1
  40. data/spec/fixtures/more/cat.rb +5 -3
  41. data/spec/fixtures/more/event.rb +4 -1
  42. data/spec/fixtures/more/invoice.rb +2 -2
  43. data/spec/fixtures/more/person.rb +1 -0
  44. data/spec/fixtures/more/user.rb +22 -0
  45. metadata +46 -13
@@ -66,7 +66,7 @@ describe "ExtendedDocument attachments" do
66
66
 
67
67
  it 'should set the content-type if passed' do
68
68
  @obj.create_attachment(:file => @file_ext, :name => @attachment_name, :content_type => @content_type)
69
- @obj['_attachments'][@attachment_name]['content-type'].should == @content_type
69
+ @obj['_attachments'][@attachment_name]['content_type'].should == @content_type
70
70
  end
71
71
  end
72
72
 
@@ -100,7 +100,7 @@ describe "ExtendedDocument attachments" do
100
100
  file = File.open(FIXTURE_PATH + '/attachments/README')
101
101
  @file.should_not == file
102
102
  @obj.update_attachment(:file => file, :name => @attachment_name, :content_type => @content_type)
103
- @obj['_attachments'][@attachment_name]['content-type'].should == @content_type
103
+ @obj['_attachments'][@attachment_name]['content_type'].should == @content_type
104
104
  end
105
105
 
106
106
  it 'should delete an attachment that exists' do
@@ -1,7 +1,9 @@
1
+ # encoding: utf-8
2
+
1
3
  require File.expand_path("../../../spec_helper", __FILE__)
2
4
  require File.join(FIXTURE_PATH, 'more', 'article')
3
5
  require File.join(FIXTURE_PATH, 'more', 'course')
4
-
6
+ require File.join(FIXTURE_PATH, 'more', 'cat')
5
7
 
6
8
  describe "ExtendedDocument" do
7
9
 
@@ -17,8 +19,11 @@ describe "ExtendedDocument" do
17
19
  end
18
20
 
19
21
  class WithCallBacks < CouchRest::ExtendedDocument
22
+ include ::CouchRest::Validation
20
23
  use_database TEST_SERVER.default_database
21
24
  property :name
25
+ property :run_before_validate
26
+ property :run_after_validate
22
27
  property :run_before_save
23
28
  property :run_after_save
24
29
  property :run_before_create
@@ -26,24 +31,56 @@ describe "ExtendedDocument" do
26
31
  property :run_before_update
27
32
  property :run_after_update
28
33
 
29
- save_callback :before do |object|
34
+ before_validate do |object|
35
+ object.run_before_validate = true
36
+ end
37
+ after_validate do |object|
38
+ object.run_after_validate = true
39
+ end
40
+ before_save do |object|
30
41
  object.run_before_save = true
31
42
  end
32
- save_callback :after do |object|
43
+ after_save do |object|
33
44
  object.run_after_save = true
34
45
  end
35
- create_callback :before do |object|
46
+ before_create do |object|
36
47
  object.run_before_create = true
37
48
  end
38
- create_callback :after do |object|
49
+ after_create do |object|
39
50
  object.run_after_create = true
40
51
  end
41
- update_callback :before do |object|
52
+ before_update do |object|
42
53
  object.run_before_update = true
43
54
  end
44
- update_callback :after do |object|
55
+ after_update do |object|
45
56
  object.run_after_update = true
46
57
  end
58
+
59
+ property :run_one
60
+ property :run_two
61
+ property :run_three
62
+
63
+ before_save :run_one_method, :run_two_method do |object|
64
+ object.run_three = true
65
+ end
66
+ def run_one_method
67
+ self.run_one = true
68
+ end
69
+ def run_two_method
70
+ self.run_two = true
71
+ end
72
+
73
+ attr_accessor :run_it
74
+ property :conditional_one
75
+ property :conditional_two
76
+
77
+ before_save :conditional_one_method, :conditional_two_method, :if => proc { self.run_it }
78
+ def conditional_one_method
79
+ self.conditional_one = true
80
+ end
81
+ def conditional_two_method
82
+ self.conditional_two = true
83
+ end
47
84
  end
48
85
 
49
86
  class WithTemplateAndUniqueID < CouchRest::ExtendedDocument
@@ -85,15 +122,17 @@ describe "ExtendedDocument" do
85
122
  end
86
123
 
87
124
  describe "a new model" do
88
- it "should be a new_record" do
125
+ it "should be a new document" do
89
126
  @obj = Basic.new
90
127
  @obj.rev.should be_nil
91
- @obj.should be_a_new_record
128
+ @obj.should be_new
129
+ @obj.should be_new_document
130
+ @obj.should be_new_record
92
131
  end
93
- it "should be a new_document" do
94
- @obj = Basic.new
95
- @obj.rev.should be_nil
96
- @obj.should be_a_new_document
132
+
133
+ it "should not failed on a nil value in argument" do
134
+ @obj = Basic.new(nil)
135
+ @obj.should == { 'couchrest-type' => 'Basic' }
97
136
  end
98
137
  end
99
138
 
@@ -101,7 +140,7 @@ describe "ExtendedDocument" do
101
140
  it "should instantialize and save a document" do
102
141
  article = Article.create(:title => 'my test')
103
142
  article.title.should == 'my test'
104
- article.should_not be_new_document
143
+ article.should_not be_new
105
144
  end
106
145
 
107
146
  it "should trigger the create callbacks" do
@@ -125,6 +164,27 @@ describe "ExtendedDocument" do
125
164
  @art.update_attributes_without_saving('date' => Time.now, :title => "super danger")
126
165
  @art['title'].should == "super danger"
127
166
  end
167
+ it "should silently ignore _id" do
168
+ @art.update_attributes_without_saving('_id' => 'foobar')
169
+ @art['_id'].should_not == 'foobar'
170
+ end
171
+ it "should silently ignore _rev" do
172
+ @art.update_attributes_without_saving('_rev' => 'foobar')
173
+ @art['_rev'].should_not == 'foobar'
174
+ end
175
+ it "should silently ignore created_at" do
176
+ @art.update_attributes_without_saving('created_at' => 'foobar')
177
+ @art['created_at'].should_not == 'foobar'
178
+ end
179
+ it "should silently ignore updated_at" do
180
+ @art.update_attributes_without_saving('updated_at' => 'foobar')
181
+ @art['updated_at'].should_not == 'foobar'
182
+ end
183
+ it "should also work using attributes= alias" do
184
+ @art.respond_to?(:attributes=).should be_true
185
+ @art.attributes = {'date' => Time.now, :title => "something else"}
186
+ @art['title'].should == "something else"
187
+ end
128
188
 
129
189
  it "should flip out if an attribute= method is missing" do
130
190
  lambda {
@@ -409,6 +469,25 @@ describe "ExtendedDocument" do
409
469
  it "should set the type" do
410
470
  @sobj['couchrest-type'].should == 'Basic'
411
471
  end
472
+
473
+ describe "save!" do
474
+
475
+ before(:each) do
476
+ @sobj = Card.new(:first_name => "Marcos", :last_name => "Tapajós")
477
+ end
478
+
479
+ it "should return true if save the document" do
480
+ @sobj.save!.should == true
481
+ end
482
+
483
+ it "should raise error if don't save the document" do
484
+ @sobj.first_name = nil
485
+ lambda { @sobj.save!.should == true }.should raise_error(RuntimeError)
486
+ end
487
+
488
+ end
489
+
490
+
412
491
  end
413
492
 
414
493
  describe "saving a model with a unique_id configured" do
@@ -419,7 +498,7 @@ describe "ExtendedDocument" do
419
498
  end
420
499
 
421
500
  it "should be a new document" do
422
- @art.should be_a_new_document
501
+ @art.should be_new
423
502
  @art.title.should be_nil
424
503
  end
425
504
 
@@ -527,12 +606,46 @@ describe "ExtendedDocument" do
527
606
  @doc = WithCallBacks.new
528
607
  end
529
608
 
609
+
610
+ describe "validate" do
611
+ it "should run before_validate before validating" do
612
+ @doc.run_before_validate.should be_nil
613
+ @doc.should be_valid
614
+ @doc.run_before_validate.should be_true
615
+ end
616
+ it "should run after_validate after validating" do
617
+ @doc.run_after_validate.should be_nil
618
+ @doc.should be_valid
619
+ @doc.run_after_validate.should be_true
620
+ end
621
+ end
530
622
  describe "save" do
531
623
  it "should run the after filter after saving" do
532
624
  @doc.run_after_save.should be_nil
533
625
  @doc.save.should be_true
534
626
  @doc.run_after_save.should be_true
535
627
  end
628
+ it "should run the grouped callbacks before saving" do
629
+ @doc.run_one.should be_nil
630
+ @doc.run_two.should be_nil
631
+ @doc.run_three.should be_nil
632
+ @doc.save.should be_true
633
+ @doc.run_one.should be_true
634
+ @doc.run_two.should be_true
635
+ @doc.run_three.should be_true
636
+ end
637
+ it "should not run conditional callbacks" do
638
+ @doc.run_it = false
639
+ @doc.save.should be_true
640
+ @doc.conditional_one.should be_nil
641
+ @doc.conditional_two.should be_nil
642
+ end
643
+ it "should run conditional callbacks" do
644
+ @doc.run_it = true
645
+ @doc.save.should be_true
646
+ @doc.conditional_one.should be_true
647
+ @doc.conditional_two.should be_true
648
+ end
536
649
  end
537
650
  describe "create" do
538
651
  it "should run the before save filter when creating" do
@@ -585,4 +698,49 @@ describe "ExtendedDocument" do
585
698
  @doc.other_arg.should == "foo-foo"
586
699
  end
587
700
  end
701
+
702
+ describe "recursive validation on an extended document" do
703
+ before :each do
704
+ reset_test_db!
705
+ @cat = Cat.new(:name => 'Sockington')
706
+ end
707
+
708
+ it "should not save if a nested casted model is invalid" do
709
+ @cat.favorite_toy = CatToy.new
710
+ @cat.should_not be_valid
711
+ @cat.save.should be_false
712
+ lambda{@cat.save!}.should raise_error
713
+ end
714
+
715
+ it "should save when nested casted model is valid" do
716
+ @cat.favorite_toy = CatToy.new(:name => 'Squeaky')
717
+ @cat.should be_valid
718
+ @cat.save.should be_true
719
+ lambda{@cat.save!}.should_not raise_error
720
+ end
721
+
722
+ it "should not save when nested collection contains an invalid casted model" do
723
+ @cat.toys = [CatToy.new(:name => 'Feather'), CatToy.new]
724
+ @cat.should_not be_valid
725
+ @cat.save.should be_false
726
+ lambda{@cat.save!}.should raise_error
727
+ end
728
+
729
+ it "should save when nested collection contains valid casted models" do
730
+ @cat.toys = [CatToy.new(:name => 'feather'), CatToy.new(:name => 'ball-o-twine')]
731
+ @cat.should be_valid
732
+ @cat.save.should be_true
733
+ lambda{@cat.save!}.should_not raise_error
734
+ end
735
+
736
+ it "should not fail if the nested casted model doesn't have validation" do
737
+ Cat.property :trainer, :cast_as => 'Person'
738
+ Cat.validates_presence_of :name
739
+ cat = Cat.new(:name => 'Mr Bigglesworth')
740
+ cat.trainer = Person.new
741
+ cat.trainer.validatable?.should be_false
742
+ cat.should be_valid
743
+ cat.save.should be_true
744
+ end
745
+ end
588
746
  end
@@ -348,12 +348,19 @@ describe "ExtendedDocument views" do
348
348
  describe "with a collection" do
349
349
  before(:all) do
350
350
  reset_test_db!
351
- @titles = ["very uniq one", "really interesting", "some fun",
351
+ titles = ["very uniq one", "really interesting", "some fun",
352
352
  "really awesome", "crazy bob", "this rocks", "super rad"]
353
- @titles.each_with_index do |title,i|
353
+ titles.each_with_index do |title,i|
354
354
  a = Article.new(:title => title, :date => Date.today)
355
355
  a.save
356
356
  end
357
+
358
+ titles = ["yesterday very uniq one", "yesterday really interesting", "yesterday some fun",
359
+ "yesterday really awesome", "yesterday crazy bob", "yesterday this rocks"]
360
+ titles.each_with_index do |title,i|
361
+ a = Article.new(:title => title, :date => Date.today - 1)
362
+ a.save
363
+ end
357
364
  end
358
365
  require 'date'
359
366
  it "should return a proxy that looks like an array of 7 Article objects" do
@@ -373,10 +380,6 @@ describe "ExtendedDocument views" do
373
380
  a.should_not be_nil
374
381
  end
375
382
  end
376
- it "should have the amount of paginated pages" do
377
- articles = Article.by_date :key => Date.today
378
- articles.paginate(:per_page => 3).amount_pages.should == 3
379
- end
380
383
  it "should provide a class method to access the collection directly" do
381
384
  articles = Article.collection_proxy_for('Article', 'by_date', :descending => true,
382
385
  :key => Date.today, :include_docs => true)
@@ -405,10 +408,6 @@ describe "ExtendedDocument views" do
405
408
  end
406
409
  end
407
410
  it "should provide a class method to get a collection for a view" do
408
- class Article
409
- provides_collection :article_details, 'Article', 'by_date', :descending => true, :include_docs => true
410
- end
411
-
412
411
  articles = Article.find_all_article_details(:key => Date.today)
413
412
  articles.class.should == Array
414
413
  articles.size.should == 7
@@ -421,6 +420,14 @@ describe "ExtendedDocument views" do
421
420
  lambda{Article.collection_proxy_for('Article', nil)}.should raise_error
422
421
  lambda{Article.paginate(:design_doc => 'Article')}.should raise_error
423
422
  end
423
+ it "should be able to span multiple keys" do
424
+ articles = Article.by_date :startkey => Date.today, :endkey => Date.today - 1
425
+ articles.paginate(:page => 1, :per_page => 3).size.should == 3
426
+ articles.paginate(:page => 2, :per_page => 3).size.should == 3
427
+ articles.paginate(:page => 3, :per_page => 3).size.should == 3
428
+ articles.paginate(:page => 4, :per_page => 3).size.should == 3
429
+ articles.paginate(:page => 5, :per_page => 3).size.should == 1
430
+ end
424
431
  end
425
432
 
426
433
  end
@@ -1,9 +1,12 @@
1
+ # encoding: utf-8
1
2
  require File.expand_path('../../../spec_helper', __FILE__)
2
3
  require File.join(FIXTURE_PATH, 'more', 'person')
3
4
  require File.join(FIXTURE_PATH, 'more', 'card')
4
5
  require File.join(FIXTURE_PATH, 'more', 'invoice')
5
6
  require File.join(FIXTURE_PATH, 'more', 'service')
6
7
  require File.join(FIXTURE_PATH, 'more', 'event')
8
+ require File.join(FIXTURE_PATH, 'more', 'cat')
9
+ require File.join(FIXTURE_PATH, 'more', 'user')
7
10
 
8
11
 
9
12
  describe "ExtendedDocument properties" do
@@ -54,6 +57,30 @@ describe "ExtendedDocument properties" do
54
57
  @card.updated_at.should_not be_nil
55
58
  end
56
59
 
60
+
61
+ describe "mass assignment protection" do
62
+
63
+ it "should not store protected attribute using mass assignment" do
64
+ cat_toy = CatToy.new(:name => "Zorro")
65
+ cat = Cat.create(:name => "Helena", :toys => [cat_toy], :favorite_toy => cat_toy, :number => 1)
66
+ cat.number.should be_nil
67
+ cat.number = 1
68
+ cat.save
69
+ cat.number.should == 1
70
+ end
71
+
72
+ it "should not store protected attribute when 'declare accessible poperties, assume all the rest are protected'" do
73
+ user = User.create(:name => "Marcos Tapajós", :admin => true)
74
+ user.admin.should be_nil
75
+ end
76
+
77
+ it "should not store protected attribute when 'declare protected properties, assume all the rest are accessible'" do
78
+ user = SpecialUser.create(:name => "Marcos Tapajós", :admin => true)
79
+ user.admin.should be_nil
80
+ end
81
+
82
+ end
83
+
57
84
  describe "validation" do
58
85
  before(:each) do
59
86
  @invoice = Invoice.new(:client_name => "matt", :employee_name => "Chris", :location => "San Diego, CA")
@@ -94,7 +121,7 @@ describe "ExtendedDocument properties" do
94
121
  @invoice.location = nil
95
122
  @invoice.should_not be_valid
96
123
  @invoice.save.should be_false
97
- @invoice.should be_new_document
124
+ @invoice.should be_new
98
125
  end
99
126
  end
100
127
 
@@ -132,14 +159,17 @@ describe "ExtendedDocument properties" do
132
159
  describe "casting" do
133
160
  describe "cast keys to any type" do
134
161
  before(:all) do
135
- event_doc = { :subject => "Some event", :occurs_at => Time.now }
162
+ event_doc = { :subject => "Some event", :occurs_at => Time.now, :end_date => Date.today }
136
163
  e = Event.database.save_doc event_doc
137
164
 
138
165
  @event = Event.get e['id']
139
166
  end
140
- it "should cast created_at to Time" do
167
+ it "should cast occurs_at to Time" do
141
168
  @event['occurs_at'].should be_an_instance_of(Time)
142
169
  end
170
+ it "should cast end_date to Date" do
171
+ @event['end_date'].should be_an_instance_of(Date)
172
+ end
143
173
  end
144
174
 
145
175
  describe "casting to Float object" do
@@ -191,5 +221,69 @@ describe "ExtendedDocument properties" do
191
221
  end
192
222
 
193
223
  end
224
+ end
225
+
226
+ describe "a newly created casted model" do
227
+ before(:each) do
228
+ reset_test_db!
229
+ @cat = Cat.new(:name => 'Toonces')
230
+ @squeaky_mouse = CatToy.new(:name => 'Squeaky')
231
+ end
232
+
233
+ describe "assigned assigned to a casted property" do
234
+ it "should have casted_by set to its parent" do
235
+ @squeaky_mouse.casted_by.should be_nil
236
+ @cat.favorite_toy = @squeaky_mouse
237
+ @squeaky_mouse.casted_by.should === @cat
238
+ end
239
+ end
240
+
241
+ describe "appended to a casted collection" do
242
+ it "should have casted_by set to its parent" do
243
+ @squeaky_mouse.casted_by.should be_nil
244
+ @cat.toys << @squeaky_mouse
245
+ @squeaky_mouse.casted_by.should === @cat
246
+ @cat.save
247
+ @cat.toys.first.casted_by.should === @cat
248
+ end
249
+ end
194
250
 
251
+ describe "list assigned to a casted collection" do
252
+ it "should have casted_by set on all elements" do
253
+ toy1 = CatToy.new(:name => 'Feather')
254
+ toy2 = CatToy.new(:name => 'Mouse')
255
+ @cat.toys = [toy1, toy2]
256
+ toy1.casted_by.should === @cat
257
+ toy2.casted_by.should === @cat
258
+ @cat.save
259
+ @cat = Cat.get(@cat.id)
260
+ @cat.toys[0].casted_by.should === @cat
261
+ @cat.toys[1].casted_by.should === @cat
262
+ end
263
+ end
195
264
  end
265
+
266
+ describe "a casted model retrieved from the database" do
267
+ before(:each) do
268
+ reset_test_db!
269
+ @cat = Cat.new(:name => 'Stimpy')
270
+ @cat.favorite_toy = CatToy.new(:name => 'Stinky')
271
+ @cat.toys << CatToy.new(:name => 'Feather')
272
+ @cat.toys << CatToy.new(:name => 'Mouse')
273
+ @cat.save
274
+ @cat = Cat.get(@cat.id)
275
+ end
276
+
277
+ describe "as a casted property" do
278
+ it "should already be casted_by its parent" do
279
+ @cat.favorite_toy.casted_by.should === @cat
280
+ end
281
+ end
282
+
283
+ describe "from a casted collection" do
284
+ it "should already be casted_by its parent" do
285
+ @cat.toys[0].casted_by.should === @cat
286
+ @cat.toys[1].casted_by.should === @cat
287
+ end
288
+ end
289
+ end