brianmario-couchrest 0.23

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 (92) hide show
  1. data/LICENSE +176 -0
  2. data/README.md +95 -0
  3. data/Rakefile +75 -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 +303 -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 +108 -0
  32. data/lib/couchrest/mixins/design_doc.rb +90 -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 +242 -0
  38. data/lib/couchrest/mixins/views.rb +169 -0
  39. data/lib/couchrest/monkeypatches.rb +113 -0
  40. data/lib/couchrest/more/casted_model.rb +28 -0
  41. data/lib/couchrest/more/extended_document.rb +215 -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/validation/auto_validate.rb +163 -0
  46. data/lib/couchrest/validation/contextual_validators.rb +78 -0
  47. data/lib/couchrest/validation/validation_errors.rb +118 -0
  48. data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
  49. data/lib/couchrest/validation/validators/confirmation_validator.rb +99 -0
  50. data/lib/couchrest/validation/validators/format_validator.rb +117 -0
  51. data/lib/couchrest/validation/validators/formats/email.rb +66 -0
  52. data/lib/couchrest/validation/validators/formats/url.rb +43 -0
  53. data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
  54. data/lib/couchrest/validation/validators/length_validator.rb +134 -0
  55. data/lib/couchrest/validation/validators/method_validator.rb +89 -0
  56. data/lib/couchrest/validation/validators/numeric_validator.rb +104 -0
  57. data/lib/couchrest/validation/validators/required_field_validator.rb +109 -0
  58. data/spec/couchrest/core/couchrest_spec.rb +201 -0
  59. data/spec/couchrest/core/database_spec.rb +699 -0
  60. data/spec/couchrest/core/design_spec.rb +138 -0
  61. data/spec/couchrest/core/document_spec.rb +267 -0
  62. data/spec/couchrest/core/server_spec.rb +35 -0
  63. data/spec/couchrest/helpers/pager_spec.rb +122 -0
  64. data/spec/couchrest/helpers/streamer_spec.rb +23 -0
  65. data/spec/couchrest/more/casted_extended_doc_spec.rb +40 -0
  66. data/spec/couchrest/more/casted_model_spec.rb +98 -0
  67. data/spec/couchrest/more/extended_doc_attachment_spec.rb +130 -0
  68. data/spec/couchrest/more/extended_doc_spec.rb +509 -0
  69. data/spec/couchrest/more/extended_doc_subclass_spec.rb +98 -0
  70. data/spec/couchrest/more/extended_doc_view_spec.rb +355 -0
  71. data/spec/couchrest/more/property_spec.rb +136 -0
  72. data/spec/fixtures/attachments/README +3 -0
  73. data/spec/fixtures/attachments/couchdb.png +0 -0
  74. data/spec/fixtures/attachments/test.html +11 -0
  75. data/spec/fixtures/more/article.rb +34 -0
  76. data/spec/fixtures/more/card.rb +20 -0
  77. data/spec/fixtures/more/course.rb +14 -0
  78. data/spec/fixtures/more/event.rb +6 -0
  79. data/spec/fixtures/more/invoice.rb +17 -0
  80. data/spec/fixtures/more/person.rb +8 -0
  81. data/spec/fixtures/more/question.rb +6 -0
  82. data/spec/fixtures/more/service.rb +12 -0
  83. data/spec/fixtures/views/lib.js +3 -0
  84. data/spec/fixtures/views/test_view/lib.js +3 -0
  85. data/spec/fixtures/views/test_view/only-map.js +4 -0
  86. data/spec/fixtures/views/test_view/test-map.js +3 -0
  87. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  88. data/spec/spec.opts +6 -0
  89. data/spec/spec_helper.rb +26 -0
  90. data/utils/remap.rb +27 -0
  91. data/utils/subset.rb +30 -0
  92. metadata +200 -0
