cequel 1.0.0.pre.5 → 1.0.0.pre.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/cequel/metal/batch.rb +4 -2
- data/lib/cequel/metal/keyspace.rb +8 -7
- data/lib/cequel/metal/statement.rb +8 -3
- data/lib/cequel/record.rb +1 -0
- data/lib/cequel/record/callbacks.rb +2 -2
- data/lib/cequel/record/lazy_record_collection.rb +44 -0
- data/lib/cequel/record/persistence.rb +19 -10
- data/lib/cequel/record/properties.rb +4 -3
- data/lib/cequel/record/record_set.rb +75 -37
- data/lib/cequel/record/schema.rb +1 -1
- data/lib/cequel/version.rb +1 -1
- data/spec/examples/record/callbacks_spec.rb +33 -0
- data/spec/examples/record/record_set_spec.rb +209 -68
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 720dcb8eb0924dd3f972efba9e93c2d96ae046ab
|
4
|
+
data.tar.gz: 36b08070aa03e28e6f0748e1c6a375e2ca57822f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c51e2ab0b17869ca3f6720e1c74b78c6f116ac705cce8aac49925750e4204012a7701ea1ed6ce4b51702a9f0a88ed2aad3d39609e5b57080ca9d2be1b0913fa6
|
7
|
+
data.tar.gz: 1dae89c0e31809ee91f54a137ca22396d8c591b5ebdf85efe9091ef53143674a71d9873b3b84b562c2deeb5eacad8ffd8dfe25d4fcb4706489656c5307e7ec81
|
data/lib/cequel/metal/batch.rb
CHANGED
@@ -43,7 +43,10 @@ module Cequel
|
|
43
43
|
#
|
44
44
|
def apply
|
45
45
|
return if @statement_count.zero?
|
46
|
-
@
|
46
|
+
if @statement_count > 1
|
47
|
+
@statement.prepend("BEGIN BATCH\n")
|
48
|
+
@statement.append("APPLY BATCH\n")
|
49
|
+
end
|
47
50
|
@keyspace.execute(*@statement.args)
|
48
51
|
end
|
49
52
|
|
@@ -51,7 +54,6 @@ module Cequel
|
|
51
54
|
|
52
55
|
def reset
|
53
56
|
@statement = Statement.new
|
54
|
-
@statement.append("BEGIN BATCH\n")
|
55
57
|
@statement_count = 0
|
56
58
|
end
|
57
59
|
|
@@ -144,13 +144,14 @@ module Cequel
|
|
144
144
|
# end
|
145
145
|
#
|
146
146
|
def batch(options = {})
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
147
|
+
return yield if get_batch
|
148
|
+
begin
|
149
|
+
new_batch = Batch.new(self, options)
|
150
|
+
set_batch(new_batch)
|
151
|
+
yield.tap { new_batch.apply }
|
152
|
+
ensure
|
153
|
+
set_batch(nil)
|
154
|
+
end
|
154
155
|
end
|
155
156
|
|
156
157
|
private
|
@@ -3,14 +3,19 @@ module Cequel
|
|
3
3
|
module Metal
|
4
4
|
|
5
5
|
class Statement
|
6
|
-
attr_reader :bind_vars
|
6
|
+
attr_reader :bind_vars, :length
|
7
7
|
|
8
8
|
def initialize
|
9
|
-
@cql, @bind_vars =
|
9
|
+
@cql, @bind_vars = [], []
|
10
10
|
end
|
11
11
|
|
12
12
|
def cql
|
13
|
-
@cql.
|
13
|
+
@cql.join
|
14
|
+
end
|
15
|
+
|
16
|
+
def prepend(cql, *bind_vars)
|
17
|
+
@cql.unshift(cql)
|
18
|
+
@bind_vars.unshift(*bind_vars)
|
14
19
|
end
|
15
20
|
|
16
21
|
def append(cql, *bind_vars)
|
data/lib/cequel/record.rb
CHANGED
@@ -8,6 +8,7 @@ require 'cequel/record/collection'
|
|
8
8
|
require 'cequel/record/persistence'
|
9
9
|
require 'cequel/record/record_set'
|
10
10
|
require 'cequel/record/bound'
|
11
|
+
require 'cequel/record/lazy_record_collection'
|
11
12
|
require 'cequel/record/scoped'
|
12
13
|
require 'cequel/record/secondary_indexes'
|
13
14
|
require 'cequel/record/associations'
|
@@ -12,11 +12,11 @@ module Cequel
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def save(options = {})
|
15
|
-
run_callbacks(:save) { super }
|
15
|
+
connection.batch { run_callbacks(:save) { super }}
|
16
16
|
end
|
17
17
|
|
18
18
|
def destroy
|
19
|
-
run_callbacks(:destroy) { super }
|
19
|
+
connection.batch { run_callbacks(:destroy) { super }}
|
20
20
|
end
|
21
21
|
|
22
22
|
protected
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Cequel
|
2
|
+
|
3
|
+
module Record
|
4
|
+
|
5
|
+
class LazyRecordCollection < DelegateClass(Array)
|
6
|
+
|
7
|
+
def initialize(record_set)
|
8
|
+
raise ArgumentError if record_set.nil?
|
9
|
+
|
10
|
+
exploded_key_attributes = [{}].tap do |all_key_attributes|
|
11
|
+
record_set.key_columns.zip(record_set.scoped_key_attributes.values) do |column, values|
|
12
|
+
all_key_attributes.replace(Array(values).flat_map do |value|
|
13
|
+
all_key_attributes.map do |key_attributes|
|
14
|
+
key_attributes.merge(column.name => value)
|
15
|
+
end
|
16
|
+
end)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
unloaded_records = exploded_key_attributes.map do |key_attributes|
|
21
|
+
record_set.target_class.new_empty(key_attributes, self)
|
22
|
+
end
|
23
|
+
|
24
|
+
super(unloaded_records)
|
25
|
+
@record_set = record_set
|
26
|
+
end
|
27
|
+
|
28
|
+
def load!
|
29
|
+
records_by_identity = index_by { |record| record.key_values }
|
30
|
+
|
31
|
+
record_set.find_each_row do |row|
|
32
|
+
identity = row.values_at(*record_set.key_column_names)
|
33
|
+
records_by_identity[identity].hydrate(row)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
attr_reader :record_set
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -17,7 +17,7 @@ module Cequel
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def hydrate(row)
|
20
|
-
new_empty
|
20
|
+
new_empty(row).__send__(:hydrated!)
|
21
21
|
end
|
22
22
|
|
23
23
|
end
|
@@ -42,11 +42,7 @@ module Cequel
|
|
42
42
|
|
43
43
|
def load
|
44
44
|
assert_keys_present!
|
45
|
-
unless loaded?
|
46
|
-
row = metal_scope.first
|
47
|
-
hydrate(row) unless row.nil?
|
48
|
-
collection_proxies.each_value { |collection| collection.loaded! }
|
49
|
-
end
|
45
|
+
record_collection.load! unless loaded?
|
50
46
|
self
|
51
47
|
end
|
52
48
|
|
@@ -96,14 +92,22 @@ module Cequel
|
|
96
92
|
!persisted?
|
97
93
|
end
|
98
94
|
|
95
|
+
def hydrate(row)
|
96
|
+
@attributes = row
|
97
|
+
hydrated!
|
98
|
+
self
|
99
|
+
end
|
100
|
+
|
99
101
|
protected
|
100
102
|
|
101
103
|
def persisted!
|
102
104
|
@persisted = true
|
105
|
+
self
|
103
106
|
end
|
104
107
|
|
105
108
|
def transient!
|
106
109
|
@persisted = false
|
110
|
+
self
|
107
111
|
end
|
108
112
|
|
109
113
|
def create
|
@@ -159,8 +163,13 @@ module Cequel
|
|
159
163
|
end
|
160
164
|
end
|
161
165
|
|
162
|
-
def
|
163
|
-
@
|
166
|
+
def record_collection
|
167
|
+
@record_collection ||=
|
168
|
+
LazyRecordCollection.new(self.class.at(*key_values)).
|
169
|
+
tap { |set| set.__setobj__([self]) }
|
170
|
+
end
|
171
|
+
|
172
|
+
def hydrated!
|
164
173
|
loaded!
|
165
174
|
persisted!
|
166
175
|
self
|
@@ -169,11 +178,11 @@ module Cequel
|
|
169
178
|
def loaded!
|
170
179
|
@loaded = true
|
171
180
|
collection_proxies.each_value { |collection| collection.loaded! }
|
181
|
+
self
|
172
182
|
end
|
173
183
|
|
174
184
|
def metal_scope
|
175
|
-
connection[table_name].
|
176
|
-
where(key_attributes)
|
185
|
+
connection[table_name].where(key_attributes)
|
177
186
|
end
|
178
187
|
|
179
188
|
def attributes_for_create
|
@@ -111,9 +111,10 @@ module Cequel
|
|
111
111
|
|
112
112
|
end
|
113
113
|
|
114
|
-
|
115
|
-
|
116
|
-
|
114
|
+
# FIXME this isn't empty anymore! Rethink.
|
115
|
+
def initialize(attributes = {}, record_collection = nil)
|
116
|
+
@attributes, @record_collection = attributes, record_collection
|
117
|
+
@collection_proxies = {}
|
117
118
|
end
|
118
119
|
|
119
120
|
def attribute_names
|
@@ -12,10 +12,13 @@ module Cequel
|
|
12
12
|
{:scoped_key_values => [], :select_columns => []}
|
13
13
|
end
|
14
14
|
|
15
|
-
|
15
|
+
attr_reader :target_class
|
16
|
+
attr_writer :unloaded_records
|
17
|
+
|
18
|
+
def initialize(target_class, attributes = {})
|
16
19
|
attributes = self.class.default_attributes.merge!(attributes)
|
17
|
-
@
|
18
|
-
super(
|
20
|
+
@target_class, @attributes = target_class, attributes
|
21
|
+
super(target_class)
|
19
22
|
end
|
20
23
|
|
21
24
|
def all
|
@@ -32,11 +35,11 @@ module Cequel
|
|
32
35
|
end
|
33
36
|
|
34
37
|
def where(column_name, value)
|
35
|
-
column =
|
38
|
+
column = target_class.reflect_on_column(column_name)
|
36
39
|
raise IllegalQuery,
|
37
40
|
"Can't scope by more than one indexed column in the same query" if scoped_indexed_column
|
38
41
|
raise ArgumentError,
|
39
|
-
"No column #{column_name} configured for #{
|
42
|
+
"No column #{column_name} configured for #{target_class.name}" unless column
|
40
43
|
raise ArgumentError,
|
41
44
|
"Use the `at` method to restrict scope by primary key" unless column.data_column?
|
42
45
|
raise ArgumentError,
|
@@ -45,26 +48,20 @@ module Cequel
|
|
45
48
|
end
|
46
49
|
|
47
50
|
def at(*scoped_key_values)
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
attributes[:scoped_key_values].concat(type_cast_key_values)
|
52
|
-
end
|
51
|
+
warn "`at` is deprecated. Use `[]` instead"
|
52
|
+
scoped_key_values.
|
53
|
+
inject(self) { |record_set, key_value| record_set[key_value] }
|
53
54
|
end
|
54
55
|
|
55
|
-
def [](
|
56
|
-
|
56
|
+
def [](*new_scoped_key_values)
|
57
|
+
new_scoped_key_values =
|
58
|
+
new_scoped_key_values.map(&method(:cast_range_key))
|
57
59
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
clazz.key_column_names.zip(key_values) do |key_name, key_value|
|
64
|
-
attributes[key_name] = key_value
|
65
|
-
end
|
66
|
-
clazz.new_empty { @attributes = attributes }
|
67
|
-
end
|
60
|
+
new_scoped_key_values =
|
61
|
+
new_scoped_key_values.first if new_scoped_key_values.one?
|
62
|
+
|
63
|
+
scoped { |attributes| attributes[:scoped_key_values] <<
|
64
|
+
new_scoped_key_values }.resolve_if_fully_specified
|
68
65
|
end
|
69
66
|
|
70
67
|
def find(*scoped_key_values)
|
@@ -72,7 +69,7 @@ module Cequel
|
|
72
69
|
end
|
73
70
|
|
74
71
|
def /(scoped_key_value)
|
75
|
-
|
72
|
+
self[scoped_key_value]
|
76
73
|
end
|
77
74
|
|
78
75
|
def after(start_key)
|
@@ -91,7 +88,7 @@ module Cequel
|
|
91
88
|
end
|
92
89
|
|
93
90
|
def from(start_key)
|
94
|
-
unless
|
91
|
+
unless partition_specified?
|
95
92
|
raise IllegalQuery,
|
96
93
|
"Can't construct exclusive range on partition key #{range_key_name}"
|
97
94
|
end
|
@@ -99,7 +96,7 @@ module Cequel
|
|
99
96
|
end
|
100
97
|
|
101
98
|
def upto(end_key)
|
102
|
-
unless
|
99
|
+
unless partition_specified?
|
103
100
|
raise IllegalQuery,
|
104
101
|
"Can't construct exclusive range on partition key #{range_key_name}"
|
105
102
|
end
|
@@ -107,7 +104,7 @@ module Cequel
|
|
107
104
|
end
|
108
105
|
|
109
106
|
def reverse
|
110
|
-
unless
|
107
|
+
unless partition_specified?
|
111
108
|
raise IllegalQuery,
|
112
109
|
"Can't reverse without scoping to partition key #{range_key_name}"
|
113
110
|
end
|
@@ -134,7 +131,7 @@ module Cequel
|
|
134
131
|
|
135
132
|
def find_each(options = {})
|
136
133
|
return enum_for(:find_each, options) unless block_given?
|
137
|
-
find_each_row(options) { |row| yield
|
134
|
+
find_each_row(options) { |row| yield target_class.hydrate(row) }
|
138
135
|
end
|
139
136
|
|
140
137
|
def find_each_row(options = {}, &block)
|
@@ -211,11 +208,11 @@ module Cequel
|
|
211
208
|
end
|
212
209
|
|
213
210
|
def scoped_key_columns
|
214
|
-
|
211
|
+
target_class.key_columns.first(scoped_key_values.length)
|
215
212
|
end
|
216
213
|
|
217
214
|
def unscoped_key_columns
|
218
|
-
|
215
|
+
target_class.key_columns.drop(scoped_key_values.length)
|
219
216
|
end
|
220
217
|
|
221
218
|
def unscoped_key_names
|
@@ -238,27 +235,68 @@ module Cequel
|
|
238
235
|
next_range_key_column.try(:name)
|
239
236
|
end
|
240
237
|
|
241
|
-
def
|
242
|
-
scoped_key_values.length
|
238
|
+
def fully_specified?
|
239
|
+
scoped_key_values.length == target_class.key_columns.length
|
240
|
+
end
|
241
|
+
|
242
|
+
def partition_specified?
|
243
|
+
scoped_key_values.length >= target_class.partition_key_columns.length
|
244
|
+
end
|
245
|
+
|
246
|
+
def multiple_records_specified?
|
247
|
+
scoped_key_values.any? { |values| values.is_a?(Array) }
|
248
|
+
end
|
249
|
+
|
250
|
+
def resolve_if_fully_specified
|
251
|
+
if fully_specified?
|
252
|
+
if multiple_records_specified?
|
253
|
+
LazyRecordCollection.new(select_non_collection_columns!)
|
254
|
+
else
|
255
|
+
LazyRecordCollection.new(self).first
|
256
|
+
end
|
257
|
+
else
|
258
|
+
self
|
259
|
+
end
|
243
260
|
end
|
244
261
|
|
245
262
|
# Try to order results by the first clustering column. Fall back to partition key if none exist.
|
246
263
|
def order_by_column
|
247
|
-
|
264
|
+
target_class.clustering_columns.first.name if target_class.clustering_columns.any?
|
265
|
+
end
|
266
|
+
|
267
|
+
def selects_collection_columns?
|
268
|
+
select_columns.any? do |column_name|
|
269
|
+
target_class.reflect_on_column(column_name).
|
270
|
+
is_a?(Cequel::Schema::CollectionColumn)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def select_non_collection_columns!
|
275
|
+
if selects_collection_columns?
|
276
|
+
raise ArgumentError,
|
277
|
+
"Can't scope by multiple keys when selecting a collection column."
|
278
|
+
end
|
279
|
+
if select_columns.empty?
|
280
|
+
non_collection_columns = target_class.columns.
|
281
|
+
reject { |column| column.is_a?(Cequel::Schema::CollectionColumn) }.
|
282
|
+
map { |column| column.name }
|
283
|
+
select(*non_collection_columns)
|
284
|
+
else
|
285
|
+
self
|
286
|
+
end
|
248
287
|
end
|
249
288
|
|
250
289
|
private
|
251
|
-
|
252
|
-
def_delegators :clazz, :connection
|
290
|
+
def_delegators :target_class, :connection
|
253
291
|
def_delegator :range_key_column, :cast, :cast_range_key
|
254
292
|
private :connection, :cast_range_key
|
255
293
|
|
256
294
|
def method_missing(method, *args, &block)
|
257
|
-
|
295
|
+
target_class.with_scope(self) { super }
|
258
296
|
end
|
259
297
|
|
260
298
|
def construct_data_set
|
261
|
-
data_set = connection[
|
299
|
+
data_set = connection[target_class.table_name]
|
262
300
|
data_set = data_set.limit(row_limit) if row_limit
|
263
301
|
data_set = data_set.select(*select_columns) if select_columns
|
264
302
|
if scoped_key_values
|
@@ -292,7 +330,7 @@ module Cequel
|
|
292
330
|
attributes_copy = Marshal.load(Marshal.dump(attributes))
|
293
331
|
attributes_copy.merge!(new_attributes)
|
294
332
|
attributes_copy.tap(&block) if block
|
295
|
-
RecordSet.new(
|
333
|
+
RecordSet.new(target_class, attributes_copy)
|
296
334
|
end
|
297
335
|
|
298
336
|
end
|
data/lib/cequel/record/schema.rb
CHANGED
@@ -15,7 +15,7 @@ module Cequel
|
|
15
15
|
module ClassMethods
|
16
16
|
extend Forwardable
|
17
17
|
|
18
|
-
def_delegators :table_schema, :key_columns, :key_column_names,
|
18
|
+
def_delegators :table_schema, :columns, :key_columns, :key_column_names,
|
19
19
|
:partition_key_columns, :clustering_columns, :compact_storage?
|
20
20
|
def_delegator :table_schema, :column, :reflect_on_column
|
21
21
|
|
data/lib/cequel/version.rb
CHANGED
@@ -24,6 +24,28 @@ describe Cequel::Record::Callbacks do
|
|
24
24
|
|
25
25
|
end
|
26
26
|
|
27
|
+
model :Comment do
|
28
|
+
belongs_to :post
|
29
|
+
key :id, :timeuuid, :auto => true
|
30
|
+
column :body, :text
|
31
|
+
|
32
|
+
before_save :create_post
|
33
|
+
after_save :run_instance_after_save
|
34
|
+
|
35
|
+
attr_writer :instance_after_save
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def create_post
|
40
|
+
post = Post.create!(permalink: 'autopost', title: 'Auto Post')
|
41
|
+
self.post = post
|
42
|
+
end
|
43
|
+
|
44
|
+
def run_instance_after_save
|
45
|
+
@instance_after_save.call
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
27
49
|
let(:new_post) do
|
28
50
|
Post.new do |post|
|
29
51
|
post.permalink = 'new-post'
|
@@ -83,4 +105,15 @@ describe Cequel::Record::Callbacks do
|
|
83
105
|
it { should include(:before_destroy) }
|
84
106
|
it { should include(:after_destroy) }
|
85
107
|
end
|
108
|
+
|
109
|
+
describe 'atomic writes' do
|
110
|
+
it 'should run callbacks in a logged batch' do
|
111
|
+
comment = Comment.new(:body => 'Great web site!')
|
112
|
+
comment.instance_after_save =
|
113
|
+
-> { expect { Post.find('autopost') }.
|
114
|
+
to raise_error(Cequel::Record::RecordNotFound) }
|
115
|
+
comment.save!
|
116
|
+
Post.find('autopost').title.should == 'Auto Post'
|
117
|
+
end
|
118
|
+
end
|
86
119
|
end
|
@@ -36,53 +36,92 @@ describe Cequel::Record::RecordSet do
|
|
36
36
|
column :permalink, :ascii, index: true
|
37
37
|
end
|
38
38
|
|
39
|
-
let(:subdomains) {
|
39
|
+
let(:subdomains) { blogs.map(&:subdomain) }
|
40
40
|
let(:uuids) { Array.new(2) { CassandraCQL::UUID.new }}
|
41
41
|
let(:now) { Time.at(Time.now.to_i) }
|
42
42
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
blog.description = "This is Blog number #{i}"
|
50
|
-
end.save
|
43
|
+
let(:blogs) do
|
44
|
+
3.times.map do |i|
|
45
|
+
Blog.new do |blog|
|
46
|
+
blog.subdomain = "blog-#{i}"
|
47
|
+
blog.name = "Blog #{i}"
|
48
|
+
blog.description = "This is Blog number #{i}"
|
51
49
|
end
|
50
|
+
end
|
51
|
+
end
|
52
52
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
:permalink => "cequel#{i}"
|
65
|
-
)
|
66
|
-
cequel[:posts].insert(
|
67
|
-
:blog_subdomain => 'postgres',
|
68
|
-
:permalink => "sequel#{i}",
|
69
|
-
:title => "Sequel #{i}"
|
70
|
-
)
|
71
|
-
end
|
53
|
+
let(:cassandra_posts) do
|
54
|
+
5.times.map do |i|
|
55
|
+
Post.new(
|
56
|
+
:blog_subdomain => 'cassandra',
|
57
|
+
:permalink => "cequel#{i}",
|
58
|
+
:title => "Cequel #{i}",
|
59
|
+
:body => "Post number #{i}",
|
60
|
+
:author_id => uuids[i%2]
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
72
64
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
65
|
+
let(:published_posts) do
|
66
|
+
5.times.map do |i|
|
67
|
+
PublishedPost.new(
|
68
|
+
:blog_subdomain => 'cassandra',
|
69
|
+
:published_at => max_uuid(now + (i - 4).minutes),
|
70
|
+
:permalink => "cequel#{i}"
|
71
|
+
)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
let(:postgres_posts) do
|
76
|
+
5.times.map do |i|
|
77
|
+
Post.new(
|
78
|
+
:blog_subdomain => 'postgres',
|
79
|
+
:permalink => "sequel#{i}",
|
80
|
+
:title => "Sequel #{i}"
|
81
|
+
)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
let(:mongo_posts) do
|
86
|
+
5.times.map do |i|
|
87
|
+
Post.new(
|
88
|
+
:blog_subdomain => 'mongo',
|
89
|
+
:permalink => "mongoid#{i}",
|
90
|
+
:title => "Mongoid #{i}"
|
91
|
+
)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
let(:orm_posts) do
|
96
|
+
5.times.map do |i|
|
97
|
+
Post.new(
|
98
|
+
:blog_subdomain => 'orms',
|
99
|
+
:permalink => "cequel#{i}",
|
100
|
+
:title => "Cequel ORM #{i}"
|
101
|
+
)
|
81
102
|
end
|
82
103
|
end
|
83
104
|
|
105
|
+
let(:posts) { [cassandra_posts, postgres_posts] }
|
106
|
+
|
107
|
+
let(:comments) do
|
108
|
+
5.times.map do |i|
|
109
|
+
Comment.new(
|
110
|
+
:blog_subdomain => 'cassandra',
|
111
|
+
:permalink => 'cequel0',
|
112
|
+
:id => CassandraCQL::UUID.new(Time.now - 5 + i),
|
113
|
+
:body => "Comment #{i}"
|
114
|
+
)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
let(:records) { posts }
|
119
|
+
|
120
|
+
before { cequel.batch { records.flatten.each { |record| record.save! }}}
|
121
|
+
|
84
122
|
describe '::find' do
|
85
123
|
context 'simple primary key' do
|
124
|
+
let(:records) { blogs }
|
86
125
|
subject { Blog.find('blog-0') }
|
87
126
|
|
88
127
|
its(:subdomain) { should == 'blog-0' }
|
@@ -104,6 +143,7 @@ describe Cequel::Record::RecordSet do
|
|
104
143
|
end
|
105
144
|
|
106
145
|
context 'compound primary key' do
|
146
|
+
let(:records) { cassandra_posts }
|
107
147
|
subject { Post['cassandra'].find('cequel0') }
|
108
148
|
|
109
149
|
its(:blog_subdomain) { should == 'cassandra' }
|
@@ -128,7 +168,8 @@ describe Cequel::Record::RecordSet do
|
|
128
168
|
end
|
129
169
|
|
130
170
|
describe '::[]' do
|
131
|
-
context 'simple primary key' do
|
171
|
+
context 'fully specified simple primary key' do
|
172
|
+
let(:records) { blogs }
|
132
173
|
subject { Blog['blog-0'] }
|
133
174
|
|
134
175
|
it 'should not query the database' do
|
@@ -151,7 +192,32 @@ describe Cequel::Record::RecordSet do
|
|
151
192
|
end
|
152
193
|
end
|
153
194
|
|
154
|
-
context '
|
195
|
+
context 'multiple simple primary keys' do
|
196
|
+
let(:records) { blogs }
|
197
|
+
subject { Blog['blog-0', 'blog-1'] }
|
198
|
+
|
199
|
+
it 'should return both specified records' do
|
200
|
+
subject.map(&:subdomain).should =~ %w(blog-0 blog-1)
|
201
|
+
end
|
202
|
+
|
203
|
+
it 'should not query the database' do
|
204
|
+
disallow_queries!
|
205
|
+
subject.map(&:subdomain)
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'should load value lazily' do
|
209
|
+
subject.first.name.should == 'Blog 0'
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'should load values for all referenced records on first access' do
|
213
|
+
max_statements! 1
|
214
|
+
subject.first.name.should == 'Blog 0'
|
215
|
+
subject.last.name.should == 'Blog 1'
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
context 'fully specified compound primary key' do
|
220
|
+
let(:records) { posts }
|
155
221
|
subject { Post['cassandra']['cequel0'] }
|
156
222
|
|
157
223
|
it 'should not query the database' do
|
@@ -175,61 +241,118 @@ describe Cequel::Record::RecordSet do
|
|
175
241
|
subject.body.should == 'Post number 0'
|
176
242
|
end
|
177
243
|
end
|
244
|
+
|
245
|
+
context 'fully specified compound primary key with multiple clustering columns' do
|
246
|
+
let(:records) { posts }
|
247
|
+
subject { Post['cassandra']['cequel0', 'cequel1'] }
|
248
|
+
|
249
|
+
it 'should combine partition key with each clustering column' do
|
250
|
+
disallow_queries!
|
251
|
+
subject.map(&:key_values).
|
252
|
+
should == [['cassandra', 'cequel0'], ['cassandra', 'cequel1']]
|
253
|
+
end
|
254
|
+
|
255
|
+
it 'should lazily load all records when one record accessed' do
|
256
|
+
max_statements! 1
|
257
|
+
subject.first.title.should == 'Cequel 0'
|
258
|
+
subject.second.title.should == 'Cequel 1'
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'should not allow collection columns to be selected' do
|
262
|
+
expect { Post.select(:tags)['cassandra']['cequel0', 'cequel1'] }.
|
263
|
+
to raise_error(ArgumentError)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
context 'partially specified compound primary key' do
|
268
|
+
let(:records) { posts }
|
269
|
+
it 'should create partial collection if not all keys specified' do
|
270
|
+
Post['cassandra'].find_each(:batch_size => 2).map(&:title).
|
271
|
+
should == (0...5).map { |i| "Cequel #{i}" }
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
context 'partially specified compound primary key with multiple partition keys' do
|
276
|
+
let(:records) { posts }
|
277
|
+
subject { Post['cassandra', 'postgres'] }
|
278
|
+
|
279
|
+
it 'should return scope to keys' do
|
280
|
+
subject.map { |post| post.title }.should =~ (0...5).
|
281
|
+
map { |i| ["Cequel #{i}", "Sequel #{i}"] }.flatten
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
context 'fully specified compound primary key with multiple partition keys' do
|
286
|
+
let(:records) { [posts, orm_posts] }
|
287
|
+
|
288
|
+
subject { Post['cassandra', 'orms']['cequel0'] }
|
289
|
+
|
290
|
+
it 'should return collection of unloaded models' do
|
291
|
+
disallow_queries!
|
292
|
+
subject.map(&:key_values).
|
293
|
+
should == [['cassandra', 'cequel0'], ['orms', 'cequel0']]
|
294
|
+
end
|
295
|
+
|
296
|
+
it 'should lazy-load all records when properties of one accessed' do
|
297
|
+
max_statements! 1
|
298
|
+
subject.first.title.should == 'Cequel 0'
|
299
|
+
subject.second.title.should == 'Cequel ORM 0'
|
300
|
+
end
|
301
|
+
end
|
178
302
|
end
|
179
303
|
|
180
304
|
describe '#all' do
|
305
|
+
let(:records) { blogs }
|
306
|
+
|
181
307
|
it 'should return all the records' do
|
182
308
|
Blog.all.map(&:subdomain).should =~ subdomains
|
183
309
|
end
|
184
310
|
end
|
185
311
|
|
186
312
|
describe '#find_each' do
|
313
|
+
let(:records) { [posts, blogs, mongo_posts] }
|
314
|
+
|
187
315
|
it 'should respect :batch_size argument' do
|
188
316
|
cequel.should_receive(:execute).twice.and_call_original
|
189
317
|
Blog.find_each(:batch_size => 2).map(&:subdomain).
|
190
318
|
should =~ subdomains
|
191
319
|
end
|
192
320
|
it 'should iterate over all keys' do
|
193
|
-
Post.find_each(:batch_size => 2).map(&:title).
|
194
|
-
|
321
|
+
Post.find_each(:batch_size => 2).map(&:title).should =~
|
322
|
+
(0...5).flat_map { |i| ["Cequel #{i}", "Sequel #{i}", "Mongoid #{i}"] }
|
195
323
|
end
|
196
324
|
end
|
197
325
|
|
198
|
-
describe '#
|
326
|
+
describe '#[]' do
|
199
327
|
it 'should return partial collection' do
|
200
|
-
Post
|
328
|
+
Post['cassandra'].find_each(:batch_size => 2).map(&:title).
|
201
329
|
should == (0...5).map { |i| "Cequel #{i}" }
|
202
330
|
end
|
203
331
|
|
204
332
|
it 'should cast arguments correctly' do
|
205
|
-
Post
|
333
|
+
Post['cassandra'.force_encoding('ASCII-8BIT')].
|
206
334
|
find_each(:batch_size => 2).map(&:title).
|
207
335
|
should == (0...5).map { |i| "Cequel #{i}" }
|
208
336
|
end
|
209
337
|
end
|
210
338
|
|
211
|
-
describe '#[]' do
|
212
|
-
it 'should create partial collection if not all keys specified' do
|
213
|
-
Post['cassandra'].find_each(:batch_size => 2).map(&:title).
|
214
|
-
should == (0...5).map { |i| "Cequel #{i}" }
|
215
|
-
end
|
216
|
-
end
|
217
|
-
|
218
339
|
describe '#/' do
|
219
|
-
it 'should behave like #
|
340
|
+
it 'should behave like #[]' do
|
220
341
|
(Post / 'cassandra').find_each(:batch_size => 2).map(&:title).
|
221
342
|
should == (0...5).map { |i| "Cequel #{i}" }
|
222
343
|
end
|
223
344
|
end
|
224
345
|
|
225
346
|
describe '#after' do
|
347
|
+
let(:records) { [posts, published_posts] }
|
348
|
+
|
226
349
|
it 'should return collection after given key' do
|
227
|
-
Post
|
350
|
+
Post['cassandra'].after('cequel1').map(&:title).
|
228
351
|
should == (2...5).map { |i| "Cequel #{i}" }
|
229
352
|
end
|
230
353
|
|
231
354
|
it 'should cast argument' do
|
232
|
-
Post
|
355
|
+
Post['cassandra'].after('cequel1'.force_encoding('ASCII-8BIT')).
|
233
356
|
map(&:title).should == (2...5).map { |i| "Cequel #{i}" }
|
234
357
|
end
|
235
358
|
|
@@ -240,13 +363,15 @@ describe Cequel::Record::RecordSet do
|
|
240
363
|
end
|
241
364
|
|
242
365
|
describe '#from' do
|
366
|
+
let(:records) { [posts, published_posts] }
|
367
|
+
|
243
368
|
it 'should return collection starting with given key' do
|
244
|
-
Post
|
369
|
+
Post['cassandra'].from('cequel1').map(&:title).
|
245
370
|
should == (1...5).map { |i| "Cequel #{i}" }
|
246
371
|
end
|
247
372
|
|
248
373
|
it 'should cast argument' do
|
249
|
-
Post
|
374
|
+
Post['cassandra'].from('cequel1'.force_encoding('ASCII-8BIT')).
|
250
375
|
map(&:title).should == (1...5).map { |i| "Cequel #{i}" }
|
251
376
|
end
|
252
377
|
|
@@ -262,8 +387,10 @@ describe Cequel::Record::RecordSet do
|
|
262
387
|
end
|
263
388
|
|
264
389
|
describe '#before' do
|
390
|
+
let(:records) { [posts, published_posts] }
|
391
|
+
|
265
392
|
it 'should return collection before given key' do
|
266
|
-
Post
|
393
|
+
Post['cassandra'].before('cequel3').map(&:title).
|
267
394
|
should == (0...3).map { |i| "Cequel #{i}" }
|
268
395
|
end
|
269
396
|
|
@@ -273,19 +400,21 @@ describe Cequel::Record::RecordSet do
|
|
273
400
|
end
|
274
401
|
|
275
402
|
it 'should cast argument' do
|
276
|
-
Post
|
403
|
+
Post['cassandra'].before('cequel3'.force_encoding('ASCII-8BIT')).
|
277
404
|
map(&:title).should == (0...3).map { |i| "Cequel #{i}" }
|
278
405
|
end
|
279
406
|
end
|
280
407
|
|
281
408
|
describe '#upto' do
|
409
|
+
let(:records) { [posts, published_posts] }
|
410
|
+
|
282
411
|
it 'should return collection up to given key' do
|
283
|
-
Post
|
412
|
+
Post['cassandra'].upto('cequel3').map(&:title).
|
284
413
|
should == (0..3).map { |i| "Cequel #{i}" }
|
285
414
|
end
|
286
415
|
|
287
416
|
it 'should cast argument' do
|
288
|
-
Post
|
417
|
+
Post['cassandra'].upto('cequel3'.force_encoding('ASCII-8BIT')).
|
289
418
|
map(&:title).should == (0..3).map { |i| "Cequel #{i}" }
|
290
419
|
end
|
291
420
|
|
@@ -296,19 +425,21 @@ describe Cequel::Record::RecordSet do
|
|
296
425
|
end
|
297
426
|
|
298
427
|
describe '#in' do
|
428
|
+
let(:records) { [posts, published_posts] }
|
429
|
+
|
299
430
|
it 'should return collection with inclusive upper bound' do
|
300
|
-
Post
|
431
|
+
Post['cassandra'].in('cequel1'..'cequel3').map(&:title).
|
301
432
|
should == (1..3).map { |i| "Cequel #{i}" }
|
302
433
|
end
|
303
434
|
|
304
435
|
it 'should cast arguments' do
|
305
|
-
Post
|
436
|
+
Post['cassandra'].in('cequel1'.force_encoding('ASCII-8BIT')..
|
306
437
|
'cequel3'.force_encoding('ASCII-8BIT')).
|
307
438
|
map(&:title).should == (1..3).map { |i| "Cequel #{i}" }
|
308
439
|
end
|
309
440
|
|
310
441
|
it 'should return collection with exclusive upper bound' do
|
311
|
-
Post
|
442
|
+
Post['cassandra'].in('cequel1'...'cequel3').map(&:title).
|
312
443
|
should == (1...3).map { |i| "Cequel #{i}" }
|
313
444
|
end
|
314
445
|
|
@@ -324,18 +455,20 @@ describe Cequel::Record::RecordSet do
|
|
324
455
|
end
|
325
456
|
|
326
457
|
describe '#reverse' do
|
458
|
+
let(:records) { [posts, comments] }
|
459
|
+
|
327
460
|
it 'should not call the database' do
|
328
461
|
disallow_queries!
|
329
|
-
Post
|
462
|
+
Post['cassandra'].reverse
|
330
463
|
end
|
331
464
|
|
332
465
|
it 'should return collection in reverse' do
|
333
|
-
Post
|
466
|
+
Post['cassandra'].reverse.map(&:title).
|
334
467
|
should == (0...5).map { |i| "Cequel #{i}" }.reverse
|
335
468
|
end
|
336
469
|
|
337
470
|
it 'should batch iterate over collection in reverse' do
|
338
|
-
Post
|
471
|
+
Post['cassandra'].reverse.find_each(:batch_size => 2).map(&:title).
|
339
472
|
should == (0...5).map { |i| "Cequel #{i}" }.reverse
|
340
473
|
end
|
341
474
|
|
@@ -351,16 +484,18 @@ describe Cequel::Record::RecordSet do
|
|
351
484
|
|
352
485
|
describe 'last' do
|
353
486
|
it 'should return the last instance' do
|
354
|
-
Post
|
487
|
+
Post['cassandra'].last.title.should == "Cequel 4"
|
355
488
|
end
|
356
489
|
|
357
490
|
it 'should return the last N instances if specified' do
|
358
|
-
Post
|
491
|
+
Post['cassandra'].last(3).map(&:title).
|
359
492
|
should == ["Cequel 2", "Cequel 3", "Cequel 4"]
|
360
493
|
end
|
361
494
|
end
|
362
495
|
|
363
496
|
describe '#first' do
|
497
|
+
let(:records) { blogs }
|
498
|
+
|
364
499
|
context 'with no arguments' do
|
365
500
|
it 'should return an arbitrary record' do
|
366
501
|
subdomains.should include(Blog.first.subdomain)
|
@@ -377,12 +512,16 @@ describe Cequel::Record::RecordSet do
|
|
377
512
|
end
|
378
513
|
|
379
514
|
describe '#limit' do
|
380
|
-
|
515
|
+
let(:records) { blogs }
|
516
|
+
|
517
|
+
it 'should return the number of records requested' do
|
381
518
|
Blog.limit(2).should have(2).entries
|
382
519
|
end
|
383
520
|
end
|
384
521
|
|
385
522
|
describe '#select' do
|
523
|
+
let(:records) { blogs }
|
524
|
+
|
386
525
|
context 'with no block' do
|
387
526
|
subject { Blog.select(:subdomain, :name).first }
|
388
527
|
|
@@ -435,6 +574,8 @@ describe Cequel::Record::RecordSet do
|
|
435
574
|
end
|
436
575
|
|
437
576
|
describe '#count' do
|
577
|
+
let(:records) { blogs }
|
578
|
+
|
438
579
|
it 'should count records' do
|
439
580
|
Blog.count.should == 3
|
440
581
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cequel
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.pre.
|
4
|
+
version: 1.0.0.pre.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mat Brown
|
@@ -12,7 +12,7 @@ authors:
|
|
12
12
|
autorequire:
|
13
13
|
bindir: bin
|
14
14
|
cert_chain: []
|
15
|
-
date: 2013-11-
|
15
|
+
date: 2013-11-09 00:00:00.000000000 Z
|
16
16
|
dependencies:
|
17
17
|
- !ruby/object:Gem::Dependency
|
18
18
|
name: activesupport
|
@@ -62,14 +62,14 @@ dependencies:
|
|
62
62
|
requirements:
|
63
63
|
- - ~>
|
64
64
|
- !ruby/object:Gem::Version
|
65
|
-
version:
|
65
|
+
version: '1.1'
|
66
66
|
type: :runtime
|
67
67
|
prerelease: false
|
68
68
|
version_requirements: !ruby/object:Gem::Requirement
|
69
69
|
requirements:
|
70
70
|
- - ~>
|
71
71
|
- !ruby/object:Gem::Version
|
72
|
-
version:
|
72
|
+
version: '1.1'
|
73
73
|
- !ruby/object:Gem::Dependency
|
74
74
|
name: i18n
|
75
75
|
requirement: !ruby/object:Gem::Requirement
|
@@ -160,6 +160,7 @@ files:
|
|
160
160
|
- lib/cequel/record/dirty.rb
|
161
161
|
- lib/cequel/record/errors.rb
|
162
162
|
- lib/cequel/record/has_many_association.rb
|
163
|
+
- lib/cequel/record/lazy_record_collection.rb
|
163
164
|
- lib/cequel/record/mass_assignment.rb
|
164
165
|
- lib/cequel/record/persistence.rb
|
165
166
|
- lib/cequel/record/properties.rb
|