jchris-couchrest 0.12.6 → 0.16

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