cequel 0.0.0 → 0.4.0
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.
- data/lib/cequel.rb +16 -0
- data/lib/cequel/batch.rb +58 -0
- data/lib/cequel/cql_row_specification.rb +22 -0
- data/lib/cequel/data_set.rb +346 -0
- data/lib/cequel/errors.rb +4 -0
- data/lib/cequel/keyspace.rb +106 -0
- data/lib/cequel/model.rb +95 -0
- data/lib/cequel/model/associations.rb +120 -0
- data/lib/cequel/model/callbacks.rb +32 -0
- data/lib/cequel/model/class_internals.rb +48 -0
- data/lib/cequel/model/column.rb +20 -0
- data/lib/cequel/model/dictionary.rb +202 -0
- data/lib/cequel/model/dirty.rb +53 -0
- data/lib/cequel/model/dynamic.rb +31 -0
- data/lib/cequel/model/errors.rb +13 -0
- data/lib/cequel/model/inheritable.rb +48 -0
- data/lib/cequel/model/instance_internals.rb +23 -0
- data/lib/cequel/model/local_association.rb +42 -0
- data/lib/cequel/model/magic.rb +79 -0
- data/lib/cequel/model/mass_assignment_security.rb +21 -0
- data/lib/cequel/model/naming.rb +17 -0
- data/lib/cequel/model/observer.rb +42 -0
- data/lib/cequel/model/persistence.rb +173 -0
- data/lib/cequel/model/properties.rb +143 -0
- data/lib/cequel/model/railtie.rb +33 -0
- data/lib/cequel/model/remote_association.rb +40 -0
- data/lib/cequel/model/scope.rb +362 -0
- data/lib/cequel/model/scoped.rb +50 -0
- data/lib/cequel/model/subclass_internals.rb +45 -0
- data/lib/cequel/model/timestamps.rb +52 -0
- data/lib/cequel/model/translation.rb +17 -0
- data/lib/cequel/model/validations.rb +50 -0
- data/lib/cequel/new_relic_instrumentation.rb +22 -0
- data/lib/cequel/row_specification.rb +63 -0
- data/lib/cequel/statement.rb +23 -0
- data/lib/cequel/version.rb +3 -0
- data/spec/environment.rb +3 -0
- data/spec/examples/data_set_spec.rb +382 -0
- data/spec/examples/keyspace_spec.rb +63 -0
- data/spec/examples/model/associations_spec.rb +109 -0
- data/spec/examples/model/callbacks_spec.rb +79 -0
- data/spec/examples/model/dictionary_spec.rb +413 -0
- data/spec/examples/model/dirty_spec.rb +39 -0
- data/spec/examples/model/dynamic_spec.rb +41 -0
- data/spec/examples/model/inheritable_spec.rb +45 -0
- data/spec/examples/model/magic_spec.rb +199 -0
- data/spec/examples/model/mass_assignment_security_spec.rb +13 -0
- data/spec/examples/model/naming_spec.rb +9 -0
- data/spec/examples/model/observer_spec.rb +86 -0
- data/spec/examples/model/persistence_spec.rb +201 -0
- data/spec/examples/model/properties_spec.rb +81 -0
- data/spec/examples/model/scope_spec.rb +677 -0
- data/spec/examples/model/serialization_spec.rb +20 -0
- data/spec/examples/model/spec_helper.rb +12 -0
- data/spec/examples/model/timestamps_spec.rb +52 -0
- data/spec/examples/model/translation_spec.rb +23 -0
- data/spec/examples/model/validations_spec.rb +86 -0
- data/spec/examples/spec_helper.rb +9 -0
- data/spec/models/asset.rb +21 -0
- data/spec/models/asset_observer.rb +5 -0
- data/spec/models/blog.rb +14 -0
- data/spec/models/blog_posts.rb +6 -0
- data/spec/models/category.rb +9 -0
- data/spec/models/comment.rb +12 -0
- data/spec/models/photo.rb +5 -0
- data/spec/models/post.rb +88 -0
- data/spec/models/post_comments.rb +14 -0
- data/spec/models/post_observer.rb +43 -0
- data/spec/support/helpers.rb +26 -0
- data/spec/support/result_stub.rb +27 -0
- metadata +125 -23
@@ -0,0 +1,81 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Cequel::Model::Properties do
|
4
|
+
let(:post) { Post.new(:id => 1) }
|
5
|
+
|
6
|
+
it 'should have getter for key' do
|
7
|
+
post.id.should == 1
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'should return key alias from class' do
|
11
|
+
Post.key_alias.should == :id
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should return key for to_key' do
|
15
|
+
post.to_key.should == [1]
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should return param for to_param' do
|
19
|
+
post.persisted!
|
20
|
+
post.to_param.should == '1'
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should have getter and setter for column' do
|
24
|
+
post.title = 'Object/row mapping'
|
25
|
+
post.title.should == 'Object/row mapping'
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should have ? accessor for boolean column' do
|
29
|
+
post.published = true
|
30
|
+
post.published?.should be_true
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'should not have ? accessor for non-boolean column' do
|
34
|
+
expect { post.title? }.to raise_error(NoMethodError)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should expose column names on class' do
|
38
|
+
Post.column_names[0..1].should == [:id, :title]
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should expose column objects on class' do
|
42
|
+
Post.columns[0..1].map { |col| [col.name, col.type] }.
|
43
|
+
should == [[:id, :int], [:title, :varchar]]
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should expose #attributes' do
|
47
|
+
post.title = 'Cequel'
|
48
|
+
post.attributes.
|
49
|
+
should == {:id => 1, :title => 'Cequel'}.with_indifferent_access
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should not return nil values with attributes' do
|
53
|
+
post.title = nil
|
54
|
+
post.attributes.should == {:id => 1}.with_indifferent_access
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should set attributes' do
|
58
|
+
post.attributes = { :title => 'Cequel' }
|
59
|
+
post.id.should == 1
|
60
|
+
post.title.should == 'Cequel'
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should set attributes from constructor' do
|
64
|
+
Post.new(:id => 1, :title => 'Cequel').title.should == 'Cequel'
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should set default column values for new instances' do
|
68
|
+
Blog.new.published.should == true
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should use key to compare equality' do
|
72
|
+
Post.new(:id => 1).should == Post.new(:id => 1)
|
73
|
+
Post.new(:id => 1).should_not == Post.new(:id => 2)
|
74
|
+
Post.new(:id => 1).should_not == Blog.new(:id => 1)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should use #generate_key method when implemented' do
|
78
|
+
Comment.new.id.should be_a(SimpleUUID::UUID)
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
@@ -0,0 +1,677 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Cequel::Model::Scope do
|
4
|
+
describe '#each' do
|
5
|
+
it 'should provide enumerator for query results' do
|
6
|
+
connection.stub(:execute).with("SELECT * FROM posts").
|
7
|
+
and_return result_stub(:id => 1, :title => 'Cequel')
|
8
|
+
|
9
|
+
Post.all.map { |post| [post.id, post.title] }.
|
10
|
+
should == [[1, 'Cequel']]
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should enumerate results for just id if no key restriction' do
|
14
|
+
connection.stub(:execute).with("SELECT ? FROM posts", [:id]).
|
15
|
+
and_return result_stub(:id => 1)
|
16
|
+
|
17
|
+
Post.select(:id).to_a.map { |post| post.id }.should == [1]
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should not enumerate results for just id if key restriction' do
|
21
|
+
connection.stub(:execute).with("SELECT * FROM posts").
|
22
|
+
and_return result_stub(:id => 1)
|
23
|
+
|
24
|
+
Post.all.to_a.map { |post| post.id }.should == []
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should provide enumerator if no block given' do
|
28
|
+
enum = Post.all.each
|
29
|
+
|
30
|
+
connection.stub(:execute).with("SELECT * FROM posts").
|
31
|
+
and_return result_stub(:id => 1, :title => 'Cequel')
|
32
|
+
|
33
|
+
enum.map { |post| post.title }.should == ['Cequel']
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'should enumerate zero times if empty-collection key restriction given' do
|
37
|
+
Post.where(:id => []).to_a.should == []
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should enumerate zero times if empty-collection restriction given' do
|
41
|
+
Post.where(:title => []).to_a.should == []
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe '#first' do
|
46
|
+
it 'should query for a single post' do
|
47
|
+
connection.stub(:execute).with("SELECT * FROM posts LIMIT 1").
|
48
|
+
and_return result_stub(:id => 1, :title => 'Cequel')
|
49
|
+
|
50
|
+
Post.first.title.should == 'Cequel'
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should query for a single post within scope' do
|
54
|
+
connection.stub(:execute).with("SELECT ? FROM posts LIMIT 1", [:id, :title]).
|
55
|
+
and_return result_stub(:id => 1, :title => 'Cequel')
|
56
|
+
|
57
|
+
Post.select(:id, :title).first.title.should == 'Cequel'
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should query scopes successively when multi-valued non-key column selected' do
|
61
|
+
connection.stub(:execute).with("SELECT * FROM posts WHERE ? = ? LIMIT 1", :title, 'Cequel').
|
62
|
+
and_return result_stub
|
63
|
+
connection.stub(:execute).with("SELECT * FROM posts WHERE ? = ? LIMIT 1", :title, 'Cassandra').
|
64
|
+
and_return result_stub(:id => 1, :title => 'Cassandra')
|
65
|
+
connection.stub(:execute).with("SELECT * FROM posts WHERE ? = ? LIMIT 1", :title, 'CQL').
|
66
|
+
and_return result_stub(:id => 2, :title => 'CQL')
|
67
|
+
|
68
|
+
Post.where(:title => %w(Cequel Cassandra CQL)).first.title.
|
69
|
+
should == 'Cassandra'
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should apply index preference when specified' do
|
73
|
+
connection.should_receive(:execute).
|
74
|
+
with("SELECT * FROM assets WHERE ? = ? AND ? = ? LIMIT 1", :checksum, 'abcdef', :class_name, 'Photo').
|
75
|
+
and_return result_stub
|
76
|
+
Photo.where(:checksum => 'abcdef').first
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'should return nil when empty key collection given' do
|
80
|
+
Post.where(:id => []).first.should be_nil
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'should return nil when empty non-key collection given' do
|
84
|
+
Post.where(:title => []).first.should be_nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe '#count' do
|
89
|
+
it 'should count records' do
|
90
|
+
connection.stub(:execute).with("SELECT COUNT(*) FROM posts").
|
91
|
+
and_return result_stub('count' => 5)
|
92
|
+
|
93
|
+
Post.count.should == 5
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'should count records in scope' do
|
97
|
+
connection.stub(:execute).
|
98
|
+
with("SELECT COUNT(*) FROM posts WHERE ? = ?", :blog_id, 1).
|
99
|
+
and_return result_stub('count' => 5)
|
100
|
+
|
101
|
+
Post.where(:blog_id => 1).count.should == 5
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'should raise error if attempting count with key restriction' do
|
105
|
+
expect { Post.where(:id => [1, 2, 3]).count }.
|
106
|
+
to raise_error(Cequel::Model::InvalidQuery)
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'should perform multiple COUNT queries if non-key column selected for multiple values' do
|
110
|
+
connection.stub(:execute).
|
111
|
+
with("SELECT COUNT(*) FROM posts WHERE ? = ?", :blog_id, 1).
|
112
|
+
and_return result_stub('count' => 3)
|
113
|
+
connection.stub(:execute).
|
114
|
+
with("SELECT COUNT(*) FROM posts WHERE ? = ?", :blog_id, 2).
|
115
|
+
and_return result_stub('count' => 2)
|
116
|
+
|
117
|
+
Post.where(:blog_id => [1, 2]).count.should == 5
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'should return nil if empty non-key restriction given' do
|
121
|
+
Post.where(:title => []).count.should == 0
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'should apply index preference when specified' do
|
125
|
+
connection.should_receive(:execute).
|
126
|
+
with("SELECT COUNT(*) FROM assets WHERE ? = ? AND ? = ?", :checksum, 'abcdef', :class_name, 'Photo').
|
127
|
+
and_return result_stub('count' => 0)
|
128
|
+
Photo.where(:checksum => 'abcdef').count
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe '#find' do
|
133
|
+
it 'should send scoped query when no block is passed' do
|
134
|
+
connection.stub(:execute).
|
135
|
+
with("SELECT ? FROM posts WHERE ? = ? LIMIT 1", [:id, :title], :id, 1).
|
136
|
+
and_return result_stub(:id => 1, :title => 'Cequel')
|
137
|
+
|
138
|
+
Post.select(:id, :title).find(1).title.should == 'Cequel'
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'should send scoped query with multiple keys when no block is passed' do
|
142
|
+
connection.stub(:execute).
|
143
|
+
with("SELECT ? FROM posts WHERE ? IN (?)", [:id, :title], :id, [1, 2]).
|
144
|
+
and_return result_stub(
|
145
|
+
{:id => 1, :title => 'Cequel'},
|
146
|
+
{:id => 2, :title => 'Cequel 2'}
|
147
|
+
)
|
148
|
+
|
149
|
+
Post.select(:id, :title).find(1, 2).
|
150
|
+
map { |post| post.title }.should == ['Cequel', 'Cequel 2']
|
151
|
+
end
|
152
|
+
|
153
|
+
it 'should delegate to enumerator when block is passed' do
|
154
|
+
connection.stub(:execute).
|
155
|
+
with("SELECT ? FROM posts", [:id, :title]).
|
156
|
+
and_return result_stub(
|
157
|
+
{:id => 1, :title => 'Cequel'},
|
158
|
+
{:id => 2, :title => 'Cequel 2'}
|
159
|
+
)
|
160
|
+
|
161
|
+
Post.select(:id, :title).find { |post| post.id == 2 }.title.
|
162
|
+
should == 'Cequel 2'
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
describe '#any?' do
|
167
|
+
it 'if called without block, should return true if COUNT > 0' do
|
168
|
+
connection.stub(:execute).
|
169
|
+
with("SELECT COUNT(*) FROM posts WHERE ? = ?", :blog_id, 1).
|
170
|
+
and_return result_stub('count' => 5)
|
171
|
+
|
172
|
+
Post.where(:blog_id => 1).any?.should be_true
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'if called without block, should return false if COUNT == 0' do
|
176
|
+
connection.stub(:execute).
|
177
|
+
with("SELECT COUNT(*) FROM posts WHERE ? = ?", :blog_id, 1).
|
178
|
+
and_return result_stub('count' => 0)
|
179
|
+
|
180
|
+
Post.where(:blog_id => 1).any?.should be_false
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'if called with block should delegate to enumerator' do
|
184
|
+
connection.stub(:execute).
|
185
|
+
with("SELECT * FROM posts WHERE ? = ?", :blog_id, 1).
|
186
|
+
and_return result_stub(
|
187
|
+
{:id => 1, :title => 'Cequel'},
|
188
|
+
{:id => 2, :title => 'Cequel 2'}
|
189
|
+
)
|
190
|
+
|
191
|
+
Post.where(:blog_id => 1).any? { |post| post.id == 1 }.should be_true
|
192
|
+
Post.where(:blog_id => 1).any? { |post| post.id == 8 }.should be_false
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
describe '#none?' do
|
197
|
+
it 'if called without block, should return false if COUNT > 0' do
|
198
|
+
connection.stub(:execute).
|
199
|
+
with("SELECT COUNT(*) FROM posts WHERE ? = ?", :blog_id, 1).
|
200
|
+
and_return result_stub('count' => 5)
|
201
|
+
|
202
|
+
Post.where(:blog_id => 1).none?.should be_false
|
203
|
+
end
|
204
|
+
|
205
|
+
it 'if called without block, should return true if COUNT == 0' do
|
206
|
+
connection.stub(:execute).
|
207
|
+
with("SELECT COUNT(*) FROM posts WHERE ? = ?", :blog_id, 1).
|
208
|
+
and_return result_stub('count' => 0)
|
209
|
+
|
210
|
+
Post.where(:blog_id => 1).none?.should be_true
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'if called with block should delegate to enumerator' do
|
214
|
+
connection.stub(:execute).
|
215
|
+
with("SELECT * FROM posts WHERE ? = ?", :blog_id, 1).
|
216
|
+
and_return result_stub(
|
217
|
+
{:id => 1, :title => 'Cequel'},
|
218
|
+
{:id => 2, :title => 'Cequel 2'}
|
219
|
+
)
|
220
|
+
|
221
|
+
Post.where(:blog_id => 1).none? { |post| post.id == 1 }.should be_false
|
222
|
+
Post.where(:blog_id => 1).none? { |post| post.id == 8 }.should be_true
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
describe '#one?' do
|
227
|
+
it 'if called without block, should return false if COUNT == 0' do
|
228
|
+
connection.stub(:execute).
|
229
|
+
with("SELECT COUNT(*) FROM posts WHERE ? = ?", :blog_id, 1).
|
230
|
+
and_return result_stub('count' => 0)
|
231
|
+
|
232
|
+
Post.where(:blog_id => 1).one?.should be_false
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'if called without block, should return true if COUNT == 1' do
|
236
|
+
connection.stub(:execute).
|
237
|
+
with("SELECT COUNT(*) FROM posts WHERE ? = ?", :blog_id, 1).
|
238
|
+
and_return result_stub('count' => 1)
|
239
|
+
|
240
|
+
Post.where(:blog_id => 1).one?.should be_true
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'if called without block, should return false if COUNT > 1' do
|
244
|
+
connection.stub(:execute).
|
245
|
+
with("SELECT COUNT(*) FROM posts WHERE ? = ?", :blog_id, 1).
|
246
|
+
and_return result_stub('count' => 12)
|
247
|
+
|
248
|
+
Post.where(:blog_id => 1).one?.should be_false
|
249
|
+
end
|
250
|
+
|
251
|
+
it 'if called with block should delegate to enumerator' do
|
252
|
+
connection.stub(:execute).
|
253
|
+
with("SELECT * FROM posts WHERE ? = ?", :blog_id, 1).
|
254
|
+
and_return result_stub(
|
255
|
+
{:id => 1, :title => 'Cequel'},
|
256
|
+
{:id => 2, :title => 'Cequel 2'}
|
257
|
+
)
|
258
|
+
|
259
|
+
Post.where(:blog_id => 1).none? { |post| post.id == 1 }.should be_false
|
260
|
+
Post.where(:blog_id => 1).none? { |post| post.id == 8 }.should be_true
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
describe '#find_in_batches' do
|
265
|
+
it 'should select in batches of given size' do
|
266
|
+
connection.stub(:execute).with("SELECT * FROM posts LIMIT 2").
|
267
|
+
and_return result_stub(
|
268
|
+
{:id => 1, :title => 'Post 1'},
|
269
|
+
{:id => 2, :title => 'Post 2'}
|
270
|
+
)
|
271
|
+
connection.stub(:execute).
|
272
|
+
with("SELECT * FROM posts WHERE ? > ? LIMIT 2", :id, 2).
|
273
|
+
and_return result_stub(:id => 3, :title => 'Post 3')
|
274
|
+
batches = []
|
275
|
+
Post.find_in_batches(:batch_size => 2) do |batch|
|
276
|
+
batches << batch
|
277
|
+
end
|
278
|
+
batches.map { |batch| batch.map(&:title) }.should ==
|
279
|
+
[['Post 1', 'Post 2'], ['Post 3']]
|
280
|
+
end
|
281
|
+
|
282
|
+
it 'should not duplicate last key if given back first in next batch' do
|
283
|
+
connection.stub(:execute).with("SELECT * FROM posts LIMIT 2").
|
284
|
+
and_return result_stub(
|
285
|
+
{:id => 1, :title => 'Post 1'},
|
286
|
+
{:id => 2, :title => 'Post 2'}
|
287
|
+
)
|
288
|
+
connection.stub(:execute).
|
289
|
+
with("SELECT * FROM posts WHERE ? > ? LIMIT 2", :id, 2).
|
290
|
+
and_return result_stub(
|
291
|
+
{:id => 2, :title => 'Post 2'},
|
292
|
+
{:id => 3, :title => 'Post 3'}
|
293
|
+
)
|
294
|
+
connection.stub(:execute).
|
295
|
+
with("SELECT * FROM posts WHERE ? > ? LIMIT 2", :id, 3).
|
296
|
+
and_return result_stub()
|
297
|
+
batches = []
|
298
|
+
Post.find_in_batches(:batch_size => 2) do |batch|
|
299
|
+
batches << batch
|
300
|
+
end
|
301
|
+
batches.map { |batch| batch.map(&:title) }.should ==
|
302
|
+
[['Post 1', 'Post 2'], ['Post 3']]
|
303
|
+
end
|
304
|
+
|
305
|
+
it 'should iterate over batches of keys' do
|
306
|
+
connection.stub(:execute).with("SELECT * FROM posts WHERE ? IN (?)", :id, [1, 2]).
|
307
|
+
and_return result_stub(
|
308
|
+
{:id => 1, :title => 'Post 1'},
|
309
|
+
{:id => 2, :title => 'Post 2'}
|
310
|
+
)
|
311
|
+
connection.stub(:execute).
|
312
|
+
with("SELECT * FROM posts WHERE ? = ?", :id, 3).
|
313
|
+
and_return result_stub(:id => 3, :title => 'Post 3')
|
314
|
+
batches = []
|
315
|
+
Post.where(:id => [1, 2, 3]).find_in_batches(:batch_size => 2) do |batch|
|
316
|
+
batches << batch
|
317
|
+
end
|
318
|
+
batches.map { |batch| batch.map(&:title) }.should ==
|
319
|
+
[['Post 1', 'Post 2'], ['Post 3']]
|
320
|
+
end
|
321
|
+
|
322
|
+
it 'should respect scope' do
|
323
|
+
connection.stub(:execute).with("SELECT ? FROM posts LIMIT 2", [:id, :title]).
|
324
|
+
and_return result_stub(
|
325
|
+
{:id => 1, :title => 'Post 1'},
|
326
|
+
{:id => 2, :title => 'Post 2'}
|
327
|
+
)
|
328
|
+
connection.stub(:execute).
|
329
|
+
with("SELECT ? FROM posts WHERE ? > ? LIMIT 2", [:id, :title], :id, 2).
|
330
|
+
and_return result_stub(:id => 3, :title => 'Post 3')
|
331
|
+
batches = []
|
332
|
+
Post.select(:id, :title).find_in_batches(:batch_size => 2) do |batch|
|
333
|
+
batches << batch
|
334
|
+
end
|
335
|
+
batches.map { |batch| batch.map(&:title) }.should ==
|
336
|
+
[['Post 1', 'Post 2'], ['Post 3']]
|
337
|
+
end
|
338
|
+
|
339
|
+
it 'should add key column to SELECT if omitted' do
|
340
|
+
connection.stub(:execute).with("SELECT ? FROM posts LIMIT 2", [:title, :id]).
|
341
|
+
and_return result_stub(
|
342
|
+
{:id => 1, :title => 'Post 1'},
|
343
|
+
{:id => 2, :title => 'Post 2'}
|
344
|
+
)
|
345
|
+
connection.stub(:execute).
|
346
|
+
with("SELECT ? FROM posts WHERE ? > ? LIMIT 2", [:title, :id], :id, 2).
|
347
|
+
and_return result_stub(:id => 3, :title => 'Post 3')
|
348
|
+
batches = []
|
349
|
+
Post.select(:title).find_in_batches(:batch_size => 2) do |batch|
|
350
|
+
batches << batch
|
351
|
+
end
|
352
|
+
batches.map { |batch| batch.map(&:title) }.should ==
|
353
|
+
[['Post 1', 'Post 2'], ['Post 3']]
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
describe '#find_each' do
|
358
|
+
it 'should iterate over batches and yield results one by one' do
|
359
|
+
connection.stub(:execute).with("SELECT * FROM posts LIMIT 2").
|
360
|
+
and_return result_stub(
|
361
|
+
{:id => 1, :title => 'Post 1'},
|
362
|
+
{:id => 2, :title => 'Post 2'}
|
363
|
+
)
|
364
|
+
connection.stub(:execute).
|
365
|
+
with("SELECT * FROM posts WHERE ? > ? LIMIT 2", :id, 2).
|
366
|
+
and_return result_stub(:id => 3, :title => 'Post 3')
|
367
|
+
Post.find_each(:batch_size => 2).map { |post| post.title }.
|
368
|
+
should == ['Post 1', 'Post 2', 'Post 3']
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
describe '#select' do
|
373
|
+
it 'should scope columns in data set' do
|
374
|
+
connection.stub(:execute).with("SELECT ? FROM posts", [:id, :title]).
|
375
|
+
and_return result_stub(:id => 1, :title => 'Cequel')
|
376
|
+
|
377
|
+
Post.select(:id, :title).map { |post| post.title }.should == ['Cequel']
|
378
|
+
end
|
379
|
+
|
380
|
+
it 'should fail fast if attempting to select only key column with restrictions on key column' do
|
381
|
+
expect { Post.where(:id => 1).select(:id) }.
|
382
|
+
to raise_error(Cequel::Model::InvalidQuery)
|
383
|
+
end
|
384
|
+
|
385
|
+
it 'should delegate to enumerator if block given' do
|
386
|
+
connection.stub(:execute).
|
387
|
+
with("SELECT * FROM posts WHERE ? = ?", :blog_id, 1).
|
388
|
+
and_return result_stub(
|
389
|
+
{:id => 1, :title => 'Cequel'},
|
390
|
+
{:id => 2, :title => 'Cequel 2'},
|
391
|
+
{:id => 3, :title => 'Cequel 3'}
|
392
|
+
)
|
393
|
+
|
394
|
+
Post.where(:blog_id => 1).select { |post| post.id < 3 }.
|
395
|
+
map { |post| post.title }.should == ['Cequel', 'Cequel 2']
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
describe '#select!' do
|
400
|
+
it 'should override previous columns in data set' do
|
401
|
+
connection.stub(:execute).with("SELECT ? FROM posts", [:id, :published]).
|
402
|
+
and_return result_stub(:id => 1, :published => true)
|
403
|
+
|
404
|
+
Post.select(:id, :title).select!(:id, :published).
|
405
|
+
map { |post| post.published }.should == [true]
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
describe '#consistency' do
|
410
|
+
it 'should scope consistency in data set' do
|
411
|
+
connection.stub(:execute).with("SELECT * FROM posts USING CONSISTENCY QUORUM").
|
412
|
+
and_return result_stub(:id => 1, :title => 'Cequel')
|
413
|
+
|
414
|
+
Post.consistency(:quorum).map { |post| post.title }.should == ['Cequel']
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
describe '#where' do
|
419
|
+
it 'should scope to row specifications in data set' do
|
420
|
+
connection.stub(:execute).with("SELECT * FROM posts WHERE ? = ?", :id, 1).
|
421
|
+
and_return result_stub(:id => 1, :title => 'Cequel')
|
422
|
+
|
423
|
+
Post.where(:id => 1).map { |post| post.title }.should == ['Cequel']
|
424
|
+
end
|
425
|
+
|
426
|
+
it 'should perform multiple queries if IN query performed on non-key column' do
|
427
|
+
connection.stub(:execute).with("SELECT * FROM posts WHERE ? = ?", :title, 'Cequel').
|
428
|
+
and_return result_stub(:id => 1, :title => 'Cequel')
|
429
|
+
connection.stub(:execute).with("SELECT * FROM posts WHERE ? = ?", :title, 'Fun').
|
430
|
+
and_return result_stub(
|
431
|
+
{:id => 2, :title => 'Fun'},
|
432
|
+
{:id => 3, :title => 'Fun'}
|
433
|
+
)
|
434
|
+
Post.where(:title => %w(Cequel Fun)).map(&:id).
|
435
|
+
should == [1, 2, 3]
|
436
|
+
end
|
437
|
+
|
438
|
+
it 'should fail fast if attempting to select only key column with restrictions on key column' do
|
439
|
+
expect { Post.select(:id).where(:id => 1) }.
|
440
|
+
to raise_error(Cequel::Model::InvalidQuery)
|
441
|
+
end
|
442
|
+
|
443
|
+
it 'should fail fast if attempting to mix key and non-key columns' do
|
444
|
+
expect { Post.where(:id => 1).where(:title => 'Cequel') }.
|
445
|
+
to raise_error(Cequel::Model::InvalidQuery)
|
446
|
+
end
|
447
|
+
|
448
|
+
it 'should use index preference if given' do
|
449
|
+
connection.should_receive(:execute).
|
450
|
+
with("SELECT * FROM assets WHERE ? = ? AND ? = ?", :checksum, 'abcdef', :class_name, 'Photo').
|
451
|
+
and_return result_stub
|
452
|
+
Photo.where(:checksum => 'abcdef').to_a
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
describe '#where!' do
|
457
|
+
it 'should override previously chained row specifications' do
|
458
|
+
connection.stub(:execute).with("SELECT * FROM posts WHERE ? = ?", :title, 'Cequel').
|
459
|
+
and_return result_stub(:id => 1, :title => 'Cequel')
|
460
|
+
|
461
|
+
Post.where(:id => 1).where!(:title => 'Cequel').
|
462
|
+
map { |post| post.title }.should == ['Cequel']
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
describe '#limit' do
|
467
|
+
it 'should limit results in data set' do
|
468
|
+
connection.stub(:execute).with("SELECT * FROM posts LIMIT 5").
|
469
|
+
and_return result_stub(:id => 1, :title => 'Cequel')
|
470
|
+
|
471
|
+
Post.limit(5).map { |post| post.title }.should == ['Cequel']
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
describe 'chaining' do
|
476
|
+
it 'should aggregate scopes' do
|
477
|
+
connection.stub(:execute).
|
478
|
+
with("SELECT ? FROM posts USING CONSISTENCY QUORUM WHERE ? = ? LIMIT 5", [:id, :title], :blog_id, 1).
|
479
|
+
and_return result_stub(:id => 1, :title => 'Cequel')
|
480
|
+
|
481
|
+
Post.select(:id, :title).
|
482
|
+
consistency(:quorum).
|
483
|
+
where(:blog_id => 1).
|
484
|
+
limit(5).
|
485
|
+
map { |post| post.title }.should == ['Cequel']
|
486
|
+
end
|
487
|
+
|
488
|
+
it 'should delegate unknown methods to the underlying class with self as current scope' do
|
489
|
+
connection.stub(:execute).
|
490
|
+
with("SELECT ? FROM posts USING CONSISTENCY QUORUM WHERE ? = ? LIMIT 5", [:id, :title], :blog_id, 1).
|
491
|
+
and_return result_stub(:id => 1, :title => 'Cequel')
|
492
|
+
|
493
|
+
Post.select(:id, :title).
|
494
|
+
consistency(:quorum).
|
495
|
+
for_blog(1).
|
496
|
+
limit(5).
|
497
|
+
map { |post| post.title }.should == ['Cequel']
|
498
|
+
end
|
499
|
+
|
500
|
+
end
|
501
|
+
|
502
|
+
describe '#update_all' do
|
503
|
+
context 'with no scope restrictions' do
|
504
|
+
let(:scope) { Post }
|
505
|
+
|
506
|
+
it 'should get all keys and then update htem' do
|
507
|
+
connection.should_receive(:execute).
|
508
|
+
with("SELECT ? FROM posts", [:id]).
|
509
|
+
and_return result_stub(
|
510
|
+
{:id => 1},
|
511
|
+
{:id => 2}
|
512
|
+
)
|
513
|
+
connection.should_receive(:execute).
|
514
|
+
with "UPDATE posts SET ? = ? WHERE ? IN (?)", :title, 'Cequel', :id, [1, 2]
|
515
|
+
scope.update_all(:title => 'Cequel')
|
516
|
+
end
|
517
|
+
end
|
518
|
+
|
519
|
+
context 'with scope selecting on ids' do
|
520
|
+
let(:scope) { Post.where(:id => [1, 2]) }
|
521
|
+
|
522
|
+
it 'should issue scoped update request' do
|
523
|
+
connection.should_receive(:execute).
|
524
|
+
with "UPDATE posts SET ? = ? WHERE ? IN (?)", :title, 'Cequel', :id, [1, 2]
|
525
|
+
scope.update_all(:title => 'Cequel')
|
526
|
+
end
|
527
|
+
|
528
|
+
end
|
529
|
+
|
530
|
+
context 'with scope selecting on non-IDs' do
|
531
|
+
let(:scope) { Post.where(:published => false) }
|
532
|
+
|
533
|
+
it 'should perform "subquery" and issue update' do
|
534
|
+
connection.stub(:execute).
|
535
|
+
with("SELECT ? FROM posts WHERE ? = ?", [:id], :published, false).
|
536
|
+
and_return result_stub({:id => 1}, {:id => 2})
|
537
|
+
|
538
|
+
connection.should_receive(:execute).
|
539
|
+
with "UPDATE posts SET ? = ? WHERE ? IN (?)", :title, 'Cequel', :id, [1, 2]
|
540
|
+
|
541
|
+
scope.update_all(:title => 'Cequel')
|
542
|
+
end
|
543
|
+
end
|
544
|
+
|
545
|
+
context 'with scope selecting multiple values on non-key column' do
|
546
|
+
let(:scope) { Post.where(:title => %w(Cequel Cassandra)) }
|
547
|
+
|
548
|
+
it 'should perform multiple subqueries and execute single update on returned keys' do
|
549
|
+
connection.stub(:execute).
|
550
|
+
with("SELECT ? FROM posts WHERE ? = ?", [:id], :title, 'Cequel').
|
551
|
+
and_return result_stub({:id => 1}, {:id => 2})
|
552
|
+
|
553
|
+
connection.stub(:execute).
|
554
|
+
with("SELECT ? FROM posts WHERE ? = ?", [:id], :title, 'Cassandra').
|
555
|
+
and_return result_stub({:id => 3}, {:id => 4})
|
556
|
+
|
557
|
+
connection.should_receive(:execute).
|
558
|
+
with "UPDATE posts SET ? = ? WHERE ? IN (?)", :published, true, :id, [1, 2, 3, 4]
|
559
|
+
|
560
|
+
scope.update_all(:published => true)
|
561
|
+
end
|
562
|
+
end
|
563
|
+
end
|
564
|
+
|
565
|
+
describe '#destroy_all' do
|
566
|
+
context 'with no scope restrictions' do
|
567
|
+
let(:scope) { Post }
|
568
|
+
|
569
|
+
it 'should destroy all instances' do
|
570
|
+
connection.should_receive(:execute).
|
571
|
+
with('SELECT * FROM posts').
|
572
|
+
and_return result_stub(
|
573
|
+
{'id' => 1, 'title' => 'Cequel'},
|
574
|
+
{'id' => 2, 'title' => 'Cassandra'}
|
575
|
+
)
|
576
|
+
connection.should_receive(:execute).
|
577
|
+
with "DELETE FROM posts WHERE ? = ?", :id, 1
|
578
|
+
connection.should_receive(:execute).
|
579
|
+
with "DELETE FROM posts WHERE ? = ?", :id, 2
|
580
|
+
scope.destroy_all
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
context 'with column restrictions' do
|
585
|
+
let(:scope) { Post.where(:id => [1, 2]) }
|
586
|
+
|
587
|
+
it 'should issue scoped update request' do
|
588
|
+
connection.should_receive(:execute).
|
589
|
+
with("SELECT * FROM posts WHERE ? IN (?)", :id, [1, 2]).
|
590
|
+
and_return result_stub(
|
591
|
+
{'id' => 1, 'title' => 'Cequel'},
|
592
|
+
{'id' => 2, 'title' => 'Cassandra'}
|
593
|
+
)
|
594
|
+
connection.should_receive(:execute).
|
595
|
+
with "DELETE FROM posts WHERE ? = ?", :id, 1
|
596
|
+
connection.should_receive(:execute).
|
597
|
+
with "DELETE FROM posts WHERE ? = ?", :id, 2
|
598
|
+
scope.destroy_all
|
599
|
+
end
|
600
|
+
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
describe '#delete_all' do
|
605
|
+
context 'with no scope restrictions' do
|
606
|
+
let(:scope) { Post }
|
607
|
+
|
608
|
+
it 'should truncate keyspace' do
|
609
|
+
connection.should_receive(:execute).
|
610
|
+
with "TRUNCATE posts"
|
611
|
+
Post.delete_all
|
612
|
+
end
|
613
|
+
end
|
614
|
+
|
615
|
+
context 'with scope selecting on ids' do
|
616
|
+
let(:scope) { Post.where(:id => [1, 2]) }
|
617
|
+
|
618
|
+
it 'should issue scoped delete request' do
|
619
|
+
connection.should_receive(:execute).
|
620
|
+
with "DELETE FROM posts WHERE ? IN (?)", :id, [1, 2]
|
621
|
+
scope.delete_all
|
622
|
+
end
|
623
|
+
|
624
|
+
end
|
625
|
+
|
626
|
+
context 'with scope selecting on non-IDs' do
|
627
|
+
let(:scope) { Post.where(:published => false) }
|
628
|
+
|
629
|
+
it 'should perform "subquery" and issue update' do
|
630
|
+
connection.stub(:execute).
|
631
|
+
with("SELECT ? FROM posts WHERE ? = ?", [:id], :published, false).
|
632
|
+
and_return result_stub({:id => 1}, {:id => 2})
|
633
|
+
|
634
|
+
connection.should_receive(:execute).
|
635
|
+
with "DELETE FROM posts WHERE ? IN (?)", :id, [1, 2]
|
636
|
+
|
637
|
+
scope.delete_all
|
638
|
+
end
|
639
|
+
end
|
640
|
+
|
641
|
+
context 'with scope selecting multiple values on non-key column' do
|
642
|
+
let(:scope) { Post.where(:title => %w(Cequel Cassandra)) }
|
643
|
+
|
644
|
+
it 'should perform multiple subqueries and execute single update on returned keys' do
|
645
|
+
connection.stub(:execute).
|
646
|
+
with("SELECT ? FROM posts WHERE ? = ?", [:id], :title, 'Cequel').
|
647
|
+
and_return result_stub({:id => 1}, {:id => 2})
|
648
|
+
|
649
|
+
connection.stub(:execute).
|
650
|
+
with("SELECT ? FROM posts WHERE ? = ?", [:id], :title, 'Cassandra').
|
651
|
+
and_return result_stub({:id => 3}, {:id => 4})
|
652
|
+
|
653
|
+
connection.should_receive(:execute).
|
654
|
+
with "DELETE FROM posts WHERE ? IN (?)", :id, [1, 2, 3, 4]
|
655
|
+
|
656
|
+
scope.delete_all
|
657
|
+
end
|
658
|
+
end
|
659
|
+
end
|
660
|
+
|
661
|
+
describe '::default_scope' do
|
662
|
+
it 'should include in scope by default' do
|
663
|
+
connection.should_receive(:execute).
|
664
|
+
with("SELECT * FROM blogs LIMIT 100").and_return result_stub
|
665
|
+
|
666
|
+
Blog.all.to_a
|
667
|
+
end
|
668
|
+
|
669
|
+
it 'should override as with normal scope' do
|
670
|
+
connection.should_receive(:execute).
|
671
|
+
with("SELECT * FROM blogs LIMIT 1000").and_return result_stub
|
672
|
+
|
673
|
+
Blog.limit(1000).to_a
|
674
|
+
end
|
675
|
+
end
|
676
|
+
|
677
|
+
end
|