@@ -0,0 +1,138 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe CouchRest::Design do
4
+
5
+ describe "defining a view" do
6
+ it "should add a view to the design doc" do
7
+ @des = CouchRest::Design.new
8
+ method = @des.view_by :name
9
+ method.should == "by_name"
10
+ @des["views"]["by_name"].should_not be_nil
11
+ end
12
+ end
13
+
14
+ describe "with an unsaved view" do
15
+ before(:each) do
16
+ @des = CouchRest::Design.new
17
+ @des.view_by :name
18
+ end
19
+ it "should accept a name" do
20
+ @des.name = "mytest"
21
+ @des.name.should == "mytest"
22
+ end
23
+ it "should not save on view definition" do
24
+ @des.rev.should be_nil
25
+ end
26
+ it "should freak out on view access" do
27
+ lambda{@des.view :by_name}.should raise_error
28
+ end
29
+ end
30
+
31
+ describe "saving" do
32
+ before(:each) do
33
+ @des = CouchRest::Design.new
34
+ @des.view_by :name
35
+ @des.database = reset_test_db!
36
+ end
37
+ it "should fail without a name" do
38
+ lambda{@des.save}.should raise_error(ArgumentError)
39
+ end
40
+ it "should work with a name" do
41
+ @des.name = "myview"
42
+ @des.save
43
+ end
44
+ end
45
+
46
+ describe "when it's saved" do
47
+ before(:each) do
48
+ @db = reset_test_db!
49
+ @db.bulk_save([{"name" => "x"},{"name" => "y"}])
50
+ @des = CouchRest::Design.new
51
+ @des.database = @db
52
+ @des.view_by :name
53
+ end
54
+ it "should by queryable when it's saved" do
55
+ @des.name = "mydesign"
56
+ @des.save
57
+ res = @des.view :by_name
58
+ res["rows"][0]["key"].should == "x"
59
+ end
60
+ it "should be queryable on specified database" do
61
+ @des.name = "mydesign"
62
+ @des.save
63
+ @des.database = nil
64
+ res = @des.view_on @db, :by_name
65
+ res["rows"][0]["key"].should == "x"
66
+ end
67
+ end
68
+
69
+ describe "from a saved document" do
70
+ before(:each) do
71
+ @db = reset_test_db!
72
+ @db.save_doc({
73
+ "_id" => "_design/test",
74
+ "views" => {
75
+ "by_name" => {
76
+ "map" => "function(doc){if (doc.name) emit(doc.name, null)}"
77
+ }
78
+ }
79
+ })
80
+ @db.bulk_save([{"name" => "a"},{"name" => "b"}])
81
+ @des = @db.get "_design/test"
82
+ end
83
+ it "should be a Design" do
84
+ @des.should be_an_instance_of(CouchRest::Design)
85
+ end
86
+ it "should have a modifiable name" do
87
+ @des.name.should == "test"
88
+ @des.name = "supertest"
89
+ @des.id.should == "_design/supertest"
90
+ end
91
+ it "should by queryable" do
92
+ res = @des.view :by_name
93
+ res["rows"][0]["key"].should == "a"
94
+ end
95
+ end
96
+
97
+ describe "a view with default options" do
98
+ before(:all) do
99
+ @db = reset_test_db!
100
+ @des = CouchRest::Design.new
101
+ @des.name = "test"
102
+ @des.view_by :name, :descending => true
103
+ @des.database = @db
104
+ @des.save
105
+ @db.bulk_save([{"name" => "a"},{"name" => "z"}])
106
+ end
107
+ it "should save them" do
108
+ @d2 = @db.get(@des.id)
109
+ @d2["views"]["by_name"]["couchrest-defaults"].should == {"descending"=>true}
110
+ end
111
+ it "should use them" do
112
+ res = @des.view :by_name
113
+ res["rows"].first["key"].should == "z"
114
+ end
115
+ it "should override them" do
116
+ res = @des.view :by_name, :descending => false
117
+ res["rows"].first["key"].should == "a"
118
+ end
119
+ end
120
+
121
+ describe "a view with multiple keys" do
122
+ before(:all) do
123
+ @db = reset_test_db!
124
+ @des = CouchRest::Design.new
125
+ @des.name = "test"
126
+ @des.view_by :name, :age
127
+ @des.database = @db
128
+ @des.save
129
+ @db.bulk_save([{"name" => "a", "age" => 2},
130
+ {"name" => "a", "age" => 4},{"name" => "z", "age" => 9}])
131
+ end
132
+ it "should work" do
133
+ res = @des.view :by_name_and_age
134
+ res["rows"].first["key"].should == ["a",2]
135
+ end
136
+ end
137
+
138
+ end
@@ -0,0 +1,267 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ class Video < CouchRest::Document; end
4
+
5
+ describe CouchRest::Document do
6
+
7
+ before(:all) do
8
+ @couch = CouchRest.new
9
+ @db = @couch.database!(TESTDB)
10
+ end
11
+
12
+ describe "[]=" do
13
+ before(:each) do
14
+ @doc = CouchRest::Document.new
15
+ end
16
+ it "should work" do
17
+ @doc["enamel"].should == nil
18
+ @doc["enamel"] = "Strong"
19
+ @doc["enamel"].should == "Strong"
20
+ end
21
+ it "[]= should convert to string" do
22
+ @doc["enamel"].should == nil
23
+ @doc[:enamel] = "Strong"
24
+ @doc["enamel"].should == "Strong"
25
+ end
26
+ it "should read as a string" do
27
+ @doc[:enamel] = "Strong"
28
+ @doc[:enamel].should == "Strong"
29
+ end
30
+ end
31
+
32
+ describe "default database" do
33
+ before(:each) do
34
+ Video.use_database nil
35
+ end
36
+ it "should be set using use_database on the model" do
37
+ Video.new.database.should be_nil
38
+ Video.use_database @db
39
+ Video.new.database.should == @db
40
+ Video.use_database nil
41
+ end
42
+
43
+ it "should be overwritten by instance" do
44
+ db = @couch.database('test')
45
+ article = Video.new
46
+ article.database.should be_nil
47
+ article.database = db
48
+ article.database.should_not be_nil
49
+ article.database.should == db
50
+ end
51
+ end
52
+
53
+ describe "new" do
54
+ before(:each) do
55
+ @doc = CouchRest::Document.new("key" => [1,2,3], :more => "values")
56
+ end
57
+ it "should create itself from a Hash" do
58
+ @doc["key"].should == [1,2,3]
59
+ @doc["more"].should == "values"
60
+ end
61
+ it "should not have rev and id" do
62
+ @doc.rev.should be_nil
63
+ @doc.id.should be_nil
64
+ end
65
+
66
+ it "should freak out when saving without a database" do
67
+ lambda{@doc.save}.should raise_error(ArgumentError)
68
+ end
69
+
70
+ end
71
+
72
+ # move to database spec
73
+ describe "saving using a database" do
74
+ before(:all) do
75
+ @doc = CouchRest::Document.new("key" => [1,2,3], :more => "values")
76
+ @db = reset_test_db!
77
+ @resp = @db.save_doc(@doc)
78
+ end
79
+ it "should apply the database" do
80
+ @doc.database.should == @db
81
+ end
82
+ it "should get id and rev" do
83
+ @doc.id.should == @resp["id"]
84
+ @doc.rev.should == @resp["rev"]
85
+ end
86
+ end
87
+
88
+ describe "bulk saving" do
89
+ before :all do
90
+ @db = reset_test_db!
91
+ end
92
+
93
+ it "should use the document bulk save cache" do
94
+ doc = CouchRest::Document.new({"_id" => "bulkdoc", "val" => 3})
95
+ doc.database = @db
96
+ doc.save(true)
97
+ lambda { doc.database.get(doc["_id"]) }.should raise_error(RestClient::ResourceNotFound)
98
+ doc.database.bulk_save
99
+ doc.database.get(doc["_id"])["val"].should == doc["val"]
100
+ end
101
+ end
102
+
103
+ describe "getting from a database" do
104
+ before(:all) do
105
+ @db = reset_test_db!
106
+ @resp = @db.save_doc({
107
+ "key" => "value"
108
+ })
109
+ @doc = @db.get @resp['id']
110
+ end
111
+ it "should return a document" do
112
+ @doc.should be_an_instance_of(CouchRest::Document)
113
+ end
114
+ it "should have a database" do
115
+ @doc.database.should == @db
116
+ end
117
+ it "should be saveable and resavable" do
118
+ @doc["more"] = "keys"
119
+ @doc.save
120
+ @db.get(@resp['id'])["more"].should == "keys"
121
+ @doc["more"] = "these keys"
122
+ @doc.save
123
+ @db.get(@resp['id'])["more"].should == "these keys"
124
+ end
125
+ end
126
+
127
+ describe "destroying a document from a db" do
128
+ before(:all) do
129
+ @db = reset_test_db!
130
+ @resp = @db.save_doc({
131
+ "key" => "value"
132
+ })
133
+ @doc = @db.get @resp['id']
134
+ end
135
+ it "should make it disappear" do
136
+ @doc.destroy
137
+ lambda{@db.get @resp['id']}.should raise_error
138
+ end
139
+ it "should error when there's no db" do
140
+ @doc = CouchRest::Document.new("key" => [1,2,3], :more => "values")
141
+ lambda{@doc.destroy}.should raise_error(ArgumentError)
142
+ end
143
+ end
144
+
145
+
146
+ describe "destroying a document from a db using bulk save" do
147
+ before(:all) do
148
+ @db = reset_test_db!
149
+ @resp = @db.save_doc({
150
+ "key" => "value"
151
+ })
152
+ @doc = @db.get @resp['id']
153
+ end
154
+ it "should defer actual deletion" do
155
+ @doc.destroy(true)
156
+ @doc['_id'].should == nil
157
+ @doc['_rev'].should == nil
158
+ lambda{@db.get @resp['id']}.should_not raise_error
159
+ @db.bulk_save
160
+ lambda{@db.get @resp['id']}.should raise_error
161
+ end
162
+ end
163
+
164
+ describe "copying a document" do
165
+ before :each do
166
+ @db = reset_test_db!
167
+ @resp = @db.save_doc({'key' => 'value'})
168
+ @docid = 'new-location'
169
+ @doc = @db.get(@resp['id'])
170
+ end
171
+ describe "to a new location" do
172
+ it "should work" do
173
+ @doc.copy @docid
174
+ newdoc = @db.get(@docid)
175
+ newdoc['key'].should == 'value'
176
+ end
177
+ it "should fail without a database" do
178
+ lambda{CouchRest::Document.new({"not"=>"a real doc"}).copy}.should raise_error(ArgumentError)
179
+ end
180
+ end
181
+ describe "to an existing location" do
182
+ before :each do
183
+ @db.save_doc({'_id' => @docid, 'will-exist' => 'here'})
184
+ end
185
+ it "should fail without a rev" do
186
+ lambda{@doc.copy @docid}.should raise_error(RestClient::RequestFailed)
187
+ end
188
+ it "should succeed with a rev" do
189
+ @to_be_overwritten = @db.get(@docid)
190
+ @doc.copy "#{@docid}?rev=#{@to_be_overwritten['_rev']}"
191
+ newdoc = @db.get(@docid)
192
+ newdoc['key'].should == 'value'
193
+ end
194
+ it "should succeed given the doc to overwrite" do
195
+ @to_be_overwritten = @db.get(@docid)
196
+ @doc.copy @to_be_overwritten
197
+ newdoc = @db.get(@docid)
198
+ newdoc['key'].should == 'value'
199
+ end
200
+ end
201
+ end
202
+ end
203
+
204
+ describe "dealing with attachments" do
205
+ before do
206
+ @db = reset_test_db!
207
+ @attach = "<html><head><title>My Doc</title></head><body><p>Has words.</p></body></html>"
208
+ response = @db.save_doc({'key' => 'value'})
209
+ @doc = @db.get(response['id'])
210
+ end
211
+
212
+ def append_attachment(name='test.html', attach=@attach)
213
+ @doc['_attachments'] ||= {}
214
+ @doc['_attachments'][name] = {
215
+ 'type' => 'text/html',
216
+ 'data' => attach
217
+ }
218
+ @doc.save
219
+ @rev = @doc['_rev']
220
+ end
221
+
222
+ describe "PUTing an attachment directly to the doc" do
223
+ before do
224
+ @doc.put_attachment('test.html', @attach)
225
+ end
226
+
227
+ it "is there" do
228
+ @db.fetch_attachment(@doc, 'test.html').should == @attach
229
+ end
230
+
231
+ it "updates the revision" do
232
+ @doc['_rev'].should_not == @rev
233
+ end
234
+
235
+ it "updates attachments" do
236
+ @attach2 = "<html><head><title>My Doc</title></head><body><p>Is Different.</p></body></html>"
237
+ @doc.put_attachment('test.html', @attach2)
238
+ @db.fetch_attachment(@doc, 'test.html').should == @attach2
239
+ end
240
+ end
241
+
242
+ describe "fetching an attachment from a doc directly" do
243
+ before do
244
+ append_attachment
245
+ end
246
+
247
+ it "pulls the attachment" do
248
+ @doc.fetch_attachment('test.html').should == @attach
249
+ end
250
+ end
251
+
252
+ describe "deleting an attachment from a doc directly" do
253
+ before do
254
+ append_attachment
255
+ @doc.delete_attachment('test.html')
256
+ end
257
+
258
+ it "removes it" do
259
+ lambda { @db.fetch_attachment(@doc, 'test.html').should }.should raise_error(RestClient::ResourceNotFound)
260
+ end
261
+
262
+ it "updates the revision" do
263
+ @doc['_rev'].should_not == @rev
264
+ end
265
+ end
266
+
267
+ end
@@ -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
@@ -0,0 +1,122 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+
3
+ describe CouchRest::Pager 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
+ @pager = CouchRest::Pager.new(@db)
10
+ end
11
+
12
+ after(:all) do
13
+ begin
14
+ @db.delete!
15
+ rescue RestClient::Request::RequestFailed
16
+ end
17
+ end
18
+
19
+ it "should store the db" do
20
+ @pager.db.should == @db
21
+ end
22
+
23
+ describe "paging all docs" do
24
+ before(:all) do
25
+ @docs = []
26
+ 100.times do |i|
27
+ @docs << ({:number => (i % 10)})
28
+ end
29
+ @db.bulk_save(@docs)
30
+ end
31
+ it "should yield total_docs / limit times" do
32
+ n = 0
33
+ @pager.all_docs(10) do |doc|
34
+ n += 1
35
+ end
36
+ n.should == 10
37
+ end
38
+ it "should yield each docrow group without duplicate docs" do
39
+ docids = {}
40
+ @pager.all_docs(10) do |docrows|
41
+ docrows.each do |row|
42
+ docids[row['id']].should be_nil
43
+ docids[row['id']] = true
44
+ end
45
+ end
46
+ docids.keys.length.should == 100
47
+ end
48
+ it "should yield each docrow group" do
49
+ @pager.all_docs(10) do |docrows|
50
+ doc = @db.get(docrows[0]['id'])
51
+ doc['number'].class.should == Fixnum
52
+ end
53
+ end
54
+ end
55
+
56
+ describe "Pager with a view and docs" do
57
+ before(:all) do
58
+ @docs = []
59
+ 100.times do |i|
60
+ @docs << ({:number => (i % 10)})
61
+ end
62
+ @db.bulk_save(@docs)
63
+ @db.save_doc({
64
+ '_id' => '_design/magic',
65
+ 'views' => {
66
+ 'number' => {
67
+ 'map' => 'function(doc){emit(doc.number,null)}'
68
+ }
69
+ }
70
+ })
71
+ end
72
+
73
+ it "should have docs" do
74
+ @docs.length.should == 100
75
+ @db.documents['rows'].length.should == 101
76
+ end
77
+
78
+ it "should have a view" do
79
+ @db.view('magic/number', :limit => 10)['rows'][0]['key'].should == 0
80
+ end
81
+
82
+ it "should yield once per key" do
83
+ results = {}
84
+ @pager.key_reduce('magic/number', 20) do |k,vs|
85
+ results[k] = vs.length
86
+ end
87
+ results[0].should == 10
88
+ results[3].should == 10
89
+ end
90
+
91
+ it "with a small step size should yield once per key" do
92
+ results = {}
93
+ @pager.key_reduce('magic/number', 7) do |k,vs|
94
+ results[k] = vs.length
95
+ end
96
+ results[0].should == 10
97
+ results[3].should == 10
98
+ results[9].should == 10
99
+ end
100
+ it "with a large step size should yield once per key" do
101
+ results = {}
102
+ @pager.key_reduce('magic/number', 1000) do |k,vs|
103
+ results[k] = vs.length
104
+ end
105
+ results[0].should == 10
106
+ results[3].should == 10
107
+ results[9].should == 10
108
+ end
109
+ it "with a begin and end should only yield in the range (and leave out the lastkey)" do
110
+ results = {}
111
+ @pager.key_reduce('magic/number', 1000, 4, 7) do |k,vs|
112
+ results[k] = vs.length
113
+ end
114
+ results[0].should be_nil
115
+ results[4].should == 10
116
+ results[6].should == 10
117
+ results[7].should be_nil
118
+ results[8].should be_nil
119
+ results[9].should be_nil
120
+ end
121
+ end
122
+ end