answer-factory 0.0.1

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 (38) hide show
  1. data/LICENSE.txt +21 -0
  2. data/Rakefile +29 -0
  3. data/Thorfile +79 -0
  4. data/VERSION +1 -0
  5. data/_spikes/old_vs_new_dominated_by?.rb +45 -0
  6. data/config/database.yml +9 -0
  7. data/lib/answer-factory.rb +14 -0
  8. data/lib/answers/answer.rb +126 -0
  9. data/lib/answers/batch.rb +49 -0
  10. data/lib/factories/factory.rb +53 -0
  11. data/lib/factories/workstation.rb +33 -0
  12. data/lib/operators/basic_operators.rb +240 -0
  13. data/lib/operators/evaluators.rb +113 -0
  14. data/lib/operators/samplers_and_selectors.rb +131 -0
  15. data/pkg/nudgegp-0.0.1.gem +0 -0
  16. data/readme.md +29 -0
  17. data/spec/answer_spec.rb +412 -0
  18. data/spec/batch_spec.rb +98 -0
  19. data/spec/config_spec.rb +94 -0
  20. data/spec/factories/factory_spec.rb +86 -0
  21. data/spec/factories/workstation_spec.rb +139 -0
  22. data/spec/operators/any_one_sampler_spec.rb +39 -0
  23. data/spec/operators/dominated_quantile_spec.rb +111 -0
  24. data/spec/operators/duplicate_genomes_spec.rb +35 -0
  25. data/spec/operators/evaluators/program_point_evaluator_spec.rb +43 -0
  26. data/spec/operators/evaluators/test_case_evaluator_spec.rb +129 -0
  27. data/spec/operators/infrastructure_spec.rb +45 -0
  28. data/spec/operators/most_dominated_subset_spec.rb +47 -0
  29. data/spec/operators/nondominated_subset_spec.rb +103 -0
  30. data/spec/operators/pointCrossover_spec.rb +60 -0
  31. data/spec/operators/pointDeletion_spec.rb +62 -0
  32. data/spec/operators/pointMutation_spec.rb +77 -0
  33. data/spec/operators/random_guess_spec.rb +77 -0
  34. data/spec/operators/resample_and_clone_spec.rb +60 -0
  35. data/spec/operators/resample_values_spec.rb +135 -0
  36. data/spec/operators/uniformBackboneCrossover_spec.rb +67 -0
  37. data/spec/spec_helper.rb +14 -0
  38. metadata +201 -0
