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.
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