oz-couchrest 0.29

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 (95) hide show
  1. data/LICENSE +176 -0
  2. data/README.md +95 -0
  3. data/Rakefile +74 -0
  4. data/THANKS.md +18 -0
  5. data/examples/model/example.rb +144 -0
  6. data/examples/word_count/markov +38 -0
  7. data/examples/word_count/views/books/chunked-map.js +3 -0
  8. data/examples/word_count/views/books/united-map.js +1 -0
  9. data/examples/word_count/views/markov/chain-map.js +6 -0
  10. data/examples/word_count/views/markov/chain-reduce.js +7 -0
  11. data/examples/word_count/views/word_count/count-map.js +6 -0
  12. data/examples/word_count/views/word_count/count-reduce.js +3 -0
  13. data/examples/word_count/word_count.rb +46 -0
  14. data/examples/word_count/word_count_query.rb +40 -0
  15. data/examples/word_count/word_count_views.rb +26 -0
  16. data/lib/couchrest.rb +198 -0
  17. data/lib/couchrest/commands/generate.rb +71 -0
  18. data/lib/couchrest/commands/push.rb +103 -0
  19. data/lib/couchrest/core/database.rb +326 -0
  20. data/lib/couchrest/core/design.rb +91 -0
  21. data/lib/couchrest/core/document.rb +87 -0
  22. data/lib/couchrest/core/response.rb +16 -0
  23. data/lib/couchrest/core/server.rb +88 -0
  24. data/lib/couchrest/core/view.rb +4 -0
  25. data/lib/couchrest/helper/pager.rb +103 -0
  26. data/lib/couchrest/helper/streamer.rb +44 -0
  27. data/lib/couchrest/helper/upgrade.rb +51 -0
  28. data/lib/couchrest/mixins.rb +4 -0
  29. data/lib/couchrest/mixins/attachments.rb +31 -0
  30. data/lib/couchrest/mixins/callbacks.rb +483 -0
  31. data/lib/couchrest/mixins/class_proxy.rb +112 -0
  32. data/lib/couchrest/mixins/collection.rb +222 -0
  33. data/lib/couchrest/mixins/design_doc.rb +114 -0
  34. data/lib/couchrest/mixins/document_queries.rb +53 -0
  35. data/lib/couchrest/mixins/extended_attachments.rb +74 -0
  36. data/lib/couchrest/mixins/extended_document_mixins.rb +8 -0
  37. data/lib/couchrest/mixins/properties.rb +125 -0
  38. data/lib/couchrest/mixins/validation.rb +257 -0
  39. data/lib/couchrest/mixins/views.rb +211 -0
  40. data/lib/couchrest/monkeypatches.rb +112 -0
  41. data/lib/couchrest/more/casted_model.rb +29 -0
  42. data/lib/couchrest/more/extended_document.rb +232 -0
  43. data/lib/couchrest/more/property.rb +40 -0
  44. data/lib/couchrest/support/blank.rb +42 -0
  45. data/lib/couchrest/support/class.rb +176 -0
  46. data/lib/couchrest/support/rails.rb +35 -0
  47. data/lib/couchrest/validation/auto_validate.rb +161 -0
  48. data/lib/couchrest/validation/contextual_validators.rb +78 -0
  49. data/lib/couchrest/validation/validation_errors.rb +118 -0
  50. data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
  51. data/lib/couchrest/validation/validators/confirmation_validator.rb +99 -0
  52. data/lib/couchrest/validation/validators/format_validator.rb +117 -0
  53. data/lib/couchrest/validation/validators/formats/email.rb +66 -0
  54. data/lib/couchrest/validation/validators/formats/url.rb +43 -0
  55. data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
  56. data/lib/couchrest/validation/validators/length_validator.rb +134 -0
  57. data/lib/couchrest/validation/validators/method_validator.rb +89 -0
  58. data/lib/couchrest/validation/validators/numeric_validator.rb +104 -0
  59. data/lib/couchrest/validation/validators/required_field_validator.rb +109 -0
  60. data/spec/couchrest/core/couchrest_spec.rb +201 -0
  61. data/spec/couchrest/core/database_spec.rb +700 -0
  62. data/spec/couchrest/core/design_spec.rb +138 -0
  63. data/spec/couchrest/core/document_spec.rb +267 -0
  64. data/spec/couchrest/core/server_spec.rb +35 -0
  65. data/spec/couchrest/helpers/pager_spec.rb +122 -0
  66. data/spec/couchrest/helpers/streamer_spec.rb +23 -0
  67. data/spec/couchrest/more/casted_extended_doc_spec.rb +75 -0
  68. data/spec/couchrest/more/casted_model_spec.rb +177 -0
  69. data/spec/couchrest/more/extended_doc_attachment_spec.rb +135 -0
  70. data/spec/couchrest/more/extended_doc_spec.rb +563 -0
  71. data/spec/couchrest/more/extended_doc_subclass_spec.rb +98 -0
  72. data/spec/couchrest/more/extended_doc_view_spec.rb +414 -0
  73. data/spec/couchrest/more/property_spec.rb +146 -0
  74. data/spec/fixtures/attachments/README +3 -0
  75. data/spec/fixtures/attachments/couchdb.png +0 -0
  76. data/spec/fixtures/attachments/test.html +11 -0
  77. data/spec/fixtures/more/article.rb +34 -0
  78. data/spec/fixtures/more/card.rb +22 -0
  79. data/spec/fixtures/more/cat.rb +18 -0
  80. data/spec/fixtures/more/course.rb +14 -0
  81. data/spec/fixtures/more/event.rb +6 -0
  82. data/spec/fixtures/more/invoice.rb +17 -0
  83. data/spec/fixtures/more/person.rb +8 -0
  84. data/spec/fixtures/more/question.rb +6 -0
  85. data/spec/fixtures/more/service.rb +12 -0
  86. data/spec/fixtures/views/lib.js +3 -0
  87. data/spec/fixtures/views/test_view/lib.js +3 -0
  88. data/spec/fixtures/views/test_view/only-map.js +4 -0
  89. data/spec/fixtures/views/test_view/test-map.js +3 -0
  90. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  91. data/spec/spec.opts +6 -0
  92. data/spec/spec_helper.rb +37 -0
  93. data/utils/remap.rb +27 -0
  94. data/utils/subset.rb +30 -0
  95. metadata +194 -0
