veritas-do-adapter 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.document +5 -0
  2. data/.gemtest +0 -0
  3. data/.rvmrc +1 -0
  4. data/.travis.yml +11 -0
  5. data/Gemfile +46 -0
  6. data/Guardfile +18 -0
  7. data/LICENSE +20 -0
  8. data/README.md +21 -0
  9. data/Rakefile +27 -0
  10. data/TODO +0 -0
  11. data/config/flay.yml +3 -0
  12. data/config/flog.yml +2 -0
  13. data/config/roodi.yml +16 -0
  14. data/config/site.reek +96 -0
  15. data/config/yardstick.yml +2 -0
  16. data/lib/veritas/adapter/data_objects/statement.rb +107 -0
  17. data/lib/veritas/adapter/data_objects/version.rb +9 -0
  18. data/lib/veritas/adapter/data_objects.rb +55 -0
  19. data/lib/veritas/relation/gateway.rb +363 -0
  20. data/lib/veritas-do-adapter.rb +4 -0
  21. data/spec/rcov.opts +6 -0
  22. data/spec/shared/binary_relation_method_behaviour.rb +51 -0
  23. data/spec/shared/command_method_behavior.rb +7 -0
  24. data/spec/shared/each_method_behaviour.rb +15 -0
  25. data/spec/shared/idempotent_method_behaviour.rb +7 -0
  26. data/spec/shared/unary_relation_method_behaviour.rb +21 -0
  27. data/spec/spec.opts +3 -0
  28. data/spec/spec_helper.rb +28 -0
  29. data/spec/unit/veritas/adapter/data_objects/class_methods/new_spec.rb +15 -0
  30. data/spec/unit/veritas/adapter/data_objects/read_spec.rb +70 -0
  31. data/spec/unit/veritas/adapter/data_objects/statement/class_methods/new_spec.rb +28 -0
  32. data/spec/unit/veritas/adapter/data_objects/statement/each_spec.rb +63 -0
  33. data/spec/unit/veritas/adapter/data_objects/statement/to_s_spec.rb +53 -0
  34. data/spec/unit/veritas/relation/gateway/class_methods/new_spec.rb +16 -0
  35. data/spec/unit/veritas/relation/gateway/difference_spec.rb +17 -0
  36. data/spec/unit/veritas/relation/gateway/drop_spec.rb +21 -0
  37. data/spec/unit/veritas/relation/gateway/each_spec.rb +84 -0
  38. data/spec/unit/veritas/relation/gateway/extend_spec.rb +27 -0
  39. data/spec/unit/veritas/relation/gateway/intersect_spec.rb +17 -0
  40. data/spec/unit/veritas/relation/gateway/join_spec.rb +44 -0
  41. data/spec/unit/veritas/relation/gateway/materialize_spec.rb +27 -0
  42. data/spec/unit/veritas/relation/gateway/optimize_spec.rb +23 -0
  43. data/spec/unit/veritas/relation/gateway/product_spec.rb +17 -0
  44. data/spec/unit/veritas/relation/gateway/project_spec.rb +21 -0
  45. data/spec/unit/veritas/relation/gateway/remove_spec.rb +21 -0
  46. data/spec/unit/veritas/relation/gateway/rename_spec.rb +21 -0
  47. data/spec/unit/veritas/relation/gateway/respond_to_spec.rb +29 -0
  48. data/spec/unit/veritas/relation/gateway/restrict_spec.rb +27 -0
  49. data/spec/unit/veritas/relation/gateway/reverse_spec.rb +21 -0
  50. data/spec/unit/veritas/relation/gateway/sort_by_spec.rb +27 -0
  51. data/spec/unit/veritas/relation/gateway/summarize_spec.rb +148 -0
  52. data/spec/unit/veritas/relation/gateway/take_spec.rb +21 -0
  53. data/spec/unit/veritas/relation/gateway/union_spec.rb +17 -0
  54. data/tasks/metrics/ci.rake +7 -0
  55. data/tasks/metrics/flay.rake +41 -0
  56. data/tasks/metrics/flog.rake +43 -0
  57. data/tasks/metrics/heckle.rake +209 -0
  58. data/tasks/metrics/metric_fu.rake +29 -0
  59. data/tasks/metrics/reek.rake +9 -0
  60. data/tasks/metrics/roodi.rake +15 -0
  61. data/tasks/metrics/yardstick.rake +23 -0
  62. data/tasks/spec.rake +39 -0
  63. data/tasks/yard.rake +9 -0
  64. data/veritas-do-adapter.gemspec +124 -0
  65. metadata +257 -0
