cequel 0.0.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/lib/cequel.rb +16 -0
  2. data/lib/cequel/batch.rb +58 -0
  3. data/lib/cequel/cql_row_specification.rb +22 -0
  4. data/lib/cequel/data_set.rb +346 -0
  5. data/lib/cequel/errors.rb +4 -0
  6. data/lib/cequel/keyspace.rb +106 -0
  7. data/lib/cequel/model.rb +95 -0
  8. data/lib/cequel/model/associations.rb +120 -0
  9. data/lib/cequel/model/callbacks.rb +32 -0
  10. data/lib/cequel/model/class_internals.rb +48 -0
  11. data/lib/cequel/model/column.rb +20 -0
  12. data/lib/cequel/model/dictionary.rb +202 -0
  13. data/lib/cequel/model/dirty.rb +53 -0
  14. data/lib/cequel/model/dynamic.rb +31 -0
  15. data/lib/cequel/model/errors.rb +13 -0
  16. data/lib/cequel/model/inheritable.rb +48 -0
  17. data/lib/cequel/model/instance_internals.rb +23 -0
  18. data/lib/cequel/model/local_association.rb +42 -0
  19. data/lib/cequel/model/magic.rb +79 -0
  20. data/lib/cequel/model/mass_assignment_security.rb +21 -0
  21. data/lib/cequel/model/naming.rb +17 -0
  22. data/lib/cequel/model/observer.rb +42 -0
  23. data/lib/cequel/model/persistence.rb +173 -0
  24. data/lib/cequel/model/properties.rb +143 -0
  25. data/lib/cequel/model/railtie.rb +33 -0
  26. data/lib/cequel/model/remote_association.rb +40 -0
  27. data/lib/cequel/model/scope.rb +362 -0
  28. data/lib/cequel/model/scoped.rb +50 -0
  29. data/lib/cequel/model/subclass_internals.rb +45 -0
  30. data/lib/cequel/model/timestamps.rb +52 -0
  31. data/lib/cequel/model/translation.rb +17 -0
  32. data/lib/cequel/model/validations.rb +50 -0
  33. data/lib/cequel/new_relic_instrumentation.rb +22 -0
  34. data/lib/cequel/row_specification.rb +63 -0
  35. data/lib/cequel/statement.rb +23 -0
  36. data/lib/cequel/version.rb +3 -0
  37. data/spec/environment.rb +3 -0
  38. data/spec/examples/data_set_spec.rb +382 -0
  39. data/spec/examples/keyspace_spec.rb +63 -0
  40. data/spec/examples/model/associations_spec.rb +109 -0
  41. data/spec/examples/model/callbacks_spec.rb +79 -0
  42. data/spec/examples/model/dictionary_spec.rb +413 -0
  43. data/spec/examples/model/dirty_spec.rb +39 -0
  44. data/spec/examples/model/dynamic_spec.rb +41 -0
  45. data/spec/examples/model/inheritable_spec.rb +45 -0
  46. data/spec/examples/model/magic_spec.rb +199 -0
  47. data/spec/examples/model/mass_assignment_security_spec.rb +13 -0
  48. data/spec/examples/model/naming_spec.rb +9 -0
  49. data/spec/examples/model/observer_spec.rb +86 -0
  50. data/spec/examples/model/persistence_spec.rb +201 -0
  51. data/spec/examples/model/properties_spec.rb +81 -0
  52. data/spec/examples/model/scope_spec.rb +677 -0
  53. data/spec/examples/model/serialization_spec.rb +20 -0
  54. data/spec/examples/model/spec_helper.rb +12 -0
  55. data/spec/examples/model/timestamps_spec.rb +52 -0
  56. data/spec/examples/model/translation_spec.rb +23 -0
  57. data/spec/examples/model/validations_spec.rb +86 -0
  58. data/spec/examples/spec_helper.rb +9 -0
  59. data/spec/models/asset.rb +21 -0
  60. data/spec/models/asset_observer.rb +5 -0
  61. data/spec/models/blog.rb +14 -0
  62. data/spec/models/blog_posts.rb +6 -0
  63. data/spec/models/category.rb +9 -0
  64. data/spec/models/comment.rb +12 -0
  65. data/spec/models/photo.rb +5 -0
  66. data/spec/models/post.rb +88 -0
  67. data/spec/models/post_comments.rb +14 -0
  68. data/spec/models/post_observer.rb +43 -0
  69. data/spec/support/helpers.rb +26 -0
  70. data/spec/support/result_stub.rb +27 -0
  71. 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