@@ -0,0 +1,23 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe CouchRest::Streamer do
4
+ before(:all) do
5
+ @cr = CouchRest.new(COUCHHOST)
6
+ @db = @cr.database(TESTDB)
7
+ @db.delete! rescue nil
8
+ @db = @cr.create_db(TESTDB) rescue nil
9
+ @streamer = CouchRest::Streamer.new(@db)
10
+ @docs = (1..1000).collect{|i| {:integer => i, :string => i.to_s}}
11
+ @db.bulk_save(@docs)
12
+ end
13
+
14
+ it "should yield each row in a view" do
15
+ count = 0
16
+ sum = 0
17
+ @streamer.view("_all_docs") do |row|
18
+ count += 1
19
+ end
20
+ count.should == 1001
21
+ end
22
+
23
+ end
@@ -0,0 +1,75 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
2
+ require File.join(FIXTURE_PATH, 'more', 'card')
3
+
4
+ class Car < CouchRest::ExtendedDocument
5
+ use_database TEST_SERVER.default_database
6
+
7
+ property :name
8
+ property :driver, :cast_as => 'Driver'
9
+ end
10
+
11
+ class Driver < CouchRest::ExtendedDocument
12
+ use_database TEST_SERVER.default_database
13
+ # You have to add a casted_by accessor if you want to reach a casted extended doc parent
14
+ attr_accessor :casted_by
15
+
16
+ property :name
17
+ end
18
+
19
+ describe "casting an extended document" do
20
+
21
+ before(:each) do
22
+ @driver = Driver.new(:name => 'Matt')
23
+ @car = Car.new(:name => 'Renault 306', :driver => @driver)
24
+ end
25
+
26
+ it "should retain all properties of the casted attribute" do
27
+ @car.driver.should == @driver
28
+ end
29
+
30
+ it "should let the casted document know who casted it" do
31
+ @car.driver.casted_by.should == @car
32
+ end
33
+ end
34
+
35
+ describe "assigning a value to casted attribute after initializing an object" do
36
+
37
+ before(:each) do
38
+ @car = Car.new(:name => 'Renault 306')
39
+ @driver = Driver.new(:name => 'Matt')
40
+ end
41
+
42
+ it "should not create an empty casted object" do
43
+ @car.driver.should be_nil
44
+ end
45
+
46
+ # Note that this isn't casting the attribute, it's just assigning it a value
47
+ # (see "should not cast attribute")
48
+ it "should let you assign the value" do
49
+ @car.driver = @driver
50
+ @car.driver.name.should == 'Matt'
51
+ end
52
+
53
+ it "should not cast attribute" do
54
+ @car.driver = JSON.parse(JSON.generate(@driver))
55
+ @car.driver.should_not be_instance_of(Driver)
56
+ end
57
+
58
+ end
59
+
60
+ describe "casting an extended document from parsed JSON" do
61
+
62
+ before(:each) do
63
+ @driver = Driver.new(:name => 'Matt')
64
+ @car = Car.new(:name => 'Renault 306', :driver => @driver)
65
+ @new_car = Car.new(JSON.parse(JSON.generate(@car)))
66
+ end
67
+
68
+ it "should cast casted attribute" do
69
+ @new_car.driver.should be_instance_of(Driver)
70
+ end
71
+
72
+ it "should retain all properties of the casted attribute" do
73
+ @new_car.driver.should == @driver
74
+ end
75
+ end
@@ -0,0 +1,177 @@
1
+ # encoding: utf-8
2
+
3
+ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
4
+ require File.join(FIXTURE_PATH, 'more', 'card')
5
+ require File.join(FIXTURE_PATH, 'more', 'cat')
6
+ require File.join(FIXTURE_PATH, 'more', 'person')
7
+
8
+
9
+ class WithCastedModelMixin < Hash
10
+ include CouchRest::CastedModel
11
+ property :name
12
+ property :no_value
13
+ property :details, :default => {}
14
+ property :casted_attribute, :cast_as => 'WithCastedModelMixin'
15
+ end
16
+
17
+ class DummyModel < CouchRest::ExtendedDocument
18
+ use_database TEST_SERVER.default_database
19
+ raise "Default DB not set" if TEST_SERVER.default_database.nil?
20
+ property :casted_attribute, :cast_as => 'WithCastedModelMixin'
21
+ property :keywords, :cast_as => ["String"]
22
+ end
23
+
24
+ describe CouchRest::CastedModel do
25
+
26
+ describe "A non hash class including CastedModel" do
27
+ it "should fail raising and include error" do
28
+ lambda do
29
+ class NotAHashButWithCastedModelMixin
30
+ include CouchRest::CastedModel
31
+ property :name
32
+ end
33
+
34
+ end.should raise_error
35
+ end
36
+ end
37
+
38
+ describe "isolated" do
39
+ before(:each) do
40
+ @obj = WithCastedModelMixin.new
41
+ end
42
+ it "should automatically include the property mixin and define getters and setters" do
43
+ @obj.name = 'Matt'
44
+ @obj.name.should == 'Matt'
45
+ end
46
+
47
+ it "should allow override of default" do
48
+ @obj = WithCastedModelMixin.new(:name => 'Eric', :details => {'color' => 'orange'})
49
+ @obj.name.should == 'Eric'
50
+ @obj.details['color'].should == 'orange'
51
+ end
52
+ end
53
+
54
+ describe "casted as an attribute, but without a value" do
55
+ before(:each) do
56
+ @obj = DummyModel.new
57
+ @casted_obj = @obj.casted_attribute
58
+ end
59
+ it "should be nil" do
60
+ @casted_obj.should == nil
61
+ end
62
+ end
63
+
64
+ describe "casted as attribute" do
65
+ before(:each) do
66
+ casted = {:name => 'not whatever'}
67
+ @obj = DummyModel.new(:casted_attribute => {:name => 'whatever', :casted_attribute => casted})
68
+ @casted_obj = @obj.casted_attribute
69
+ end
70
+
71
+ it "should be available from its parent" do
72
+ @casted_obj.should be_an_instance_of(WithCastedModelMixin)
73
+ end
74
+
75
+ it "should have the getters defined" do
76
+ @casted_obj.name.should == 'whatever'
77
+ end
78
+
79
+ it "should know who casted it" do
80
+ @casted_obj.casted_by.should == @obj
81
+ end
82
+
83
+ it "should return nil for the 'no_value' attribute" do
84
+ @casted_obj.no_value.should be_nil
85
+ end
86
+
87
+ it "should return nil for the unknown attribute" do
88
+ @casted_obj["unknown"].should be_nil
89
+ end
90
+
91
+ it "should return {} for the hash attribute" do
92
+ @casted_obj.details.should == {}
93
+ end
94
+
95
+ it "should cast its own attributes" do
96
+ @casted_obj.casted_attribute.should be_instance_of(WithCastedModelMixin)
97
+ end
98
+ end
99
+
100
+ describe "casted as an array of a different type" do
101
+ before(:each) do
102
+ @obj = DummyModel.new(:keywords => ['couch', 'sofa', 'relax', 'canapé'])
103
+ end
104
+
105
+ it "should cast the array propery" do
106
+ @obj.keywords.should be_an_instance_of(Array)
107
+ @obj.keywords.first.should == 'couch'
108
+ end
109
+
110
+ end
111
+
112
+ describe "saved document with casted models" do
113
+ before(:each) do
114
+ reset_test_db!
115
+ @obj = DummyModel.new(:casted_attribute => {:name => 'whatever'})
116
+ @obj.save.should be_true
117
+ @obj = DummyModel.get(@obj.id)
118
+ end
119
+
120
+ it "should be able to load with the casted models" do
121
+ casted_obj = @obj.casted_attribute
122
+ casted_obj.should_not be_nil
123
+ casted_obj.should be_an_instance_of(WithCastedModelMixin)
124
+ end
125
+
126
+ it "should have defined getters for the casted model" do
127
+ casted_obj = @obj.casted_attribute
128
+ casted_obj.name.should == "whatever"
129
+ end
130
+
131
+ it "should have defined setters for the casted model" do
132
+ casted_obj = @obj.casted_attribute
133
+ casted_obj.name = "test"
134
+ casted_obj.name.should == "test"
135
+ end
136
+
137
+ it "should retain an override of a casted model attribute's default" do
138
+ casted_obj = @obj.casted_attribute
139
+ casted_obj.details['color'] = 'orange'
140
+ @obj.save
141
+ casted_obj = DummyModel.get(@obj.id).casted_attribute
142
+ casted_obj.details['color'].should == 'orange'
143
+ end
144
+
145
+ end
146
+
147
+ describe "saving document with array of casted models and validation" do
148
+ before :each do
149
+ @cat = Cat.new
150
+ @cat.save
151
+ end
152
+
153
+ it "should save" do
154
+ toy = CatToy.new :name => "Mouse"
155
+ @cat.toys.push(toy)
156
+ @cat.save.should be_true
157
+ end
158
+
159
+ it "should fail because name is not present" do
160
+ toy = CatToy.new
161
+ @cat.toys.push(toy)
162
+ @cat.should_not be_valid
163
+ @cat.save.should be_false
164
+ end
165
+
166
+ it "should not fail if the casted model doesn't have validation" do
167
+ Cat.property :masters, :cast_as => ['Person'], :default => []
168
+ Cat.validates_present :name
169
+ cat = Cat.new(:name => 'kitty')
170
+ cat.should be_valid
171
+ cat.masters.push Person.new
172
+ cat.should be_valid
173
+ end
174
+
175
+ end
176
+
177
+ end
@@ -0,0 +1,135 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe "ExtendedDocument attachments" do
4
+
5
+ describe "#has_attachment?" do
6
+ before(:each) do
7
+ reset_test_db!
8
+ @obj = Basic.new
9
+ @obj.save.should == true
10
+ @file = File.open(FIXTURE_PATH + '/attachments/test.html')
11
+ @attachment_name = 'my_attachment'
12
+ @obj.create_attachment(:file => @file, :name => @attachment_name)
13
+ end
14
+
15
+ it 'should return false if there is no attachment' do
16
+ @obj.has_attachment?('bogus').should be_false
17
+ end
18
+
19
+ it 'should return true if there is an attachment' do
20
+ @obj.has_attachment?(@attachment_name).should be_true
21
+ end
22
+
23
+ it 'should return true if an object with an attachment is reloaded' do
24
+ @obj.save.should be_true
25
+ reloaded_obj = Basic.get(@obj.id)
26
+ reloaded_obj.has_attachment?(@attachment_name).should be_true
27
+ end
28
+
29
+ it 'should return false if an attachment has been removed' do
30
+ @obj.delete_attachment(@attachment_name)
31
+ @obj.has_attachment?(@attachment_name).should be_false
32
+ end
33
+ end
34
+
35
+ describe "creating an attachment" do
36
+ before(:each) do
37
+ @obj = Basic.new
38
+ @obj.save.should == true
39
+ @file_ext = File.open(FIXTURE_PATH + '/attachments/test.html')
40
+ @file_no_ext = File.open(FIXTURE_PATH + '/attachments/README')
41
+ @attachment_name = 'my_attachment'
42
+ @content_type = 'media/mp3'
43
+ end
44
+
45
+ it "should create an attachment from file with an extension" do
46
+ @obj.create_attachment(:file => @file_ext, :name => @attachment_name)
47
+ @obj.save.should == true
48
+ reloaded_obj = Basic.get(@obj.id)
49
+ reloaded_obj['_attachments'][@attachment_name].should_not be_nil
50
+ end
51
+
52
+ it "should create an attachment from file without an extension" do
53
+ @obj.create_attachment(:file => @file_no_ext, :name => @attachment_name)
54
+ @obj.save.should == true
55
+ reloaded_obj = Basic.get(@obj.id)
56
+ reloaded_obj['_attachments'][@attachment_name].should_not be_nil
57
+ end
58
+
59
+ it 'should raise ArgumentError if :file is missing' do
60
+ lambda{ @obj.create_attachment(:name => @attachment_name) }.should raise_error
61
+ end
62
+
63
+ it 'should raise ArgumentError if :name is missing' do
64
+ lambda{ @obj.create_attachment(:file => @file_ext) }.should raise_error
65
+ end
66
+
67
+ it 'should set the content-type if passed' do
68
+ @obj.create_attachment(:file => @file_ext, :name => @attachment_name, :content_type => @content_type)
69
+ @obj['_attachments'][@attachment_name]['content-type'].should == @content_type
70
+ end
71
+ end
72
+
73
+ describe 'reading, updating, and deleting an attachment' do
74
+ before(:each) do
75
+ @obj = Basic.new
76
+ @file = File.open(FIXTURE_PATH + '/attachments/test.html')
77
+ @attachment_name = 'my_attachment'
78
+ @obj.create_attachment(:file => @file, :name => @attachment_name)
79
+ @obj.save.should == true
80
+ @file.rewind
81
+ @content_type = 'media/mp3'
82
+ end
83
+
84
+ it 'should read an attachment that exists' do
85
+ @obj.read_attachment(@attachment_name).should == @file.read
86
+ end
87
+
88
+ it 'should update an attachment that exists' do
89
+ file = File.open(FIXTURE_PATH + '/attachments/README')
90
+ @file.should_not == file
91
+ @obj.update_attachment(:file => file, :name => @attachment_name)
92
+ @obj.save
93
+ reloaded_obj = Basic.get(@obj.id)
94
+ file.rewind
95
+ reloaded_obj.read_attachment(@attachment_name).should_not == @file.read
96
+ reloaded_obj.read_attachment(@attachment_name).should == file.read
97
+ end
98
+
99
+ it 'should se the content-type if passed' do
100
+ file = File.open(FIXTURE_PATH + '/attachments/README')
101
+ @file.should_not == file
102
+ @obj.update_attachment(:file => file, :name => @attachment_name, :content_type => @content_type)
103
+ @obj['_attachments'][@attachment_name]['content-type'].should == @content_type
104
+ end
105
+
106
+ it 'should delete an attachment that exists' do
107
+ @obj.delete_attachment(@attachment_name)
108
+ @obj.save
109
+ lambda{Basic.get(@obj.id).read_attachment(@attachment_name)}.should raise_error
110
+ end
111
+ end
112
+
113
+ describe "#attachment_url" do
114
+ before(:each) do
115
+ @obj = Basic.new
116
+ @file = File.open(FIXTURE_PATH + '/attachments/test.html')
117
+ @attachment_name = 'my_attachment'
118
+ @obj.create_attachment(:file => @file, :name => @attachment_name)
119
+ @obj.save.should == true
120
+ end
121
+
122
+ it 'should return nil if attachment does not exist' do
123
+ @obj.attachment_url('bogus').should be_nil
124
+ end
125
+
126
+ it 'should return the attachment URL as specified by CouchDB HttpDocumentApi' do
127
+ @obj.attachment_url(@attachment_name).should == "#{Basic.database}/#{@obj.id}/#{@attachment_name}"
128
+ end
129
+
130
+ it 'should return the attachment URI' do
131
+ @obj.attachment_uri(@attachment_name).should == "#{Basic.database.uri}/#{@obj.id}/#{@attachment_name}"
132
+ end
133
+
134
+ end
135
+ end
@@ -0,0 +1,563 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+ require File.join(FIXTURE_PATH, 'more', 'article')
3
+ require File.join(FIXTURE_PATH, 'more', 'course')
4
+
5
+
6
+ describe "ExtendedDocument" do
7
+
8
+ class WithDefaultValues < CouchRest::ExtendedDocument
9
+ use_database TEST_SERVER.default_database
10
+ property :preset, :default => {:right => 10, :top_align => false}
11
+ property :set_by_proc, :default => Proc.new{Time.now}, :cast_as => 'Time'
12
+ property :tags, :default => []
13
+ property :read_only_with_default, :default => 'generic', :read_only => true
14
+ property :default_false, :default => false
15
+ property :name
16
+ timestamps!
17
+ end
18
+
19
+ class WithCallBacks < CouchRest::ExtendedDocument
20
+ use_database TEST_SERVER.default_database
21
+ property :name
22
+ property :run_before_save
23
+ property :run_after_save
24
+ property :run_before_create
25
+ property :run_after_create
26
+ property :run_before_update
27
+ property :run_after_update
28
+
29
+ save_callback :before do |object|
30
+ object.run_before_save = true
31
+ end
32
+ save_callback :after do |object|
33
+ object.run_after_save = true
34
+ end
35
+ create_callback :before do |object|
36
+ object.run_before_create = true
37
+ end
38
+ create_callback :after do |object|
39
+ object.run_after_create = true
40
+ end
41
+ update_callback :before do |object|
42
+ object.run_before_update = true
43
+ end
44
+ update_callback :after do |object|
45
+ object.run_after_update = true
46
+ end
47
+ end
48
+
49
+ class WithTemplateAndUniqueID < CouchRest::ExtendedDocument
50
+ use_database TEST_SERVER.default_database
51
+ unique_id do |model|
52
+ model['important-field']
53
+ end
54
+ property :preset, :default => 'value'
55
+ property :has_no_default
56
+ end
57
+
58
+ class WithGetterAndSetterMethods < CouchRest::ExtendedDocument
59
+ use_database TEST_SERVER.default_database
60
+
61
+ property :other_arg
62
+ def arg
63
+ other_arg
64
+ end
65
+
66
+ def arg=(value)
67
+ self.other_arg = "foo-#{value}"
68
+ end
69
+ end
70
+
71
+ before(:each) do
72
+ @obj = WithDefaultValues.new
73
+ end
74
+
75
+ describe "instance database connection" do
76
+ it "should use the default database" do
77
+ @obj.database.name.should == 'couchrest-test'
78
+ end
79
+
80
+ it "should override the default db" do
81
+ @obj.database = TEST_SERVER.database!('couchrest-extendedmodel-test')
82
+ @obj.database.name.should == 'couchrest-extendedmodel-test'
83
+ @obj.database.delete!
84
+ end
85
+ end
86
+
87
+ describe "a new model" do
88
+ it "should be a new_record" do
89
+ @obj = Basic.new
90
+ @obj.rev.should be_nil
91
+ @obj.should be_a_new_record
92
+ 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
97
+ end
98
+ end
99
+
100
+ describe "update attributes without saving" do
101
+ before(:each) do
102
+ a = Article.get "big-bad-danger" rescue nil
103
+ a.destroy if a
104
+ @art = Article.new(:title => "big bad danger")
105
+ @art.save
106
+ end
107
+ it "should work for attribute= methods" do
108
+ @art['title'].should == "big bad danger"
109
+ @art.update_attributes_without_saving('date' => Time.now, :title => "super danger")
110
+ @art['title'].should == "super danger"
111
+ end
112
+
113
+ it "should flip out if an attribute= method is missing" do
114
+ lambda {
115
+ @art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
116
+ }.should raise_error
117
+ end
118
+
119
+ it "should not change other attributes if there is an error" do
120
+ lambda {
121
+ @art.update_attributes_without_saving('slug' => "new-slug", :title => "super danger")
122
+ }.should raise_error
123
+ @art['title'].should == "big bad danger"
124
+ end
125
+ end
126
+
127
+ describe "update attributes" do
128
+ before(:each) do
129
+ a = Article.get "big-bad-danger" rescue nil
130
+ a.destroy if a
131
+ @art = Article.new(:title => "big bad danger")
132
+ @art.save
133
+ end
134
+ it "should save" do
135
+ @art['title'].should == "big bad danger"
136
+ @art.update_attributes('date' => Time.now, :title => "super danger")
137
+ loaded = Article.get(@art.id)
138
+ loaded['title'].should == "super danger"
139
+ end
140
+ end
141
+
142
+ describe "with default" do
143
+ it "should have the default value set at initalization" do
144
+ @obj.preset.should == {:right => 10, :top_align => false}
145
+ end
146
+
147
+ it "should have the default false value explicitly assigned" do
148
+ @obj.default_false.should == false
149
+ end
150
+
151
+ it "should automatically call a proc default at initialization" do
152
+ @obj.set_by_proc.should be_an_instance_of(Time)
153
+ @obj.set_by_proc.should == @obj.set_by_proc
154
+ @obj.set_by_proc.should < Time.now
155
+ end
156
+
157
+ it "should let you overwrite the default values" do
158
+ obj = WithDefaultValues.new(:preset => 'test')
159
+ obj.preset = 'test'
160
+ end
161
+
162
+ it "should work with a default empty array" do
163
+ obj = WithDefaultValues.new(:tags => ['spec'])
164
+ obj.tags.should == ['spec']
165
+ end
166
+
167
+ it "should set default value of read-only property" do
168
+ obj = WithDefaultValues.new
169
+ obj.read_only_with_default.should == 'generic'
170
+ end
171
+ end
172
+
173
+ describe "a doc with template values (CR::Model spec)" do
174
+ before(:all) do
175
+ WithTemplateAndUniqueID.all.map{|o| o.destroy(true)}
176
+ WithTemplateAndUniqueID.database.bulk_delete
177
+ @tmpl = WithTemplateAndUniqueID.new
178
+ @tmpl2 = WithTemplateAndUniqueID.new(:preset => 'not_value', 'important-field' => '1')
179
+ end
180
+ it "should have fields set when new" do
181
+ @tmpl.preset.should == 'value'
182
+ end
183
+ it "shouldn't override explicitly set values" do
184
+ @tmpl2.preset.should == 'not_value'
185
+ end
186
+ it "shouldn't override existing documents" do
187
+ @tmpl2.save
188
+ tmpl2_reloaded = WithTemplateAndUniqueID.get(@tmpl2.id)
189
+ @tmpl2.preset.should == 'not_value'
190
+ tmpl2_reloaded.preset.should == 'not_value'
191
+ end
192
+ end
193
+
194
+ describe "getting a model" do
195
+ before(:all) do
196
+ @art = Article.new(:title => 'All About Getting')
197
+ @art.save
198
+ end
199
+ it "should load and instantiate it" do
200
+ foundart = Article.get @art.id
201
+ foundart.title.should == "All About Getting"
202
+ end
203
+ end
204
+
205
+ describe "getting a model with a subobjects array" do
206
+ before(:all) do
207
+ course_doc = {
208
+ "title" => "Metaphysics 200",
209
+ "questions" => [
210
+ {
211
+ "q" => "Carve the ___ of reality at the ___.",
212
+ "a" => ["beast","joints"]
213
+ },{
214
+ "q" => "Who layed the smack down on Leibniz's Law?",
215
+ "a" => "Willard Van Orman Quine"
216
+ }
217
+ ]
218
+ }
219
+ r = Course.database.save_doc course_doc
220
+ @course = Course.get r['id']
221
+ end
222
+ it "should load the course" do
223
+ @course.title.should == "Metaphysics 200"
224
+ end
225
+ it "should instantiate them as such" do
226
+ @course["questions"][0].a[0].should == "beast"
227
+ end
228
+ end
229
+
230
+ describe "finding all instances of a model" do
231
+ before(:all) do
232
+ WithTemplateAndUniqueID.design_doc_fresh = false
233
+ WithTemplateAndUniqueID.all.map{|o| o.destroy(true)}
234
+ WithTemplateAndUniqueID.database.bulk_delete
235
+ WithTemplateAndUniqueID.new('important-field' => '1').save
236
+ WithTemplateAndUniqueID.new('important-field' => '2').save
237
+ WithTemplateAndUniqueID.new('important-field' => '3').save
238
+ WithTemplateAndUniqueID.new('important-field' => '4').save
239
+ end
240
+ it "should make the design doc" do
241
+ WithTemplateAndUniqueID.all
242
+ d = WithTemplateAndUniqueID.design_doc
243
+ d['views']['all']['map'].should include('WithTemplateAndUniqueID')
244
+ end
245
+ it "should find all" do
246
+ rs = WithTemplateAndUniqueID.all
247
+ rs.length.should == 4
248
+ end
249
+ end
250
+
251
+ describe "counting all instances of a model" do
252
+ before(:each) do
253
+ @db = reset_test_db!
254
+ WithTemplateAndUniqueID.design_doc_fresh = false
255
+ end
256
+
257
+ it ".count should return 0 if there are no docuemtns" do
258
+ WithTemplateAndUniqueID.count.should == 0
259
+ end
260
+
261
+ it ".count should return the number of documents" do
262
+ WithTemplateAndUniqueID.new('important-field' => '1').save
263
+ WithTemplateAndUniqueID.new('important-field' => '2').save
264
+ WithTemplateAndUniqueID.new('important-field' => '3').save
265
+
266
+ WithTemplateAndUniqueID.count.should == 3
267
+ end
268
+ end
269
+
270
+ describe "finding the first instance of a model" do
271
+ before(:each) do
272
+ @db = reset_test_db!
273
+ WithTemplateAndUniqueID.design_doc_fresh = false
274
+ WithTemplateAndUniqueID.new('important-field' => '1').save
275
+ WithTemplateAndUniqueID.new('important-field' => '2').save
276
+ WithTemplateAndUniqueID.new('important-field' => '3').save
277
+ WithTemplateAndUniqueID.new('important-field' => '4').save
278
+ end
279
+ it "should make the design doc" do
280
+ WithTemplateAndUniqueID.all
281
+ d = WithTemplateAndUniqueID.design_doc
282
+ d['views']['all']['map'].should include('WithTemplateAndUniqueID')
283
+ end
284
+ it "should find first" do
285
+ rs = WithTemplateAndUniqueID.first
286
+ rs['important-field'].should == "1"
287
+ end
288
+ it "should return nil if no instances are found" do
289
+ WithTemplateAndUniqueID.all.each {|obj| obj.destroy }
290
+ WithTemplateAndUniqueID.first.should be_nil
291
+ end
292
+ end
293
+
294
+ describe "getting a model with a subobject field" do
295
+ before(:all) do
296
+ course_doc = {
297
+ "title" => "Metaphysics 410",
298
+ "professor" => {
299
+ "name" => ["Mark", "Hinchliff"]
300
+ },
301
+ "final_test_at" => "2008/12/19 13:00:00 +0800"
302
+ }
303
+ r = Course.database.save_doc course_doc
304
+ @course = Course.get r['id']
305
+ end
306
+ it "should load the course" do
307
+ @course["professor"]["name"][1].should == "Hinchliff"
308
+ end
309
+ it "should instantiate the professor as a person" do
310
+ @course['professor'].last_name.should == "Hinchliff"
311
+ end
312
+ it "should instantiate the final_test_at as a Time" do
313
+ @course['final_test_at'].should == Time.parse("2008/12/19 13:00:00 +0800")
314
+ end
315
+ end
316
+
317
+ describe "timestamping" do
318
+ before(:each) do
319
+ oldart = Article.get "saving-this" rescue nil
320
+ oldart.destroy if oldart
321
+ @art = Article.new(:title => "Saving this")
322
+ @art.save
323
+ end
324
+
325
+ it "should define the updated_at and created_at getters and set the values" do
326
+ @obj.save
327
+ obj = WithDefaultValues.get(@obj.id)
328
+ obj.should be_an_instance_of(WithDefaultValues)
329
+ obj.created_at.should be_an_instance_of(Time)
330
+ obj.updated_at.should be_an_instance_of(Time)
331
+ obj.created_at.to_s.should == @obj.updated_at.to_s
332
+ end
333
+ it "should set the time on create" do
334
+ (Time.now - @art.created_at).should < 2
335
+ foundart = Article.get @art.id
336
+ foundart.created_at.should == foundart.updated_at
337
+ end
338
+ it "should set the time on update" do
339
+ @art.save
340
+ @art.created_at.should < @art.updated_at
341
+ end
342
+ end
343
+
344
+ describe "basic saving and retrieving" do
345
+ it "should work fine" do
346
+ @obj.name = "should be easily saved and retrieved"
347
+ @obj.save
348
+ saved_obj = WithDefaultValues.get(@obj.id)
349
+ saved_obj.should_not be_nil
350
+ end
351
+
352
+ it "should parse the Time attributes automatically" do
353
+ @obj.name = "should parse the Time attributes automatically"
354
+ @obj.set_by_proc.should be_an_instance_of(Time)
355
+ @obj.save
356
+ @obj.set_by_proc.should be_an_instance_of(Time)
357
+ saved_obj = WithDefaultValues.get(@obj.id)
358
+ saved_obj.set_by_proc.should be_an_instance_of(Time)
359
+ end
360
+ end
361
+
362
+ describe "saving a model" do
363
+ before(:all) do
364
+ @sobj = Basic.new
365
+ @sobj.save.should == true
366
+ end
367
+
368
+ it "should save the doc" do
369
+ doc = Basic.get(@sobj.id)
370
+ doc['_id'].should == @sobj.id
371
+ end
372
+
373
+ it "should be set for resaving" do
374
+ rev = @obj.rev
375
+ @sobj['another-key'] = "some value"
376
+ @sobj.save
377
+ @sobj.rev.should_not == rev
378
+ end
379
+
380
+ it "should set the id" do
381
+ @sobj.id.should be_an_instance_of(String)
382
+ end
383
+
384
+ it "should set the type" do
385
+ @sobj['couchrest-type'].should == 'Basic'
386
+ end
387
+ end
388
+
389
+ describe "saving a model with a unique_id configured" do
390
+ before(:each) do
391
+ @art = Article.new
392
+ @old = Article.database.get('this-is-the-title') rescue nil
393
+ Article.database.delete_doc(@old) if @old
394
+ end
395
+
396
+ it "should be a new document" do
397
+ @art.should be_a_new_document
398
+ @art.title.should be_nil
399
+ end
400
+
401
+ it "should require the title" do
402
+ lambda{@art.save}.should raise_error
403
+ @art.title = 'This is the title'
404
+ @art.save.should == true
405
+ end
406
+
407
+ it "should not change the slug on update" do
408
+ @art.title = 'This is the title'
409
+ @art.save.should == true
410
+ @art.title = 'new title'
411
+ @art.save.should == true
412
+ @art.slug.should == 'this-is-the-title'
413
+ end
414
+
415
+ it "should raise an error when the slug is taken" do
416
+ @art.title = 'This is the title'
417
+ @art.save.should == true
418
+ @art2 = Article.new(:title => 'This is the title!')
419
+ lambda{@art2.save}.should raise_error
420
+ end
421
+
422
+ it "should set the slug" do
423
+ @art.title = 'This is the title'
424
+ @art.save.should == true
425
+ @art.slug.should == 'this-is-the-title'
426
+ end
427
+
428
+ it "should set the id" do
429
+ @art.title = 'This is the title'
430
+ @art.save.should == true
431
+ @art.id.should == 'this-is-the-title'
432
+ end
433
+ end
434
+
435
+ describe "saving a model with a unique_id lambda" do
436
+ before(:each) do
437
+ @templated = WithTemplateAndUniqueID.new
438
+ @old = WithTemplateAndUniqueID.get('very-important') rescue nil
439
+ @old.destroy if @old
440
+ end
441
+
442
+ it "should require the field" do
443
+ lambda{@templated.save}.should raise_error
444
+ @templated['important-field'] = 'very-important'
445
+ @templated.save.should == true
446
+ end
447
+
448
+ it "should save with the id" do
449
+ @templated['important-field'] = 'very-important'
450
+ @templated.save.should == true
451
+ t = WithTemplateAndUniqueID.get('very-important')
452
+ t.should == @templated
453
+ end
454
+
455
+ it "should not change the id on update" do
456
+ @templated['important-field'] = 'very-important'
457
+ @templated.save.should == true
458
+ @templated['important-field'] = 'not-important'
459
+ @templated.save.should == true
460
+ t = WithTemplateAndUniqueID.get('very-important')
461
+ t.should == @templated
462
+ end
463
+
464
+ it "should raise an error when the id is taken" do
465
+ @templated['important-field'] = 'very-important'
466
+ @templated.save.should == true
467
+ lambda{WithTemplateAndUniqueID.new('important-field' => 'very-important').save}.should raise_error
468
+ end
469
+
470
+ it "should set the id" do
471
+ @templated['important-field'] = 'very-important'
472
+ @templated.save.should == true
473
+ @templated.id.should == 'very-important'
474
+ end
475
+ end
476
+
477
+ describe "destroying an instance" do
478
+ before(:each) do
479
+ @dobj = Basic.new
480
+ @dobj.save.should == true
481
+ end
482
+ it "should return true" do
483
+ result = @dobj.destroy
484
+ result.should == true
485
+ end
486
+ it "should be resavable" do
487
+ @dobj.destroy
488
+ @dobj.rev.should be_nil
489
+ @dobj.id.should be_nil
490
+ @dobj.save.should == true
491
+ end
492
+ it "should make it go away" do
493
+ @dobj.destroy
494
+ lambda{Basic.get(@dobj.id)}.should raise_error
495
+ end
496
+ end
497
+
498
+
499
+ describe "callbacks" do
500
+
501
+ before(:each) do
502
+ @doc = WithCallBacks.new
503
+ end
504
+
505
+ describe "save" do
506
+ it "should run the after filter after saving" do
507
+ @doc.run_after_save.should be_nil
508
+ @doc.save.should be_true
509
+ @doc.run_after_save.should be_true
510
+ end
511
+ end
512
+ describe "create" do
513
+ it "should run the before save filter when creating" do
514
+ @doc.run_before_save.should be_nil
515
+ @doc.create.should_not be_nil
516
+ @doc.run_before_save.should be_true
517
+ end
518
+ it "should run the before create filter" do
519
+ @doc.run_before_create.should be_nil
520
+ @doc.create.should_not be_nil
521
+ @doc.create
522
+ @doc.run_before_create.should be_true
523
+ end
524
+ it "should run the after create filter" do
525
+ @doc.run_after_create.should be_nil
526
+ @doc.create.should_not be_nil
527
+ @doc.create
528
+ @doc.run_after_create.should be_true
529
+ end
530
+ end
531
+ describe "update" do
532
+
533
+ before(:each) do
534
+ @doc.save
535
+ end
536
+ it "should run the before update filter when updating an existing document" do
537
+ @doc.run_before_update.should be_nil
538
+ @doc.update
539
+ @doc.run_before_update.should be_true
540
+ end
541
+ it "should run the after update filter when updating an existing document" do
542
+ @doc.run_after_update.should be_nil
543
+ @doc.update
544
+ @doc.run_after_update.should be_true
545
+ end
546
+ it "should run the before update filter when saving an existing document" do
547
+ @doc.run_before_update.should be_nil
548
+ @doc.save
549
+ @doc.run_before_update.should be_true
550
+ end
551
+
552
+ end
553
+ end
554
+
555
+ describe "getter and setter methods" do
556
+ it "should try to call the arg= method before setting :arg in the hash" do
557
+ @doc = WithGetterAndSetterMethods.new(:arg => "foo")
558
+ @doc['arg'].should be_nil
559
+ @doc[:arg].should be_nil
560
+ @doc.other_arg.should == "foo-foo"
561
+ end
562
+ end
563
+ end