jpbougie-couchrest 0.27

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 (94) 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 +200 -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 +304 -0
  20. data/lib/couchrest/core/design.rb +79 -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/design_doc.rb +94 -0
  33. data/lib/couchrest/mixins/document_queries.rb +44 -0
  34. data/lib/couchrest/mixins/extended_attachments.rb +68 -0
  35. data/lib/couchrest/mixins/extended_document_mixins.rb +7 -0
  36. data/lib/couchrest/mixins/properties.rb +129 -0
  37. data/lib/couchrest/mixins/validation.rb +257 -0
  38. data/lib/couchrest/mixins/views.rb +177 -0
  39. data/lib/couchrest/monkeypatches.rb +113 -0
  40. data/lib/couchrest/more/casted_model.rb +29 -0
  41. data/lib/couchrest/more/extended_document.rb +220 -0
  42. data/lib/couchrest/more/property.rb +40 -0
  43. data/lib/couchrest/support/blank.rb +42 -0
  44. data/lib/couchrest/support/class.rb +176 -0
  45. data/lib/couchrest/support/rails.rb +35 -0
  46. data/lib/couchrest/validation/auto_validate.rb +163 -0
  47. data/lib/couchrest/validation/contextual_validators.rb +78 -0
  48. data/lib/couchrest/validation/validation_errors.rb +118 -0
  49. data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
  50. data/lib/couchrest/validation/validators/confirmation_validator.rb +99 -0
  51. data/lib/couchrest/validation/validators/format_validator.rb +117 -0
  52. data/lib/couchrest/validation/validators/formats/email.rb +66 -0
  53. data/lib/couchrest/validation/validators/formats/url.rb +43 -0
  54. data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
  55. data/lib/couchrest/validation/validators/length_validator.rb +134 -0
  56. data/lib/couchrest/validation/validators/method_validator.rb +89 -0
  57. data/lib/couchrest/validation/validators/numeric_validator.rb +104 -0
  58. data/lib/couchrest/validation/validators/required_field_validator.rb +109 -0
  59. data/spec/couchrest/core/couchrest_spec.rb +201 -0
  60. data/spec/couchrest/core/database_spec.rb +699 -0
  61. data/spec/couchrest/core/design_spec.rb +138 -0
  62. data/spec/couchrest/core/document_spec.rb +267 -0
  63. data/spec/couchrest/core/server_spec.rb +35 -0
  64. data/spec/couchrest/helpers/pager_spec.rb +122 -0
  65. data/spec/couchrest/helpers/streamer_spec.rb +23 -0
  66. data/spec/couchrest/more/casted_extended_doc_spec.rb +40 -0
  67. data/spec/couchrest/more/casted_model_spec.rb +142 -0
  68. data/spec/couchrest/more/extended_doc_attachment_spec.rb +130 -0
  69. data/spec/couchrest/more/extended_doc_spec.rb +537 -0
  70. data/spec/couchrest/more/extended_doc_subclass_spec.rb +98 -0
  71. data/spec/couchrest/more/extended_doc_view_spec.rb +338 -0
  72. data/spec/couchrest/more/property_spec.rb +136 -0
  73. data/spec/fixtures/attachments/README +3 -0
  74. data/spec/fixtures/attachments/couchdb.png +0 -0
  75. data/spec/fixtures/attachments/test.html +11 -0
  76. data/spec/fixtures/more/article.rb +34 -0
  77. data/spec/fixtures/more/card.rb +20 -0
  78. data/spec/fixtures/more/cat.rb +18 -0
  79. data/spec/fixtures/more/course.rb +14 -0
  80. data/spec/fixtures/more/event.rb +6 -0
  81. data/spec/fixtures/more/invoice.rb +17 -0
  82. data/spec/fixtures/more/person.rb +8 -0
  83. data/spec/fixtures/more/question.rb +6 -0
  84. data/spec/fixtures/more/service.rb +12 -0
  85. data/spec/fixtures/views/lib.js +3 -0
  86. data/spec/fixtures/views/test_view/lib.js +3 -0
  87. data/spec/fixtures/views/test_view/only-map.js +4 -0
  88. data/spec/fixtures/views/test_view/test-map.js +3 -0
  89. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  90. data/spec/spec.opts +6 -0
  91. data/spec/spec_helper.rb +38 -0
  92. data/utils/remap.rb +27 -0
  93. data/utils/subset.rb +30 -0
  94. metadata +192 -0
