cequel 1.0.0.pre.5 → 1.0.0.pre.6
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.
- 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
|