Binary file
@@ -0,0 +1,29 @@
1
+ # Pragmatic Genetic Programming with Nudge
2
+
3
+ ## Caveat
4
+
5
+ This particular project is undergoing a lot of serious surgery right now.
6
+
7
+ ## What it does
8
+
9
+ Well, it's complicated, and in a state of dynamic change right this second.
10
+
11
+ Let's just say it helps _regular people_ build, run and manage technical experiments that use genetic programming. There's more... but that's the bottom line.
12
+
13
+ ## Getting started
14
+
15
+ You'll need a working (and running) installation of CouchDB.
16
+
17
+ Then:
18
+
19
+ gem install nudge
20
+ gem install thor
21
+ gem install couch-rest
22
+ gem install sinatra
23
+
24
+ and for running specs (which you _should be doing_):
25
+
26
+ gem install rspec
27
+ gem install sinatra
28
+ gem install fakeweb
29
+
@@ -0,0 +1,412 @@
1
+ #encoding: utf-8
2
+ require File.join(File.dirname(__FILE__), "spec_helper")
3
+
4
+
5
+ describe "Answer" do
6
+
7
+ describe "initialization" do
8
+ it "should accept a string OR a NudgeProgram as an initialization parameter" do
9
+ lambda{Answer.new("any string")}.should_not raise_error
10
+ lambda{Answer.new(NudgeProgram.new("block {}"))}.should_not raise_error
11
+ end
12
+
13
+ it "should validate the initial parameter and make sure it's only one of the accepted classes" do
14
+ lambda{Answer.new(8812.9)}.should raise_error(ArgumentError)
15
+ lambda{Answer.new(Time.now)}.should raise_error(ArgumentError)
16
+ lambda{Answer.new}.should raise_error(ArgumentError)
17
+ end
18
+
19
+ it "should set the #draft_blueprint attribute from the string, if present" do
20
+ Answer.new("do x").draft_blueprint.should == "do x"
21
+ Answer.new("some «random»\njunk").draft_blueprint.should == "some «random»\njunk"
22
+ end
23
+
24
+ it "should set the #draft_blueprint attribute to the blueprint of the NudgeProgram, if present" do
25
+ Answer.new(NudgeProgram.new("value «foo»\n«foo» bar")).draft_blueprint.should ==
26
+ "value «foo» \n«foo» bar"
27
+ end
28
+
29
+ it "should set the #program attribute to a new NudgeProgram based on init'n string, if present" do
30
+ np = Answer.new("do x").program
31
+ np.should be_a_kind_of(NudgeProgram)
32
+ np.blueprint.should == "do x"
33
+ end
34
+
35
+ it "should set the #program attribute even if the init'n string is unparseable" do
36
+ np = Answer.new("not a program")
37
+ np.program.should be_a_kind_of(NudgeProgram)
38
+ np.draft_blueprint.should == "not a program"
39
+ np.program.linked_code.should be_a_kind_of(NilPoint)
40
+ np.program.blueprint.should == ""
41
+ end
42
+
43
+ it "should set the #program attribute to the init'n NudgeProgram, if present" do
44
+ prog = NudgeProgram.new("block {ref a ref b}")
45
+ na = Answer.new(prog)
46
+ na.program.should == prog
47
+ na.draft_blueprint.should == prog.blueprint
48
+ end
49
+
50
+ it "should respond to #blueprint by returning self.program.blueprint" do
51
+ prog = NudgeProgram.new("block {ref a do x}")
52
+ na = Answer.new(prog)
53
+ prog.should_receive(:blueprint).and_return("hi there")
54
+ na.blueprint.should == "hi there"
55
+ end
56
+
57
+ it "should have a #scores hash, which is empty" do
58
+ Answer.new("foo").scores.should == {}
59
+ end
60
+
61
+ it "should have a #timestamp, which is when (wall clock time) it was made" do
62
+ tn = Time.now
63
+ Time.should_receive(:now).and_return(tn)
64
+ Answer.new("bar").timestamp.should == tn
65
+ end
66
+
67
+ it "should have a [named] #progress attribute, defaulting to zero" do
68
+ Answer.new("baz").progress.should == 0
69
+ Answer.new("baz", progress:12).progress.should == 12
70
+ end
71
+
72
+ it "should have an #ancestors Array, defaulting to []" do
73
+ Answer.new("quux").ancestors.should == []
74
+ Answer.new("quux", ancestors:[1,2,3]).ancestors.should == [1,2,3]
75
+ end
76
+ end
77
+
78
+
79
+ describe "serialization" do
80
+ describe "writing" do
81
+ before(:each) do
82
+ @a1 = Answer.new("block {do a}")
83
+ end
84
+
85
+ it "should contain the blueprint" do
86
+ @a1.data['blueprint'].should == @a1.blueprint
87
+ end
88
+
89
+ it "should contain the tags" do
90
+ @a1.data['tags'].should == @a1.tags
91
+ end
92
+
93
+
94
+ it "should contain the scores" do
95
+ @a1.data['scores'].should == @a1.scores
96
+ end
97
+
98
+ it "should contain the timestamp" do
99
+ @a1.data['timestamp'].should == @a1.timestamp
100
+ end
101
+ end
102
+
103
+ describe "reading" do
104
+
105
+ end
106
+ end
107
+
108
+
109
+ describe "#parses?" do
110
+ it "should respond to #parses? with an answer from its NudgeProgram" do
111
+ Answer.new("not good word").parses?.should == false
112
+ Answer.new("block {do nice_nudge}").parses?.should == true
113
+ end
114
+ end
115
+
116
+
117
+ describe "scores" do
118
+ before(:each) do
119
+ @answer1 = Answer.new("do a")
120
+ @answer1.scores[:error] = 81.9
121
+ @answer1.scores[:a_1] = 2200
122
+
123
+ @answer2 = Answer.new("some other crap")
124
+ @answer2.scores[:error] = 7
125
+ @answer2.scores[:a_1] = 1200
126
+ end
127
+
128
+ it "should require symbols as keys" do
129
+ lambda{@answer2.scores["foo"]}.should raise_error(ArgumentError)
130
+ lambda{@answer2.scores[:bar]}.should_not raise_error(ArgumentError)
131
+ end
132
+
133
+ describe "#known_criteria" do
134
+ it "#known_criteria should return a sorted list of the keys of the scores hash" do
135
+ @answer1.known_criteria.should == [:a_1, :error]
136
+ Answer.new("x").known_criteria.should == []
137
+ end
138
+ end
139
+
140
+ describe "#score_vector" do
141
+ it "#score_vector should return an Array of #scores" do
142
+ a1s = @answer1.score_vector
143
+ a1s.should be_a_kind_of(Array)
144
+ a1s.sort.should == [2200, 81.9].sort # ignoring returned orderuntil next spec
145
+ end
146
+
147
+ it "should use the argument to order the scores in the result" do
148
+ @answer1.score_vector([:a_1]).should == [2200]
149
+ @answer1.score_vector([:error, :a_1]).should == [81.9, 2200]
150
+ end
151
+
152
+ it "the scores should be by default in #known_criteria (alphabetical) order" do
153
+ @answer1.scores[:bobby] = -121.1
154
+ a1s = @answer1.score_vector
155
+ a1s[0].should == @answer1.scores[:a_1]
156
+ a1s[1].should == @answer1.scores[:bobby]
157
+ a1s[2].should == @answer1.scores[:error]
158
+ end
159
+
160
+ it "should return nil for any nonexistent score item" do
161
+ @answer1.score_vector([:nonexisto, :a_1]).should == [nil, 2200]
162
+ end
163
+ end
164
+
165
+ describe "#dominated_by?" do
166
+
167
+ it "should return false when the compared Answer doesn't have the same scoring_criteria" do
168
+ @answer1.scores = {a:10}
169
+ @answer2.scores = { b:99}
170
+ @answer1.dominated_by?(@answer2).should == false
171
+
172
+ @answer1.scores = {a:10, b:10}
173
+ @answer2.scores = { b:99, c:10}
174
+ @answer1.dominated_by?(@answer2).should == false
175
+
176
+ @answer1.scores = {a:10, b:10, c:10, d:10}
177
+ @answer2.scores = { b:99, c:10, d:1}
178
+ @answer1.dominated_by?(@answer2).should == false
179
+
180
+ @answer1.scores = {x1:1, x2:2, x3:3}
181
+ @answer2.scores = { x2:0, x4:12}
182
+ @answer1.dominated_by?(@answer2).should == false
183
+ @answer2.dominated_by?(@answer1).should == false
184
+ end
185
+
186
+ it "should use the comparison template to check whether the scoring criteria are the same" do
187
+ @answer1.scores = {a:10, b:10, c:10, d:10}
188
+ @answer2.scores = { b:99, c:10, d:1}
189
+ @answer1.dominated_by?(@answer2).should == false
190
+
191
+ @answer1.scores = {a:10, b:10, c:10, d:10}
192
+ @answer2.scores = { b:99, c:10, d:1}
193
+ @answer1.dominated_by?(@answer2, [:d]).should == true
194
+ @answer2.dominated_by?(@answer1, [:b,:c]).should == true
195
+ end
196
+
197
+ it "should return true if another Answer has one score better and all the rest the same" do
198
+ @answer1.scores = {:a => 10, :b =>10}
199
+ @answer2.scores = {:a => 10, :b => 9}
200
+ @answer1.dominated_by?(@answer2).should == true
201
+ end
202
+
203
+ it "should return false if another Answer has all scores worse" do
204
+ @answer1.scores = {:a => 10, :b =>10}
205
+ @answer2.scores = {:a=> 1000, :b => 1000}
206
+ @answer1.dominated_by?(@answer2).should == false
207
+ end
208
+
209
+ it "should return false if another Answer has all scores the same and one worse" do
210
+ @answer1.scores = {:a => 10, :b =>10}
211
+ @answer2.scores = {:a=> 10, :b => 1000}
212
+ @answer1.dominated_by?(@answer2).should == false
213
+ end
214
+
215
+ it "should return false if another Answer has some scores better and some worse" do
216
+ @answer1.scores = {:a => 10, :b =>10}
217
+ @answer2.scores = {:a=> 1000, :b => 1}
218
+ @answer1.dominated_by?(@answer2).should == false
219
+ end
220
+
221
+ it "should return true if another Answer has all scores better" do
222
+ @answer1.scores = {:a => 10, :b =>10}
223
+ @answer2.scores = {:a=> 1, :b => 1}
224
+ @answer1.dominated_by?(@answer2).should == true
225
+ end
226
+
227
+ it "should return false if another Answer has all scores identical" do
228
+ @answer1.scores = {:a => 10, :b =>10}
229
+ @answer2.scores = {:a=> 10, :b => 10}
230
+ @answer1.dominated_by?(@answer2).should == false
231
+ end
232
+
233
+ it "should use known_criteria if no comparison template is passed in" do
234
+ @answer1.scores = {:a => 10, :b =>10, :c => 100}
235
+ @answer2.scores = {:a=> 1, :b => 1, :c => 99}
236
+ @answer1.should_receive(:known_criteria).at_least(1).times.and_return([:a,:b,:c])
237
+ @answer1.dominated_by?(@answer2).should == true
238
+ end
239
+
240
+ it "should use a comparison template if one is passed in" do
241
+ @answer1.scores = {:a => 10, :b =>10, :c => 10}
242
+ @answer2.scores = {:a=> 1, :b => 200, :c => 10}
243
+ @answer1.dominated_by?(@answer2, [:a]).should == true
244
+ @answer1.dominated_by?(@answer2, [:b]).should == false
245
+ @answer1.dominated_by?(@answer2, [:c]).should == false
246
+
247
+ @answer2.dominated_by?(@answer1, [:a]).should == false
248
+ @answer2.dominated_by?(@answer1, [:b]).should == true
249
+ @answer2.dominated_by?(@answer1, [:c]).should == false
250
+
251
+ @answer1.dominated_by?(@answer2, [:a, :c]).should == true
252
+ @answer2.dominated_by?(@answer1, [:b, :c]).should == true
253
+ end
254
+
255
+
256
+ it "should be conservative when comparing against a missing score" do
257
+ @answer1.scores = {:a => 10, :b =>nil, :c => 100}
258
+ @answer2.scores = {:a=> 1, :b => 12, :c => 99}
259
+ lambda{@answer1.dominated_by?(@answer2)}.should_not raise_error(NoMethodError)
260
+ @answer1.dominated_by?(@answer2).should == false
261
+ end
262
+ end
263
+ end
264
+
265
+
266
+ describe "#points method" do
267
+ it "should return self.program.points" do
268
+ Answer.new("").points.should == 0
269
+ Answer.new("block {do a do b}").points.should == 3
270
+ Answer.new("block {value «foo» block {value «foo»}}").points.should == 4
271
+ end
272
+ end
273
+
274
+
275
+ describe "replace_point_or_clone" do
276
+ before(:each) do
277
+ @simple = Answer.new("block { do a do b do c do d do e}")
278
+ @complicated = Answer.new("block { block {do a} do b block { block {do c} block {do d}} do e}")
279
+ @str_insert = "ref x"
280
+ @pt_insert = ReferencePoint.new("y")
281
+ end
282
+
283
+ it "should return a NudgeProgram (not an Answer)" do
284
+ @simple.replace_point_or_clone(2,@str_insert).should be_a_kind_of(NudgeProgram)
285
+ end
286
+
287
+ it "should return a clone of the original NudgeProgram if the position param is out of bounds" do
288
+ new_guy = @simple.replace_point_or_clone(-11,"do x")
289
+ new_guy.blueprint.should == @simple.blueprint
290
+ new_guy.object_id.should_not == @simple.program.object_id
291
+
292
+ new_guy = @simple.replace_point_or_clone(1911,"do x")
293
+ new_guy.blueprint.should == @simple.blueprint
294
+ end
295
+
296
+ it "should raise an ArgumentError if passed an unparseable replacement" do
297
+ lambda{@simple.replace_point_or_clone(3,"some crap")}.should raise_error(ArgumentError)
298
+ lambda{@simple.replace_point_or_clone(3,"block {}")}.should_not raise_error
299
+ end
300
+
301
+ it "should produce a new parsable NudgeProgram with a new blueprint (as expected)" do
302
+ @simple.replace_point_or_clone(2,@str_insert).blueprint.should ==
303
+ "block {\n ref x\n do b\n do c\n do d\n do e}"
304
+ @complicated.replace_point_or_clone(2,@str_insert).blueprint.should ==
305
+ "block {\n ref x\n do b\n block {\n block {\n do c}\n block {\n do d}}\n do e}"
306
+ end
307
+
308
+ it "should replace the expected specific point (and any subpoints and footnotes it has)" do
309
+ a1 = Answer.new("block {value «code» value «int»}\n«code» value «float»\n«float» 1.1\n«int» 12")
310
+ a1.replace_point_or_clone(2,"do x").blueprint.should ==
311
+ "block {\n do x\n value «int»} \n«int» 12"
312
+ end
313
+
314
+ it "should insert the new footnotes it needs in the right place in the footnote_section" do
315
+ a1 = Answer.new("block {value «code» value «int»}\n«code» value «float»\n«float» 1.1\n«int» 12")
316
+ a1.replace_point_or_clone(3,"value «foo»\n«foo» bar").blueprint.should ==
317
+ "block {\n value «code»\n value «foo»} \n«code» value «float»\n«float» 1.1\n«foo» bar"
318
+ end
319
+
320
+ it "should not affect extra (unused) footnotes" do
321
+ a1 = Answer.new("block {value «int»}\n«float» 1.1\n«int» 12")
322
+ a1.replace_point_or_clone(2, "do x").blueprint.should ==
323
+ "block {\n do x} \n«float» 1.1"
324
+ end
325
+
326
+ it "should work when putting in something that lacks a footnote" do
327
+ @simple.replace_point_or_clone(3, "value «quux»").blueprint.should ==
328
+ "block {\n do a\n value «quux»\n do c\n do d\n do e} \n«quux»"
329
+ end
330
+
331
+ it "should act as expected when replacing the root" do
332
+ @simple.replace_point_or_clone(1, "value «quux»").blueprint.should ==
333
+ "value «quux» \n«quux»"
334
+ end
335
+ end
336
+
337
+
338
+
339
+ describe "delete_point_or_clone" do
340
+ before(:each) do
341
+ @simple = Answer.new("block { do a do b do c do d do e}")
342
+ @complicated = Answer.new("block { block {do a} do b block { block {do c} block {do d}} do e}")
343
+ end
344
+
345
+
346
+ it "should return a NudgeProgram (not an Answer)" do
347
+ @simple.delete_point_or_clone(2).should be_a_kind_of(NudgeProgram)
348
+ end
349
+
350
+ it "should return a clone of the calling Answer's program if the point is out of bounds" do
351
+ new_guy = @simple.delete_point_or_clone(-11)
352
+ new_guy.blueprint.should == @simple.blueprint
353
+ new_guy.object_id.should_not == @simple.program.object_id
354
+
355
+ new_guy = @simple.delete_point_or_clone(1911)
356
+ new_guy.blueprint.should == @simple.blueprint
357
+ end
358
+
359
+ it "should return a NudgeProgram with at least one fewer program points (if the range is OK)" do
360
+ @complicated.delete_point_or_clone(2).points.should < @complicated.points
361
+ end
362
+
363
+ it "should delete the expected specific point (and any subpoints it has)" do
364
+ @complicated.delete_point_or_clone(5).blueprint.should ==
365
+ "block {\n block {\n do a}\n do b\n do e}"
366
+ end
367
+
368
+ it "should return NudgeProgram.new('block {}') when the entire program is deleted" do
369
+ @complicated.delete_point_or_clone(1).blueprint.should == "block {}"
370
+ end
371
+
372
+ it "should delete the footnotes associated with a point it deletes" do
373
+ a1 = Answer.new("block {value «a» value «b» value «c»}\n«a» 1\n«b» 2\n«c» 3")
374
+ a1.delete_point_or_clone(3).blueprint.should ==
375
+ "block {\n value «a»\n value «c»} \n«a» 1\n«c» 3"
376
+ end
377
+
378
+ it "should not delete extra footnotes" do
379
+ a1 = Answer.new("block {value «a»}\n«a» 1\n«b» 2\n«c» 3")
380
+ a1.delete_point_or_clone(2).blueprint.should ==
381
+ "block {} \n«b» 2\n«c» 3"
382
+ end
383
+ end
384
+
385
+
386
+ describe "tags" do
387
+ it "should have a tags array" do
388
+ Answer.new("").tags.should be_a_kind_of(Set)
389
+ end
390
+
391
+ it "should be possible to set the tags upon initialization" do
392
+ Answer.new("do a", tags:[:here, :now]).tags.should == Set.new([:here, :now])
393
+ end
394
+
395
+ it "should have an #add_tag method" do
396
+ a1 = Answer.new("")
397
+ a1.add_tag(:foo)
398
+ a1.tags.should include(:foo)
399
+ end
400
+
401
+ it "should validate that tags being added are Symbols" do
402
+ lambda{Answer.new("").add_tag("foo")}.should raise_error(ArgumentError)
403
+ end
404
+
405
+ it "should have an #remove_tag method" do
406
+ a1 = Answer.new("", tags:[:a, :b])
407
+ a1.tags.should include(:a)
408
+ a1.remove_tag(:a)
409
+ a1.tags.should_not include(:a)
410
+ end
411
+ end
412
+ end
@@ -0,0 +1,98 @@
1
+ require File.join(File.dirname(__FILE__), "./spec_helper")
2
+
3
+ describe "Batches" do
4
+ it "should be a kind of Array" do
5
+ Batch.new.should be_a_kind_of(Array)
6
+ end
7
+
8
+ it "should raise an exception if a non-Answer is added after initialization" do
9
+ careful = Batch.[](Answer.new("block {}"), Answer.new("block {}"))
10
+ careful.length.should == 2
11
+
12
+ lambda{careful = Batch.[](12)}.should raise_error(ArgumentError)
13
+ lambda{careful = Batch.[](Answer.new("do int_add"))}.should_not raise_error(ArgumentError)
14
+
15
+ lambda{careful = Batch.[](Answer.new("do int_add"),12)}.should raise_error(ArgumentError)
16
+ lambda{careful = Batch.[](Answer.new("do int_add"),
17
+ Answer.new("do int_add"))}.should_not raise_error(ArgumentError)
18
+
19
+ lambda{careful = Batch.new(12)}.should raise_error(ArgumentError)
20
+ lambda{careful = Batch.new(Answer.new("do int_add"))}.should_not raise_error(ArgumentError)
21
+
22
+ lambda{careful[1] = 991}.should raise_error(ArgumentError)
23
+ lambda{careful[1] = Answer.new("do int_add")}.should_not raise_error(ArgumentError)
24
+
25
+ lambda{careful << false}.should raise_error(ArgumentError)
26
+ lambda{careful << Answer.new("do int_add")}.should_not raise_error(ArgumentError)
27
+ end
28
+
29
+
30
+ describe "database persistence" do
31
+ before(:each) do
32
+ FakeWeb.allow_net_connect = false
33
+ end
34
+
35
+ describe "bulk_save!" do
36
+ it "should have a #bulk_save method" do
37
+ Batch.new.should respond_to(:bulk_save!)
38
+ end
39
+
40
+ it "should validate a String as its argument" do
41
+ lambda{Batch.new.bulk_save!("some string")}.should_not raise_error(ArgumentError)
42
+ lambda{Batch.new.bulk_save!(8812)}.should raise_error(ArgumentError)
43
+ end
44
+
45
+
46
+ describe "writing" do
47
+ before(:each) do
48
+ @uri = "http://mycouch.db/boo"
49
+ @b1 = Batch.new
50
+ @a1 = Answer.new("do a")
51
+ @b1 << @a1
52
+ end
53
+
54
+ it "should create the database if it doesn't exist" do
55
+ FakeWeb.register_uri(:any, @uri, :body => "We are here!", :status => [200, "OK"])
56
+ CouchRest.stub(:database!).and_return(the_db = Object.new)
57
+ the_db.stub!(:bulk_save)
58
+ CouchRest.should_receive(:database!)
59
+ Batch.new.bulk_save!(@uri)
60
+ end
61
+
62
+ it "should bulk_save the Answers" do
63
+ CouchRest.stub(:database!).and_return(the_db = Object.new)
64
+ the_db.should_receive(:bulk_save)
65
+ @b1.bulk_save!(@uri)
66
+ end
67
+
68
+ describe "data" do
69
+ it "should return an array of its contents' #data" do
70
+ @b1.data.should == [ @a1.data ]
71
+ end
72
+ end
73
+ end
74
+
75
+ describe "reading" do
76
+ before(:each) do
77
+ @uri = "http://127.0.0.1/baz:5984"
78
+ end
79
+
80
+ it "create a new Batch" do
81
+ Batch.load_tagged_answers(@uri,"foo").should be_a_kind_of(Batch)
82
+ end
83
+
84
+ it "should access the database" do
85
+ CouchRest.should_receive(:database).with(@uri)
86
+ Batch.load_tagged_answers(@uri, "workstation_1")
87
+ end
88
+
89
+ it "should work"
90
+ end
91
+
92
+ end
93
+
94
+
95
+ it "should have a Batch.load_from_couch method that reads a bunch of Answers from the db"
96
+
97
+ end
98
+ end