@@ -0,0 +1,98 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+ require File.join(FIXTURE_PATH, 'more', 'card')
3
+ require File.join(FIXTURE_PATH, 'more', 'course')
4
+
5
+ # add a default value
6
+ Card.property :bg_color, :default => '#ccc'
7
+
8
+ class BusinessCard < Card
9
+ property :extension_code
10
+ property :job_title
11
+ end
12
+
13
+ class DesignBusinessCard < BusinessCard
14
+ property :bg_color, :default => '#eee'
15
+ end
16
+
17
+ class OnlineCourse < Course
18
+ property :url
19
+ view_by :url
20
+ end
21
+
22
+ class Animal < CouchRest::ExtendedDocument
23
+ use_database TEST_SERVER.default_database
24
+ property :name
25
+ view_by :name
26
+ end
27
+
28
+ class Dog < Animal; end
29
+
30
+ describe "Subclassing an ExtendedDocument" do
31
+
32
+ before(:each) do
33
+ @card = BusinessCard.new
34
+ end
35
+
36
+ it "shouldn't messup the parent's properties" do
37
+ Card.properties.should_not == BusinessCard.properties
38
+ end
39
+
40
+ it "should share the same db default" do
41
+ @card.database.uri.should == Card.database.uri
42
+ end
43
+
44
+ it "should share the same autovalidation details" do
45
+ @card.auto_validation.should be_true
46
+ end
47
+
48
+ it "should have kept the validation details" do
49
+ @card.should_not be_valid
50
+ end
51
+
52
+ it "should have added the new validation details" do
53
+ validated_fields = @card.class.validators.contexts[:default].map{|v| v.field_name}
54
+ validated_fields.should include(:extension_code)
55
+ validated_fields.should include(:job_title)
56
+ end
57
+
58
+ it "should not add to the parent's validations" do
59
+ validated_fields = Card.validators.contexts[:default].map{|v| v.field_name}
60
+ validated_fields.should_not include(:extension_code)
61
+ validated_fields.should_not include(:job_title)
62
+ end
63
+
64
+ it "should inherit default property values" do
65
+ @card.bg_color.should == '#ccc'
66
+ end
67
+
68
+ it "should be able to overwrite a default property" do
69
+ DesignBusinessCard.new.bg_color.should == '#eee'
70
+ end
71
+
72
+ it "should have a design doc slug based on the subclass name" do
73
+ Course.refresh_design_doc
74
+ OnlineCourse.design_doc_slug.should =~ /^OnlineCourse/
75
+ end
76
+
77
+ it "should have its own design_doc_fresh" do
78
+ Animal.refresh_design_doc
79
+ Dog.design_doc_fresh.should_not == true
80
+ Dog.refresh_design_doc
81
+ Dog.design_doc_fresh.should == true
82
+ end
83
+
84
+ it "should not add views to the parent's design_doc" do
85
+ Course.design_doc['views'].keys.should_not include('by_url')
86
+ end
87
+
88
+ it "should not add the parent's views to its design doc" do
89
+ Course.refresh_design_doc
90
+ OnlineCourse.refresh_design_doc
91
+ OnlineCourse.design_doc['views'].keys.should_not include('by_title')
92
+ end
93
+
94
+ it "should have an all view with a guard clause for couchrest-type == subclass name in the map function" do
95
+ OnlineCourse.design_doc['views']['all']['map'].should =~ /if \(doc\['couchrest-type'\] == 'OnlineCourse'\)/
96
+ end
97
+ end
98
+
@@ -0,0 +1,338 @@
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
+ describe "ExtendedDocument views" do
6
+
7
+ class Unattached < CouchRest::ExtendedDocument
8
+ # Note: no use_database here
9
+ property :title
10
+ property :questions
11
+ property :professor
12
+ view_by :title
13
+ end
14
+
15
+ describe "a model with simple views and a default param" do
16
+ before(:all) do
17
+ Article.all.map{|a| a.destroy(true)}
18
+ Article.database.bulk_delete
19
+ written_at = Time.now - 24 * 3600 * 7
20
+ @titles = ["this and that", "also interesting", "more fun", "some junk"]
21
+ @titles.each do |title|
22
+ a = Article.new(:title => title)
23
+ a.date = written_at
24
+ a.save
25
+ written_at += 24 * 3600
26
+ end
27
+ end
28
+
29
+ it "should have a design doc" do
30
+ Article.design_doc["views"]["by_date"].should_not be_nil
31
+ end
32
+
33
+ it "should save the design doc" do
34
+ Article.by_date #rescue nil
35
+ doc = Article.database.get Article.design_doc.id
36
+ doc['views']['by_date'].should_not be_nil
37
+ end
38
+
39
+ it "should return the matching raw view result" do
40
+ view = Article.by_date :raw => true
41
+ view['rows'].length.should == 4
42
+ end
43
+
44
+ it "should not include non-Articles" do
45
+ Article.database.save_doc({"date" => 1})
46
+ view = Article.by_date :raw => true
47
+ view['rows'].length.should == 4
48
+ end
49
+
50
+ it "should return the matching objects (with default argument :descending => true)" do
51
+ articles = Article.by_date
52
+ articles.collect{|a|a.title}.should == @titles.reverse
53
+ end
54
+
55
+ it "should allow you to override default args" do
56
+ articles = Article.by_date :descending => false
57
+ articles.collect{|a|a.title}.should == @titles
58
+ end
59
+ end
60
+
61
+ describe "another model with a simple view" do
62
+ before(:all) do
63
+ reset_test_db!
64
+ %w{aaa bbb ddd eee}.each do |title|
65
+ Course.new(:title => title).save
66
+ end
67
+ end
68
+ it "should make the design doc upon first query" do
69
+ Course.by_title
70
+ doc = Course.design_doc
71
+ doc['views']['all']['map'].should include('Course')
72
+ end
73
+ it "should can query via view" do
74
+ # register methods with method-missing, for local dispatch. method
75
+ # missing lookup table, no heuristics.
76
+ view = Course.view :by_title
77
+ designed = Course.by_title
78
+ view.should == designed
79
+ end
80
+ it "should get them" do
81
+ rs = Course.by_title
82
+ rs.length.should == 4
83
+ end
84
+ it "should yield" do
85
+ courses = []
86
+ Course.view(:by_title) do |course|
87
+ courses << course
88
+ end
89
+ courses[0]["doc"]["title"].should =='aaa'
90
+ end
91
+ it "should yield with by_key method" do
92
+ courses = []
93
+ Course.by_title do |course|
94
+ courses << course
95
+ end
96
+ courses[0]["doc"]["title"].should =='aaa'
97
+ end
98
+ end
99
+
100
+
101
+ describe "a ducktype view" do
102
+ before(:all) do
103
+ @id = TEST_SERVER.default_database.save_doc({:dept => true})['id']
104
+ end
105
+ it "should setup" do
106
+ duck = Course.get(@id) # from a different db
107
+ duck["dept"].should == true
108
+ end
109
+ it "should make the design doc" do
110
+ @as = Course.by_dept
111
+ @doc = Course.design_doc
112
+ @doc["views"]["by_dept"]["map"].should_not include("couchrest")
113
+ end
114
+ it "should not look for class" do |variable|
115
+ @as = Course.by_dept
116
+ @as[0]['_id'].should == @id
117
+ end
118
+ end
119
+
120
+ describe "a model class not tied to a database" do
121
+ before(:all) do
122
+ reset_test_db!
123
+ @db = TEST_SERVER.default_database
124
+ %w{aaa bbb ddd eee}.each do |title|
125
+ u = Unattached.new(:title => title)
126
+ u.database = @db
127
+ u.save
128
+ @first_id ||= u.id
129
+ end
130
+ end
131
+ it "should barf on all if no database given" do
132
+ lambda{Unattached.all}.should raise_error
133
+ end
134
+ it "should query all" do
135
+ rs = Unattached.all :database=>@db
136
+ rs.length.should == 4
137
+ end
138
+ it "should barf on query if no database given" do
139
+ lambda{Unattached.view :by_title}.should raise_error
140
+ end
141
+ it "should make the design doc upon first query" do
142
+ Unattached.by_title :database=>@db
143
+ doc = Unattached.design_doc
144
+ doc['views']['all']['map'].should include('Unattached')
145
+ end
146
+ it "should merge query params" do
147
+ rs = Unattached.by_title :database=>@db, :startkey=>"bbb", :endkey=>"eee"
148
+ rs.length.should == 3
149
+ end
150
+ it "should query via view" do
151
+ view = Unattached.view :by_title, :database=>@db
152
+ designed = Unattached.by_title :database=>@db
153
+ view.should == designed
154
+ end
155
+ it "should yield" do
156
+ things = []
157
+ Unattached.view(:by_title, :database=>@db) do |thing|
158
+ things << thing
159
+ end
160
+ things[0]["doc"]["title"].should =='aaa'
161
+ end
162
+ it "should yield with by_key method" do
163
+ things = []
164
+ Unattached.by_title(:database=>@db) do |thing|
165
+ things << thing
166
+ end
167
+ things[0]["doc"]["title"].should =='aaa'
168
+ end
169
+ it "should barf on get if no database given" do
170
+ lambda{Unattached.get("aaa")}.should raise_error
171
+ end
172
+ it "should get from specific database" do
173
+ u = Unattached.get(@first_id, @db)
174
+ u.title.should == "aaa"
175
+ end
176
+ it "should barf on first if no database given" do
177
+ lambda{Unattached.first}.should raise_error
178
+ end
179
+ it "should get first" do
180
+ u = Unattached.first :database=>@db
181
+ u.title.should =~ /\A...\z/
182
+ end
183
+ it "should barf on all_design_doc_versions if no database given" do
184
+ lambda{Unattached.all_design_doc_versions}.should raise_error
185
+ end
186
+ it "should be able to cleanup the db/bump the revision number" do
187
+ # if the previous specs were not run, the model_design_doc will be blank
188
+ Unattached.view_by :questions
189
+ Unattached.by_questions(:database => @db)
190
+ original_revision = Unattached.model_design_doc(@db)['_rev']
191
+ Unattached.cleanup_design_docs!(@db)
192
+ Unattached.model_design_doc(@db)['_rev'].should_not == original_revision
193
+ end
194
+ end
195
+
196
+ describe "class proxy" do
197
+ before(:all) do
198
+ reset_test_db!
199
+ @us = Unattached.on(TEST_SERVER.default_database)
200
+ %w{aaa bbb ddd eee}.each do |title|
201
+ u = @us.new(:title => title)
202
+ u.save
203
+ @first_id ||= u.id
204
+ end
205
+ end
206
+ it "should query all" do
207
+ rs = @us.all
208
+ rs.length.should == 4
209
+ end
210
+ it "should make the design doc upon first query" do
211
+ @us.by_title
212
+ doc = @us.design_doc
213
+ doc['views']['all']['map'].should include('Unattached')
214
+ end
215
+ it "should merge query params" do
216
+ rs = @us.by_title :startkey=>"bbb", :endkey=>"eee"
217
+ rs.length.should == 3
218
+ end
219
+ it "should query via view" do
220
+ view = @us.view :by_title
221
+ designed = @us.by_title
222
+ view.should == designed
223
+ end
224
+ it "should yield" do
225
+ things = []
226
+ @us.view(:by_title) do |thing|
227
+ things << thing
228
+ end
229
+ things[0]["doc"]["title"].should =='aaa'
230
+ end
231
+ it "should yield with by_key method" do
232
+ things = []
233
+ @us.by_title do |thing|
234
+ things << thing
235
+ end
236
+ things[0]["doc"]["title"].should =='aaa'
237
+ end
238
+ it "should get from specific database" do
239
+ u = @us.get(@first_id)
240
+ u.title.should == "aaa"
241
+ end
242
+ it "should get first" do
243
+ u = @us.first
244
+ u.title.should =~ /\A...\z/
245
+ end
246
+ it "should clean up design docs left around on specific database" do
247
+ @us.by_title
248
+ original_id = @us.model_design_doc['_rev']
249
+ Unattached.view_by :professor
250
+ @us.by_professor
251
+ @us.model_design_doc['_rev'].should_not == original_id
252
+ end
253
+ end
254
+
255
+ describe "a model with a compound key view" do
256
+ before(:all) do
257
+ Article.design_doc_fresh = false
258
+ Article.by_user_id_and_date.each{|a| a.destroy(true)}
259
+ Article.database.bulk_delete
260
+ written_at = Time.now - 24 * 3600 * 7
261
+ @titles = ["uniq one", "even more interesting", "less fun", "not junk"]
262
+ @user_ids = ["quentin", "aaron"]
263
+ @titles.each_with_index do |title,i|
264
+ u = i % 2
265
+ a = Article.new(:title => title, :user_id => @user_ids[u])
266
+ a.date = written_at
267
+ a.save
268
+ written_at += 24 * 3600
269
+ end
270
+ end
271
+ it "should create the design doc" do
272
+ Article.by_user_id_and_date rescue nil
273
+ doc = Article.design_doc
274
+ doc['views']['by_date'].should_not be_nil
275
+ end
276
+ it "should sort correctly" do
277
+ articles = Article.by_user_id_and_date
278
+ articles.collect{|a|a['user_id']}.should == ['aaron', 'aaron', 'quentin',
279
+ 'quentin']
280
+ articles[1].title.should == 'not junk'
281
+ end
282
+ it "should be queryable with couchrest options" do
283
+ articles = Article.by_user_id_and_date :limit => 1, :startkey => 'quentin'
284
+ articles.length.should == 1
285
+ articles[0].title.should == "even more interesting"
286
+ end
287
+ end
288
+
289
+ describe "with a custom view" do
290
+ before(:all) do
291
+ @titles = ["very uniq one", "even less interesting", "some fun",
292
+ "really junk", "crazy bob"]
293
+ @tags = ["cool", "lame"]
294
+ @titles.each_with_index do |title,i|
295
+ u = i % 2
296
+ a = Article.new(:title => title, :tags => [@tags[u]])
297
+ a.save
298
+ end
299
+ end
300
+ it "should be available raw" do
301
+ view = Article.by_tags :raw => true
302
+ view['rows'].length.should == 5
303
+ end
304
+
305
+ it "should be default to :reduce => false" do
306
+ ars = Article.by_tags
307
+ ars.first.tags.first.should == 'cool'
308
+ end
309
+
310
+ it "should be raw when reduce is true" do
311
+ view = Article.by_tags :reduce => true, :group => true
312
+ view['rows'].find{|r|r['key'] == 'cool'}['value'].should == 3
313
+ end
314
+ end
315
+
316
+ # TODO: moved to Design, delete
317
+ describe "adding a view" do
318
+ before(:each) do
319
+ reset_test_db!
320
+ Article.by_date
321
+ @original_doc_rev = Article.model_design_doc['_rev']
322
+ @design_docs = Article.database.documents :startkey => "_design/", :endkey => "_design/\u9999"
323
+ end
324
+ it "should not create a design doc on view definition" do
325
+ Article.view_by :created_at
326
+ newdocs = Article.database.documents :startkey => "_design/", :endkey => "_design/\u9999"
327
+ newdocs["rows"].length.should == @design_docs["rows"].length
328
+ end
329
+ it "should create a new version of the design document on view access" do
330
+ ddocs = Article.all_design_doc_versions["rows"].length
331
+ Article.view_by :updated_at
332
+ Article.by_updated_at
333
+ @original_doc_rev.should_not == Article.model_design_doc['_rev']
334
+ Article.design_doc["views"].keys.should include("by_updated_at")
335
+ end
336
+ end
337
+
338
+ end
@@ -0,0 +1,136 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
2
+ require File.join(FIXTURE_PATH, 'more', 'card')
3
+ require File.join(FIXTURE_PATH, 'more', 'invoice')
4
+ require File.join(FIXTURE_PATH, 'more', 'service')
5
+ require File.join(FIXTURE_PATH, 'more', 'event')
6
+
7
+
8
+ describe "ExtendedDocument properties" do
9
+
10
+ before(:each) do
11
+ reset_test_db!
12
+ @card = Card.new(:first_name => "matt")
13
+ end
14
+
15
+ it "should be accessible from the object" do
16
+ @card.properties.should be_an_instance_of(Array)
17
+ @card.properties.map{|p| p.name}.should include("first_name")
18
+ end
19
+
20
+ it "should let you access a property value (getter)" do
21
+ @card.first_name.should == "matt"
22
+ end
23
+
24
+ it "should let you set a property value (setter)" do
25
+ @card.last_name = "Aimonetti"
26
+ @card.last_name.should == "Aimonetti"
27
+ end
28
+
29
+ it "should not let you set a property value if it's read only" do
30
+ lambda{@card.read_only_value = "test"}.should raise_error
31
+ end
32
+
33
+ it "should let you use an alias for an attribute" do
34
+ @card.last_name = "Aimonetti"
35
+ @card.family_name.should == "Aimonetti"
36
+ @card.family_name.should == @card.last_name
37
+ end
38
+
39
+ it "should be auto timestamped" do
40
+ @card.created_at.should be_nil
41
+ @card.updated_at.should be_nil
42
+ @card.save.should be_true
43
+ @card.created_at.should_not be_nil
44
+ @card.updated_at.should_not be_nil
45
+ end
46
+
47
+ describe "validation" do
48
+ before(:each) do
49
+ @invoice = Invoice.new(:client_name => "matt", :employee_name => "Chris", :location => "San Diego, CA")
50
+ end
51
+
52
+ it "should be able to be validated" do
53
+ @card.valid?.should == true
54
+ end
55
+
56
+ it "should let you validate the presence of an attribute" do
57
+ @card.first_name = nil
58
+ @card.should_not be_valid
59
+ @card.errors.should_not be_empty
60
+ @card.errors.on(:first_name).should == ["First name must not be blank"]
61
+ end
62
+
63
+ it "should let you look up errors for a field by a string name" do
64
+ @card.first_name = nil
65
+ @card.should_not be_valid
66
+ @card.errors.on('first_name').should == ["First name must not be blank"]
67
+ end
68
+
69
+ it "should validate the presence of 2 attributes" do
70
+ @invoice.clear
71
+ @invoice.should_not be_valid
72
+ @invoice.errors.should_not be_empty
73
+ @invoice.errors.on(:client_name).first.should == "Client name must not be blank"
74
+ @invoice.errors.on(:employee_name).should_not be_empty
75
+ end
76
+
77
+ it "should let you set an error message" do
78
+ @invoice.location = nil
79
+ @invoice.valid?
80
+ @invoice.errors.on(:location).should == ["Hey stupid!, you forgot the location"]
81
+ end
82
+
83
+ it "should validate before saving" do
84
+ @invoice.location = nil
85
+ @invoice.should_not be_valid
86
+ @invoice.save.should be_false
87
+ @invoice.should be_new_document
88
+ end
89
+ end
90
+
91
+ describe "autovalidation" do
92
+ before(:each) do
93
+ @service = Service.new(:name => "Coumpound analysis", :price => 3_000)
94
+ end
95
+
96
+ it "should be valid" do
97
+ @service.should be_valid
98
+ end
99
+
100
+ it "should not respond to properties not setup" do
101
+ @service.respond_to?(:client_name).should be_false
102
+ end
103
+
104
+ describe "property :name, :length => 4...20" do
105
+
106
+ it "should autovalidate the presence when length is set" do
107
+ @service.name = nil
108
+ @service.should_not be_valid
109
+ @service.errors.should_not be_nil
110
+ @service.errors.on(:name).first.should == "Name must be between 4 and 19 characters long"
111
+ end
112
+
113
+ it "should autovalidate the correct length" do
114
+ @service.name = "a"
115
+ @service.should_not be_valid
116
+ @service.errors.should_not be_nil
117
+ @service.errors.on(:name).first.should == "Name must be between 4 and 19 characters long"
118
+ end
119
+ end
120
+ end
121
+
122
+ describe "casting" do
123
+ describe "cast keys to any type" do
124
+ before(:all) do
125
+ event_doc = { :subject => "Some event", :occurs_at => Time.now }
126
+ e = Event.database.save_doc event_doc
127
+
128
+ @event = Event.get e['id']
129
+ end
130
+ it "should cast created_at to Time" do
131
+ @event['occurs_at'].should be_an_instance_of(Time)
132
+ end
133
+ end
134
+ end
135
+
136
+ end