@@ -0,0 +1,363 @@
1
+ # encoding: utf-8
2
+
3
+ module Veritas
4
+ class Relation
5
+
6
+ # A relation backed by an adapter
7
+ class Gateway < Relation
8
+
9
+ DECORATED_CLASS = superclass
10
+
11
+ # remove methods so they can be proxied
12
+ undef_method *DECORATED_CLASS.public_instance_methods(false).map(&:to_s) - %w[ materialize ]
13
+ undef_method :project, :remove, :extend, :rename, :restrict, :sort_by, :reverse, :drop, :take
14
+
15
+ # The adapter the gateway will use to fetch results
16
+ #
17
+ # @return [Adapter::DataObjects]
18
+ #
19
+ # @api private
20
+ attr_reader :adapter
21
+ protected :adapter
22
+
23
+ # The relation the gateway will use to generate SQL
24
+ #
25
+ # @return [Relation]
26
+ #
27
+ # @api private
28
+ attr_reader :relation
29
+ protected :relation
30
+
31
+ # Initialize a Gateway
32
+ #
33
+ # @param [Adapter::DataObjects] adapter
34
+ #
35
+ # @param [Relation] relation
36
+ #
37
+ # @return [undefined]
38
+ #
39
+ # @api private
40
+ def initialize(adapter, relation)
41
+ @adapter = adapter
42
+ @relation = relation
43
+ end
44
+
45
+ # Iterate over each row in the results
46
+ #
47
+ # @example
48
+ # gateway = Gateway.new(adapter, relation)
49
+ # gateway.each { |tuple| ... }
50
+ #
51
+ # @yield [tuple]
52
+ #
53
+ # @yieldparam [Tuple] tuple
54
+ # each tuple in the results
55
+ #
56
+ # @return [self]
57
+ #
58
+ # @api public
59
+ def each
60
+ return to_enum unless block_given?
61
+ tuples.each { |tuple| yield tuple }
62
+ self
63
+ end
64
+
65
+ # Return a relation that is the join of two relations
66
+ #
67
+ # @example natural join
68
+ # join = relation.join(other)
69
+ #
70
+ # @example theta-join using a block
71
+ # join = relation.join(other) { |r| r.a.gte(r.b) }
72
+ #
73
+ # @param [Relation] other
74
+ # the other relation to join
75
+ #
76
+ # @yield [relation]
77
+ # optional block to restrict the tuples with
78
+ #
79
+ # @yieldparam [Relation] relation
80
+ # the context to evaluate the restriction with
81
+ #
82
+ # @yieldreturn [Function, #call]
83
+ # predicate to restrict the tuples with
84
+ #
85
+ # @return [Gateway]
86
+ # return a gateway if the adapters are equal
87
+ # @return [Algebra::Join]
88
+ # return a normal join when the adapters are not equal
89
+ # @return [Algebra::Restriction]
90
+ # return a normal restriction when the adapters are not equal
91
+ # for a theta-join
92
+ #
93
+ # @api public
94
+ def join(other)
95
+ if block_given?
96
+ super
97
+ else
98
+ binary_operation(__method__, other, Algebra::Join)
99
+ end
100
+ end
101
+
102
+ # Return a relation that is the cartesian product of two relations
103
+ #
104
+ # @example
105
+ # product = gateway.product(other)
106
+ #
107
+ # @param [Relation] other
108
+ # the other relation to find the product with
109
+ #
110
+ # @return [Gateway]
111
+ # return a gateway if the adapters are equal
112
+ # @return [Algebra::Product]
113
+ # return a normal product when the adapters are not equal
114
+ #
115
+ # @api public
116
+ def product(other)
117
+ binary_operation(__method__, other, Algebra::Product)
118
+ end
119
+
120
+ # Return the union between relations
121
+ #
122
+ # @example
123
+ # union = gateway.union(other)
124
+ #
125
+ # @param [Relation] other
126
+ # the other relation to find the union with
127
+ #
128
+ # @return [Gateway]
129
+ # return a gateway if the adapters are equal
130
+ # @return [Algebra::Union]
131
+ # return a normal union when the adapters are not equal
132
+ #
133
+ # @api public
134
+ def union(other)
135
+ binary_operation(__method__, other, Algebra::Union)
136
+ end
137
+
138
+ # Return the intersection between relations
139
+ #
140
+ # @example
141
+ # intersect = gateway.intersect(other)
142
+ #
143
+ # @param [Relation] other
144
+ # the other relation to find the intersect with
145
+ #
146
+ # @return [Gateway]
147
+ # return a gateway if the adapters are equal
148
+ # @return [Algebra::Intersection]
149
+ # return a normal intersection when the adapters are not equal
150
+ #
151
+ # @api public
152
+ def intersect(other)
153
+ binary_operation(__method__, other, Algebra::Intersection)
154
+ end
155
+
156
+ # Return the diferrence between relations
157
+ #
158
+ # @example
159
+ # difference = gateway.difference(other)
160
+ #
161
+ # @param [Relation] other
162
+ # the other relation to find the difference with
163
+ #
164
+ # @return [Gateway]
165
+ # return a gateway if the adapters are equal
166
+ # @return [Algebra::Difference]
167
+ # return a normal dfference when the adapters are not equal
168
+ #
169
+ # @api public
170
+ def difference(other)
171
+ binary_operation(__method__, other, Algebra::Difference)
172
+ end
173
+
174
+ # Return a summarized relation
175
+ #
176
+ # @example with no arguments
177
+ # summarization = gateway.summarize do |context|
178
+ # context.add(:count, context[:id].count)
179
+ # end
180
+ #
181
+ # @example with a relation
182
+ # summarization = gateway.summarize(relation) do |context|
183
+ # context.add(:count, context[:id].count)
184
+ # end
185
+ #
186
+ # @example with a header
187
+ # summarization = gateway.summarize([ :name ]) do |context|
188
+ # context.add(:count, context[:id].count)
189
+ # end
190
+ #
191
+ # @example with another gateway
192
+ # summarization = gateway.summarize(other_gateway) do |context|
193
+ # context.add(:count, context[:id].count)
194
+ # end
195
+ #
196
+ # @param [Gateway, Relation, Header, #to_ary] summarize_with
197
+ #
198
+ # @yield [function]
199
+ # Evaluate a summarization function
200
+ #
201
+ # @yieldparam [Evaluator::Context] context
202
+ # the context to evaluate the function within
203
+ #
204
+ # @return [Gateway]
205
+ # return a gateway if the adapters are equal, or there is no adapter
206
+ # @return [Algebra::Summarization]
207
+ # return a normal summarization when the adapters are not equal
208
+ #
209
+ # @api public
210
+ def summarize(summarize_with = TABLE_DEE, &block)
211
+ if summarize_merge?(summarize_with)
212
+ summarize_merge(summarize_with, &block)
213
+ else
214
+ summarize_split(summarize_with, &block)
215
+ end
216
+ end
217
+
218
+ # Test if the method is supported on this object
219
+ #
220
+ # @param [Symbol] method
221
+ #
222
+ # @return [Boolean]
223
+ #
224
+ # @api private
225
+ def respond_to?(method, *)
226
+ super || forwardable?(method)
227
+ end
228
+
229
+ private
230
+
231
+ # Proxy the message to the relation
232
+ #
233
+ # @param [Symbol] method
234
+ #
235
+ # @param [Array] *args
236
+ #
237
+ # @return [self]
238
+ # return self for all command methods
239
+ # @return [Object]
240
+ # return response from all query methods
241
+ #
242
+ # @api private
243
+ def method_missing(method, *args, &block)
244
+ forwardable?(method) ? forward(method, *args, &block) : super
245
+ end
246
+
247
+ # Test if the method can be forwarded to the relation
248
+ #
249
+ # @param [Symbol] method
250
+ #
251
+ # @return [Boolean]
252
+ #
253
+ # @api private
254
+ def forwardable?(method)
255
+ relation.respond_to?(method)
256
+ end
257
+
258
+ # Forward the message to the relation
259
+ #
260
+ # @param [Array] *args
261
+ #
262
+ # @return [self]
263
+ # return self for all command methods
264
+ # @return [Object]
265
+ # return response from all query methods
266
+ #
267
+ # @api private
268
+ def forward(*args, &block)
269
+ relation = self.relation
270
+ response = relation.public_send(*args, &block)
271
+ if response.equal?(relation)
272
+ self
273
+ elsif response.kind_of?(DECORATED_CLASS)
274
+ self.class.new(adapter, response)
275
+ else
276
+ response
277
+ end
278
+ end
279
+
280
+ # Return a list of tuples to iterate over
281
+ #
282
+ # @return [#each]
283
+ #
284
+ # @api private
285
+ def tuples
286
+ relation = self.relation
287
+ if materialized?
288
+ relation
289
+ else
290
+ DECORATED_CLASS.new(header, adapter.read(relation))
291
+ end
292
+ end
293
+
294
+ # Return a binary relation
295
+ #
296
+ # @param [Relation] other
297
+ #
298
+ # @return [Gateway]
299
+ # return a gateway if the adapters are equal
300
+ # @return [Relation]
301
+ # return a binary relation when the adapters are not equal
302
+ #
303
+ # @api private
304
+ def binary_operation(method, other, factory)
305
+ if same_adapter?(other)
306
+ forward(method, other.relation)
307
+ else
308
+ factory.new(self, other)
309
+ end
310
+ end
311
+
312
+ # Test if the other object uses the same adapter
313
+ #
314
+ # @param [Gateway, Relation] other
315
+ #
316
+ # @return [Boolean]
317
+ #
318
+ # @api private
319
+ def same_adapter?(other)
320
+ other.respond_to?(:adapter) && adapter.eql?(other.adapter)
321
+ end
322
+
323
+ # Test if the summarize_with object can be merged into the summarization
324
+ #
325
+ # @param [Gateway, Relation, Header] summarize_with
326
+ #
327
+ # @return [Boolean]
328
+ #
329
+ # @api private
330
+ def summarize_merge?(summarize_with)
331
+ !summarize_with.respond_to?(:header) ||
332
+ summarize_with.equal?(TABLE_DEE) ||
333
+ same_adapter?(summarize_with)
334
+ end
335
+
336
+ # Merge the summarize_with into the summarization
337
+ #
338
+ # @param [Gateway, Relation, Header] summarize_with
339
+ #
340
+ # @return [Gateway]
341
+ #
342
+ # @api private
343
+ def summarize_merge(summarize_with, &block)
344
+ summarize_with = summarize_with.relation if summarize_with.respond_to?(:relation)
345
+ forward(:summarize, summarize_with, &block)
346
+ end
347
+
348
+ # Split the summarize_with into a separate relation, wrapped in a summarization
349
+ #
350
+ # @param [Gateway, Relation, Header] summarize_with
351
+ #
352
+ # @return [Algebra::Summarization]
353
+ #
354
+ # @api private
355
+ def summarize_split(summarize_with, &block)
356
+ # evaluate the gateway, then summarize with the provided relation
357
+ context = Evaluator::Context.new(header - summarize_with.header, &block)
358
+ Algebra::Summarization.new(self, summarize_with, context.functions)
359
+ end
360
+
361
+ end # class Gateway
362
+ end # class Relation
363
+ end # module Veritas
@@ -0,0 +1,4 @@
1
+ # encoding: utf-8
2
+
3
+ require 'veritas/adapter/data_objects'
4
+ require 'veritas/relation/gateway'
data/spec/rcov.opts ADDED
@@ -0,0 +1,6 @@
1
+ --exclude-only "spec/,^/"
2
+ --sort coverage
3
+ --callsites
4
+ --xrefs
5
+ --profile
6
+ --text-summary
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'a binary relation method' do
4
+ describe 'when other has the same adapter' do
5
+ let(:other_relation) { mock('Other Relation') }
6
+ let(:other) { described_class.new(adapter, other_relation) }
7
+ let(:gateway) { mock('Other Gateway') }
8
+
9
+ before do
10
+ relation.stub!(operation).and_return(gateway)
11
+ end
12
+
13
+ it { should equal(gateway) }
14
+
15
+ it 'passes the other relation to the binary operation' do
16
+ relation.should_receive(operation).with(other_relation)
17
+ subject
18
+ end
19
+ end
20
+
21
+ describe 'when other has a different adapter' do
22
+ let(:other_adapter) { mock('Other Adapter') }
23
+ let(:other) { described_class.new(other_adapter, stub) }
24
+
25
+ before do
26
+ factory.stub!(:new).and_return(binary_relation)
27
+ end
28
+
29
+ it { should equal(binary_relation) }
30
+
31
+ it 'initializes the binary operation with the gateways' do
32
+ factory.should_receive(:new).with(object, other)
33
+ subject
34
+ end
35
+ end
36
+
37
+ describe 'when other has no adapter' do
38
+ let(:other) { mock('Other Relation') }
39
+
40
+ before do
41
+ factory.stub!(:new).and_return(binary_relation)
42
+ end
43
+
44
+ it { should equal(binary_relation) }
45
+
46
+ it 'initializes the binary operation with the gateway and other relation' do
47
+ factory.should_receive(:new).with(object, other)
48
+ subject
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'a command method' do
4
+ it 'returns self' do
5
+ should equal(object)
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'an #each method' do
4
+ it_should_behave_like 'a command method'
5
+
6
+ context 'with no block' do
7
+ subject { object.each }
8
+
9
+ it { should be_instance_of(to_enum.class) }
10
+
11
+ it 'yields the expected values' do
12
+ subject.to_a.should eql(object.to_a)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'an idempotent method' do
4
+ it 'is idempotent' do
5
+ should equal(instance_eval(&self.class.subject))
6
+ end
7
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ shared_examples_for 'a unary relation method' do
4
+ let(:gateway) { mock('New Gateway') }
5
+
6
+ before do
7
+ described_class.stub!(:new).and_return(gateway)
8
+ end
9
+
10
+ it { should equal(gateway) }
11
+
12
+ it 'tests the response is a relation' do
13
+ response.should_receive(:kind_of?).with(Relation)
14
+ subject
15
+ end
16
+
17
+ it 'initializes the new gateway with the adapter and response' do
18
+ described_class.should_receive(:new).with(adapter, response)
19
+ subject
20
+ end
21
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --loadby random
3
+ --format profile
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'backports'
5
+ require 'backports/basic_object'
6
+ require 'spec'
7
+ require 'spec/autorun'
8
+ require 'veritas'
9
+ require 'veritas-sql-generator'
10
+
11
+ include Veritas
12
+
13
+ # require spec support files and shared behavior
14
+ Dir[File.expand_path('../{support,shared}/**/*.rb', __FILE__)].each { |f| require f }
15
+
16
+ Spec::Runner.configure do |config|
17
+
18
+ # Record the original Attribute descendants
19
+ config.before do
20
+ @original_descendants = Attribute.descendants.dup
21
+ end
22
+
23
+ # Reset the Attribute descendants
24
+ config.after do
25
+ Attribute.descendants.replace(@original_descendants)
26
+ end
27
+
28
+ end
@@ -0,0 +1,15 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require 'veritas/adapter/data_objects'
5
+
6
+ describe Adapter::DataObjects, '.new' do
7
+ subject { object.new(uri) }
8
+
9
+ let(:uri) { stub }
10
+ let(:object) { described_class }
11
+
12
+ it { should be_instance_of(described_class) }
13
+
14
+ it { should be_frozen }
15
+ end
@@ -0,0 +1,70 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require 'veritas/adapter/data_objects'
5
+
6
+ shared_examples_for 'it uses the data_objects driver' do
7
+ let(:connection) { mock('Connection', :close => nil) }
8
+
9
+ before do
10
+ DataObjects::Connection.stub!(:new).and_return(connection)
11
+ end
12
+
13
+ it 'opens a connection' do
14
+ DataObjects::Connection.should_receive(:new).with(uri).and_return(connection)
15
+ subject
16
+ end
17
+
18
+ it 'closes a connection' do
19
+ connection.should_receive(:close).with(no_args)
20
+ subject
21
+ end
22
+
23
+ it 'does not close a connection if the constructor throws an exception' do
24
+ mock_exception = Class.new(Exception)
25
+ DataObjects::Connection.should_receive(:new).and_raise(mock_exception)
26
+ connection.should_not_receive(:close)
27
+ expect { subject }.to raise_error(mock_exception)
28
+ end
29
+ end
30
+
31
+ describe Adapter::DataObjects, '#read' do
32
+ let(:uri) { stub }
33
+ let(:object) { described_class.new(uri) }
34
+ let(:relation) { mock('Relation') }
35
+ let(:statement) { mock('Statement') }
36
+ let(:rows) { [ [ 1 ], [ 2 ], [ 3 ] ] }
37
+ let(:yields) { [] }
38
+
39
+ before do
40
+ expectation = statement.stub(:each)
41
+ rows.each { |row| expectation.and_yield(row) }
42
+
43
+ described_class::Statement.stub!(:new).and_return(statement)
44
+ end
45
+
46
+ context 'with a block' do
47
+ subject { object.read(relation) { |row| yields << row } }
48
+
49
+
50
+ it_should_behave_like 'it uses the data_objects driver'
51
+ it_should_behave_like 'a command method'
52
+
53
+ it 'yields each row' do
54
+ expect { subject }.to change { yields.dup }.
55
+ from([]).
56
+ to(rows)
57
+ end
58
+
59
+ it 'initializes a statement' do
60
+ described_class::Statement.should_receive(:new).with(connection, relation).and_return(statement)
61
+ subject
62
+ end
63
+ end
64
+
65
+ context 'without a block' do
66
+ subject { object.read(relation) }
67
+
68
+ it { should be_instance_of(to_enum.class) }
69
+ end
70
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require 'veritas/adapter/data_objects/statement'
5
+
6
+ describe Adapter::DataObjects::Statement, '.new' do
7
+ let(:connection) { stub }
8
+ let(:relation) { stub }
9
+ let(:object) { described_class }
10
+
11
+ context 'without a visitor' do
12
+ subject { object.new(connection, relation) }
13
+
14
+ it { should be_instance_of(described_class) }
15
+
16
+ it { should be_frozen }
17
+ end
18
+
19
+ context 'with a visitor' do
20
+ subject { object.new(connection, relation, visitor) }
21
+
22
+ let(:visitor) { stub }
23
+
24
+ it { should be_instance_of(described_class) }
25
+
26
+ it { should be_frozen }
27
+ end
28
+ end
@@ -0,0 +1,63 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+ require 'veritas/adapter/data_objects/statement'
5
+
6
+ describe Adapter::DataObjects::Statement, '#each' do
7
+ let(:reader) { mock('Reader', :next! => false).as_null_object }
8
+ let(:command) { mock('Command', :execute_reader => reader).as_null_object }
9
+ let(:connection) { mock('Connection', :create_command => command) }
10
+ let(:attribute) { stub }
11
+ let(:primitive) { stub }
12
+ let(:relation) { mock('Relation', :header => [ attribute ]) }
13
+ let(:generator) { mock('Generator').as_null_object }
14
+ let(:rows) { [ stub, stub ] }
15
+ let(:object) { described_class.new(connection, relation, generator) }
16
+ let(:yields) { [] }
17
+
18
+ before do
19
+ command.stub!(:dup => command, :freeze => command)
20
+ attribute.stub_chain(:class, :primitive).and_return(primitive)
21
+ end
22
+
23
+ context 'with no block' do
24
+ subject { object.each }
25
+
26
+ it { should be_instance_of(to_enum.class) }
27
+
28
+ it 'yields the expected attributes' do
29
+ subject.to_a.should eql(object.to_a)
30
+ end
31
+ end
32
+
33
+ context 'with a block' do
34
+ subject { object.each { |row| yields << row } }
35
+
36
+ before do
37
+ connection.should_receive(:create_command).with(object.to_s)
38
+
39
+ command.should_receive(:set_types).with([ primitive ]).ordered
40
+ command.should_receive(:execute_reader).with(no_args).ordered
41
+
42
+ rows.each do |values|
43
+ reader.should_receive(:next!).with(no_args).ordered.and_return(true)
44
+ reader.should_receive(:values).with(no_args).ordered.and_return(values)
45
+ end
46
+
47
+ reader.should_receive(:next!).with(no_args).ordered.and_return(false)
48
+ reader.should_receive(:close).with(no_args).ordered
49
+ end
50
+
51
+ before do
52
+ relation.should_receive(:header)
53
+ end
54
+
55
+ it_should_behave_like 'a command method'
56
+
57
+ it 'yields each row' do
58
+ expect { subject }.to change { yields.dup }.
59
+ from([]).
60
+ to(rows)
61
+ end
62
+ end
63
+ end