cequel 1.0.0.pre.1 → 1.0.0.pre.2
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.rb +2 -0
- data/lib/cequel/model.rb +2 -0
- data/lib/cequel/model/base.rb +10 -0
- data/lib/cequel/model/collection.rb +5 -1
- data/lib/cequel/model/dirty.rb +62 -0
- data/lib/cequel/model/errors.rb +1 -0
- data/lib/cequel/model/record_set.rb +61 -72
- data/lib/cequel/model/schema.rb +2 -1
- data/lib/cequel/model/scoped.rb +1 -1
- data/lib/cequel/model/secondary_indexes.rb +31 -0
- data/lib/cequel/model/validations.rb +1 -1
- data/lib/cequel/schema/column.rb +18 -2
- data/lib/cequel/schema/table.rb +6 -6
- data/lib/cequel/schema/table_synchronizer.rb +3 -3
- data/lib/cequel/schema/table_writer.rb +2 -1
- data/lib/cequel/util.rb +46 -0
- data/lib/cequel/version.rb +1 -1
- data/spec/examples/model/dirty_spec.rb +68 -0
- data/spec/examples/model/record_set_spec.rb +33 -2
- data/spec/examples/model/schema_spec.rb +1 -1
- data/spec/examples/model/secondary_index_spec.rb +45 -0
- data/spec/examples/model/spec_helper.rb +0 -1
- data/spec/examples/schema/table_reader_spec.rb +8 -8
- data/spec/examples/schema/table_writer_spec.rb +8 -8
- metadata +14 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7888f93ffd3d74bdfef143a9c8d18c6c9f2f4ba1
|
4
|
+
data.tar.gz: 831e5a90a03cf1c22ddf08421284107254aa59a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 41a8585f9c78171c34944551914e4e06b44fabe615b9192901782b6653be345314c63ad4e4b5b048818bb10e1d4085aad24006285a9bd573489153b24719ccc3
|
7
|
+
data.tar.gz: 90297a4b794c2eb265ffdc7659f80c1b90ef8870c1d04ff910d4d015e425a47395b8151fa72c8cf85266538e5e7fe3c495b9433751776eb727ee5983f986314c
|
data/lib/cequel.rb
CHANGED
data/lib/cequel/model.rb
CHANGED
@@ -8,6 +8,7 @@ require 'cequel/model/collection'
|
|
8
8
|
require 'cequel/model/persistence'
|
9
9
|
require 'cequel/model/record_set'
|
10
10
|
require 'cequel/model/scoped'
|
11
|
+
require 'cequel/model/secondary_indexes'
|
11
12
|
require 'cequel/model/associations'
|
12
13
|
require 'cequel/model/association_collection'
|
13
14
|
require 'cequel/model/belongs_to_association'
|
@@ -15,6 +16,7 @@ require 'cequel/model/has_many_association'
|
|
15
16
|
require 'cequel/model/mass_assignment'
|
16
17
|
require 'cequel/model/callbacks'
|
17
18
|
require 'cequel/model/validations'
|
19
|
+
require 'cequel/model/dirty'
|
18
20
|
|
19
21
|
require 'cequel/model/base'
|
20
22
|
|
data/lib/cequel/model/base.rb
CHANGED
@@ -9,9 +9,11 @@ module Cequel
|
|
9
9
|
include Cequel::Model::Persistence
|
10
10
|
include Cequel::Model::Associations
|
11
11
|
extend Cequel::Model::Scoped
|
12
|
+
extend Cequel::Model::SecondaryIndexes
|
12
13
|
include Cequel::Model::MassAssignment
|
13
14
|
include Cequel::Model::Callbacks
|
14
15
|
include Cequel::Model::Validations
|
16
|
+
include Cequel::Model::Dirty
|
15
17
|
extend ActiveModel::Naming
|
16
18
|
include ActiveModel::Serializers::JSON
|
17
19
|
include ActiveModel::Serializers::Xml
|
@@ -41,6 +43,14 @@ module Cequel
|
|
41
43
|
instance_eval(&block) if block
|
42
44
|
end
|
43
45
|
|
46
|
+
def ==(other)
|
47
|
+
if key_values.any? { |value| value.nil? }
|
48
|
+
super
|
49
|
+
else
|
50
|
+
self.class == other.class && key_values == other.key_values
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
44
54
|
def inspect
|
45
55
|
inspected_attributes = attributes.each_pair.map do |attr, value|
|
46
56
|
inspected_value = value.is_a?(CassandraCQL::UUID) ?
|
@@ -10,6 +10,7 @@ module Cequel
|
|
10
10
|
extend Forwardable
|
11
11
|
|
12
12
|
def_delegators :@model, :loaded?, :updater, :deleter
|
13
|
+
def_delegators :__getobj__, :clone, :dup
|
13
14
|
|
14
15
|
attr_reader :column_name
|
15
16
|
|
@@ -50,7 +51,9 @@ module Cequel
|
|
50
51
|
private
|
51
52
|
|
52
53
|
def to_modify(&block)
|
53
|
-
if loaded?
|
54
|
+
if loaded?
|
55
|
+
@model.__send__("#{@column_name}_will_change!")
|
56
|
+
block.()
|
54
57
|
else modifications << block
|
55
58
|
end
|
56
59
|
self
|
@@ -175,6 +178,7 @@ module Cequel
|
|
175
178
|
updater.set_add(column_name, object)
|
176
179
|
to_modify { super }
|
177
180
|
end
|
181
|
+
alias_method :<<, :add
|
178
182
|
|
179
183
|
def clear
|
180
184
|
deleter.delete_columns(column_name)
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Cequel
|
2
|
+
|
3
|
+
module Model
|
4
|
+
|
5
|
+
module Dirty
|
6
|
+
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included { include ActiveModel::Dirty }
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
def key(name, *)
|
14
|
+
define_attribute_method(name)
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def column(name, *)
|
19
|
+
define_attribute_method(name)
|
20
|
+
super
|
21
|
+
end
|
22
|
+
|
23
|
+
def set(name, *)
|
24
|
+
define_attribute_method(name)
|
25
|
+
super
|
26
|
+
end
|
27
|
+
|
28
|
+
def list(name, *)
|
29
|
+
define_attribute_method(name)
|
30
|
+
super
|
31
|
+
end
|
32
|
+
|
33
|
+
def map(name, *)
|
34
|
+
define_attribute_method(name)
|
35
|
+
super
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
def save(options = {})
|
41
|
+
super.tap do |success|
|
42
|
+
if success
|
43
|
+
@previously_changed = changes
|
44
|
+
@changed_attributes.clear
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def write_attribute(name, value)
|
52
|
+
if loaded? && value != read_attribute(name)
|
53
|
+
__send__("#{name}_will_change!")
|
54
|
+
end
|
55
|
+
super
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
data/lib/cequel/model/errors.rb
CHANGED
@@ -5,14 +5,26 @@ module Cequel
|
|
5
5
|
class RecordSet
|
6
6
|
|
7
7
|
extend Forwardable
|
8
|
+
extend Cequel::Util::HashAccessors
|
8
9
|
include Enumerable
|
9
10
|
|
10
11
|
Bound = Struct.new(:value, :inclusive)
|
11
12
|
|
12
|
-
def
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
def self.default_attributes
|
14
|
+
{:scoped_key_values => [], :select_columns => []}
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.create(clazz, attributes = {})
|
18
|
+
attributes = default_attributes.merge!(attributes)
|
19
|
+
if attributes[:scoped_key_values].length >= clazz.partition_key_columns.length
|
20
|
+
SinglePartitionRecordSet.new(clazz, attributes)
|
21
|
+
else
|
22
|
+
RecordSet.new(clazz, attributes)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(clazz, attributes)
|
27
|
+
@clazz, @attributes = clazz, attributes
|
16
28
|
end
|
17
29
|
|
18
30
|
def all
|
@@ -21,18 +33,29 @@ module Cequel
|
|
21
33
|
|
22
34
|
def select(*columns)
|
23
35
|
return super if block_given?
|
24
|
-
scoped { |
|
36
|
+
scoped { |attributes| attributes[:select_columns].concat(columns) }
|
25
37
|
end
|
26
38
|
|
27
39
|
def limit(count)
|
28
|
-
scoped
|
40
|
+
scoped(:row_limit => count)
|
41
|
+
end
|
42
|
+
|
43
|
+
def where(column_name, value)
|
44
|
+
column = clazz.table_schema.column(column_name)
|
45
|
+
raise IllegalQuery,
|
46
|
+
"Can't scope by more than one indexed column in the same query" if scoped_indexed_column
|
47
|
+
raise ArgumentError,
|
48
|
+
"No column #{column_name} configured for #{clazz.name}" unless column
|
49
|
+
raise ArgumentError,
|
50
|
+
"Use the `at` method to restrict scope by primary key" unless column.data_column?
|
51
|
+
raise ArgumentError,
|
52
|
+
"Can't scope by non-indexed column #{column_name}" unless column.indexed?
|
53
|
+
scoped(scoped_indexed_column: {column_name => value})
|
29
54
|
end
|
30
55
|
|
31
56
|
def at(*scoped_key_values)
|
32
|
-
|
33
|
-
|
34
|
-
scoped(record_set_class) do |record_set|
|
35
|
-
record_set.scoped_key_values.concat(scoped_key_values)
|
57
|
+
scoped do |attributes|
|
58
|
+
attributes[:scoped_key_values].concat(scoped_key_values)
|
36
59
|
end
|
37
60
|
end
|
38
61
|
|
@@ -58,22 +81,18 @@ module Cequel
|
|
58
81
|
end
|
59
82
|
|
60
83
|
def after(start_key)
|
61
|
-
scoped
|
62
|
-
record_set.lower_bound = Bound.new(start_key, false)
|
63
|
-
end
|
84
|
+
scoped(lower_bound: Bound.new(start_key, false))
|
64
85
|
end
|
65
86
|
|
66
87
|
def before(end_key)
|
67
|
-
scoped
|
68
|
-
record_set.upper_bound = Bound.new(end_key, false)
|
69
|
-
end
|
88
|
+
scoped(upper_bound: Bound.new(end_key, false))
|
70
89
|
end
|
71
90
|
|
72
91
|
def in(range)
|
73
|
-
scoped
|
74
|
-
|
75
|
-
|
76
|
-
|
92
|
+
scoped(
|
93
|
+
lower_bound: Bound.new(range.first, true),
|
94
|
+
upper_bound: Bound.new(range.last, !range.exclude_end?)
|
95
|
+
)
|
77
96
|
end
|
78
97
|
|
79
98
|
def first(count = nil)
|
@@ -116,29 +135,17 @@ module Cequel
|
|
116
135
|
end
|
117
136
|
end
|
118
137
|
|
119
|
-
protected
|
120
|
-
attr_accessor :row_limit
|
121
|
-
attr_reader :select_columns, :scoped_key_values,
|
122
|
-
:lower_bound, :upper_bound
|
123
|
-
|
124
|
-
def reversed?
|
125
|
-
false
|
126
|
-
end
|
127
|
-
|
128
|
-
def lower_bound=(bound)
|
129
|
-
@lower_bound = bound
|
130
|
-
end
|
131
|
-
|
132
|
-
def upper_bound=(bound)
|
133
|
-
@upper_bound = bound
|
134
|
-
end
|
135
|
-
|
136
138
|
def data_set
|
137
139
|
@data_set ||= construct_data_set
|
138
140
|
end
|
139
141
|
|
142
|
+
protected
|
143
|
+
attr_reader :attributes
|
144
|
+
hattr_reader :attributes, :select_columns, :scoped_key_values, :row_limit,
|
145
|
+
:lower_bound, :upper_bound, :scoped_indexed_column
|
146
|
+
|
140
147
|
def next_batch_from(row)
|
141
|
-
|
148
|
+
after(row[range_key_name])
|
142
149
|
end
|
143
150
|
|
144
151
|
def find_nested_batches_from(row, options, &block)
|
@@ -176,23 +183,10 @@ module Cequel
|
|
176
183
|
scoped_key_columns.map { |column| column.name }
|
177
184
|
end
|
178
185
|
|
179
|
-
def chain_from(collection)
|
180
|
-
@select_columns = collection.select_columns.dup
|
181
|
-
@scoped_key_values = collection.scoped_key_values.dup
|
182
|
-
@lower_bound = collection.lower_bound
|
183
|
-
@upper_bound = collection.upper_bound
|
184
|
-
@row_limit = collection.row_limit
|
185
|
-
self
|
186
|
-
end
|
187
|
-
|
188
186
|
private
|
189
187
|
attr_reader :clazz
|
190
188
|
def_delegators :clazz, :connection
|
191
189
|
|
192
|
-
def scoped(record_set_class = self.class, &block)
|
193
|
-
record_set_class.new(clazz).chain_from(self).tap(&block)
|
194
|
-
end
|
195
|
-
|
196
190
|
def next_key_column
|
197
191
|
clazz.key_columns[scoped_key_values.length + 1]
|
198
192
|
end
|
@@ -217,6 +211,7 @@ module Cequel
|
|
217
211
|
fragment = construct_bound_fragment(upper_bound, '<')
|
218
212
|
data_set = data_set.where(fragment, upper_bound.value)
|
219
213
|
end
|
214
|
+
data_set = data_set.where(scoped_indexed_column) if scoped_indexed_column
|
220
215
|
data_set
|
221
216
|
end
|
222
217
|
|
@@ -225,43 +220,41 @@ module Cequel
|
|
225
220
|
"TOKEN(#{range_key_name}) #{operator} TOKEN(?)"
|
226
221
|
end
|
227
222
|
|
223
|
+
def scoped(new_attributes = {}, &block)
|
224
|
+
attributes_copy = Marshal.load(Marshal.dump(attributes))
|
225
|
+
attributes_copy.merge!(new_attributes)
|
226
|
+
attributes_copy.tap(&block) if block
|
227
|
+
RecordSet.create(clazz, attributes_copy)
|
228
|
+
end
|
229
|
+
|
228
230
|
end
|
229
231
|
|
230
|
-
class
|
232
|
+
class SinglePartitionRecordSet < RecordSet
|
231
233
|
|
232
|
-
|
233
|
-
super
|
234
|
-
@reversed = false
|
235
|
-
end
|
234
|
+
hattr_inquirer :attributes, :reversed
|
236
235
|
|
237
236
|
def from(start_key)
|
238
|
-
scoped
|
239
|
-
record_set.lower_bound = Bound.new(start_key, true)
|
240
|
-
end
|
237
|
+
scoped(lower_bound: Bound.new(start_key, true))
|
241
238
|
end
|
242
239
|
|
243
240
|
def upto(end_key)
|
244
|
-
scoped
|
245
|
-
record_set.upper_bound = Bound.new(end_key, true)
|
246
|
-
end
|
241
|
+
scoped(upper_bound: Bound.new(end_key, true))
|
247
242
|
end
|
248
243
|
|
249
244
|
def reverse
|
250
|
-
scoped
|
245
|
+
scoped(reversed: !reversed?)
|
251
246
|
end
|
252
247
|
|
253
248
|
def last
|
254
249
|
reverse.first
|
255
250
|
end
|
256
251
|
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
self
|
252
|
+
# @api private
|
253
|
+
def next_batch_from(row)
|
254
|
+
reversed? ? before(row[range_key_name]) : super
|
261
255
|
end
|
262
256
|
|
263
257
|
protected
|
264
|
-
attr_writer :reversed
|
265
258
|
|
266
259
|
def construct_data_set
|
267
260
|
data_set = super
|
@@ -269,10 +262,6 @@ module Cequel
|
|
269
262
|
data_set
|
270
263
|
end
|
271
264
|
|
272
|
-
def reversed?
|
273
|
-
@reversed
|
274
|
-
end
|
275
|
-
|
276
265
|
def construct_bound_fragment(bound, base_operator)
|
277
266
|
operator = bound.inclusive ? "#{base_operator}=" : base_operator
|
278
267
|
"#{range_key_name} #{operator} ?"
|
data/lib/cequel/model/schema.rb
CHANGED
@@ -9,7 +9,8 @@ module Cequel
|
|
9
9
|
module ClassMethods
|
10
10
|
extend Forwardable
|
11
11
|
|
12
|
-
def_delegators :table_schema, :key_columns, :key_column_names
|
12
|
+
def_delegators :table_schema, :key_columns, :key_column_names,
|
13
|
+
:partition_key_columns, :clustering_columns
|
13
14
|
|
14
15
|
def synchronize_schema
|
15
16
|
Cequel::Schema::TableSynchronizer.
|
data/lib/cequel/model/scoped.rb
CHANGED
@@ -0,0 +1,31 @@
|
|
1
|
+
module Cequel
|
2
|
+
|
3
|
+
module Model
|
4
|
+
|
5
|
+
module SecondaryIndexes
|
6
|
+
|
7
|
+
def column(name, type, options = {})
|
8
|
+
super
|
9
|
+
name = name.to_sym
|
10
|
+
if options[:index]
|
11
|
+
instance_eval <<-RUBY, __FILE__, __LINE__+1
|
12
|
+
def with_#{name}(value)
|
13
|
+
all.where(#{name.inspect}, value)
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_by_#{name}(value)
|
17
|
+
with_#{name}(value).first
|
18
|
+
end
|
19
|
+
|
20
|
+
def find_all_by_#{name}(value)
|
21
|
+
with_#{name}(value).to_a
|
22
|
+
end
|
23
|
+
RUBY
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
data/lib/cequel/schema/column.rb
CHANGED
@@ -10,6 +10,22 @@ module Cequel
|
|
10
10
|
@name, @type = name, type
|
11
11
|
end
|
12
12
|
|
13
|
+
def key?
|
14
|
+
partition_key? || clustering_column?
|
15
|
+
end
|
16
|
+
|
17
|
+
def partition_key?
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def clustering_column?
|
22
|
+
false
|
23
|
+
end
|
24
|
+
|
25
|
+
def data_column?
|
26
|
+
!key?
|
27
|
+
end
|
28
|
+
|
13
29
|
def to_cql
|
14
30
|
"#{@name} #{@type}"
|
15
31
|
end
|
@@ -53,8 +69,8 @@ module Cequel
|
|
53
69
|
"#{@name} #{@clustering_order}"
|
54
70
|
end
|
55
71
|
|
56
|
-
def
|
57
|
-
|
72
|
+
def clustering_column?
|
73
|
+
true
|
58
74
|
end
|
59
75
|
|
60
76
|
end
|
data/lib/cequel/schema/table.rb
CHANGED
@@ -8,7 +8,7 @@ module Cequel
|
|
8
8
|
|
9
9
|
attr_reader :name,
|
10
10
|
:columns,
|
11
|
-
:
|
11
|
+
:partition_key_columns,
|
12
12
|
:clustering_columns,
|
13
13
|
:data_columns,
|
14
14
|
:properties
|
@@ -16,13 +16,13 @@ module Cequel
|
|
16
16
|
|
17
17
|
def initialize(name)
|
18
18
|
@name = name
|
19
|
-
@
|
19
|
+
@partition_key_columns, @clustering_columns, @data_columns = [], [], []
|
20
20
|
@columns, @columns_by_name = [], {}
|
21
21
|
@properties = ActiveSupport::HashWithIndifferentAccess.new
|
22
22
|
end
|
23
23
|
|
24
24
|
def add_key(name, type, clustering_order = nil)
|
25
|
-
if @
|
25
|
+
if @partition_key_columns.empty?
|
26
26
|
unless clustering_order.nil?
|
27
27
|
raise ArgumentError,
|
28
28
|
"Can't set clustering order for partition key #{name}"
|
@@ -35,7 +35,7 @@ module Cequel
|
|
35
35
|
|
36
36
|
def add_partition_key(name, type)
|
37
37
|
column = PartitionKey.new(name, type(type))
|
38
|
-
@
|
38
|
+
@partition_key_columns << add_column(column)
|
39
39
|
end
|
40
40
|
|
41
41
|
def add_clustering_column(name, type, clustering_order = nil)
|
@@ -71,7 +71,7 @@ module Cequel
|
|
71
71
|
end
|
72
72
|
|
73
73
|
def key_columns
|
74
|
-
@
|
74
|
+
@partition_key_columns + @clustering_columns
|
75
75
|
end
|
76
76
|
|
77
77
|
def key_column_names
|
@@ -79,7 +79,7 @@ module Cequel
|
|
79
79
|
end
|
80
80
|
|
81
81
|
def partition_key(name)
|
82
|
-
@
|
82
|
+
@partition_key_columns.find { |column| column.name == name }
|
83
83
|
end
|
84
84
|
|
85
85
|
def clustering_column(name)
|
@@ -86,15 +86,15 @@ module Cequel
|
|
86
86
|
end
|
87
87
|
|
88
88
|
def each_key_pair(&block)
|
89
|
-
if existing.
|
89
|
+
if existing.partition_key_columns.length != updated.partition_key_columns.length
|
90
90
|
raise InvalidSchemaMigration,
|
91
|
-
"Existing partition keys #{existing.
|
91
|
+
"Existing partition keys #{existing.partition_key_columns.map { |key| key.name }.join(',')} differ from specified partition keys #{updated.partition_key_columns.map { |key| key.name }.join(',')}"
|
92
92
|
end
|
93
93
|
if existing.clustering_columns.length != updated.clustering_columns.length
|
94
94
|
raise InvalidSchemaMigration,
|
95
95
|
"Existing clustering keys #{existing.clustering_columns.map { |key| key.name }.join(',')} differ from specified clustering keys #{updated.clustering_columns.map { |key| key.name }.join(',')}"
|
96
96
|
end
|
97
|
-
existing.
|
97
|
+
existing.partition_key_columns.zip(updated.partition_key_columns, &block)
|
98
98
|
existing.clustering_columns.zip(updated.clustering_columns, &block)
|
99
99
|
end
|
100
100
|
|
@@ -51,7 +51,8 @@ module Cequel
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def keys_cql
|
54
|
-
partition_cql = table.
|
54
|
+
partition_cql = table.partition_key_columns.
|
55
|
+
map { |key| key.name }.join(', ')
|
55
56
|
if table.clustering_columns.any?
|
56
57
|
nonpartition_cql =
|
57
58
|
table.clustering_columns.map { |key| key.name }.join(', ')
|
data/lib/cequel/util.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module Cequel
|
2
|
+
|
3
|
+
module Util
|
4
|
+
|
5
|
+
module HashAccessors
|
6
|
+
|
7
|
+
def hattr_reader(hash, *attributes)
|
8
|
+
attributes.each do |attribute|
|
9
|
+
module_eval <<-RUBY, __FILE__, __LINE__+1
|
10
|
+
def #{attribute}
|
11
|
+
#{hash}[#{attribute.to_sym.inspect}]
|
12
|
+
end
|
13
|
+
RUBY
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def hattr_inquirer(hash, *attributes)
|
18
|
+
attributes.each do |attribute|
|
19
|
+
module_eval <<-RUBY, __FILE__, __LINE__+1
|
20
|
+
def #{attribute}?
|
21
|
+
!!#{hash}[#{attribute.to_sym.inspect}]
|
22
|
+
end
|
23
|
+
RUBY
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def hattr_writer(hash, *attributes)
|
28
|
+
attributes.each do |attribute|
|
29
|
+
module_eval <<-RUBY, __FILE__, __LINE__+1
|
30
|
+
def #{attribute}=(value)
|
31
|
+
#{hash}[#{attribute.to_sym.inspect}] = value
|
32
|
+
end
|
33
|
+
RUBY
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def hattr_accessor(hash, *attributes)
|
38
|
+
hattr_reader(hash, *attributes)
|
39
|
+
hattr_writer(hash, *attributes)
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
data/lib/cequel/version.rb
CHANGED
@@ -0,0 +1,68 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Cequel::Model::Dirty do
|
4
|
+
model :Post do
|
5
|
+
key :permalink, :text
|
6
|
+
column :title, :text
|
7
|
+
set :categories, :text
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'loaded model' do
|
11
|
+
let(:post) do
|
12
|
+
Post.create!(
|
13
|
+
permalink: 'cequel',
|
14
|
+
title: 'Cequel',
|
15
|
+
categories: Set['Libraries']
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should not have changed attributes by default' do
|
20
|
+
post.changed_attributes.should be_empty
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'should have changed attributes if attributes change' do
|
24
|
+
post.title = 'Cequel ORM'
|
25
|
+
post.changed_attributes.
|
26
|
+
should == {:title => 'Cequel'}.with_indifferent_access
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should not have changed attributes if attribute set to the same thing' do
|
30
|
+
post.title = 'Cequel'
|
31
|
+
post.changed_attributes.should be_empty
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'should support *_changed? method' do
|
35
|
+
post.title = 'Cequel ORM'
|
36
|
+
post.title_changed?.should be_true
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should not have changed attributes after save' do
|
40
|
+
post.title = 'Cequel ORM'
|
41
|
+
post.save
|
42
|
+
post.changed_attributes.should be_empty
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should have previous changes after save' do
|
46
|
+
post.title = 'Cequel ORM'
|
47
|
+
post.save
|
48
|
+
post.previous_changes.
|
49
|
+
should == { :title => ['Cequel', 'Cequel ORM'] }.with_indifferent_access
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should detect changes to collections' do
|
53
|
+
post.categories << 'Gems'
|
54
|
+
post.changes.should ==
|
55
|
+
{categories: [Set['Libraries'], Set['Libraries', 'Gems']]}.
|
56
|
+
with_indifferent_access
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'unloaded model' do
|
61
|
+
let(:post) { Post['cequel'] }
|
62
|
+
|
63
|
+
it 'should not track changes' do
|
64
|
+
post.title = 'Cequel'
|
65
|
+
post.changes.should be_empty
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -12,13 +12,15 @@ describe Cequel::Model::RecordSet do
|
|
12
12
|
key :permalink, :text
|
13
13
|
column :title, :text
|
14
14
|
column :body, :text
|
15
|
-
column :author_id, :uuid
|
15
|
+
column :author_id, :uuid, :index => true
|
16
|
+
column :author_name, :text, :index => true
|
16
17
|
list :tags, :text
|
17
18
|
set :categories, :text
|
18
19
|
map :shares, :text, :int
|
19
20
|
end
|
20
21
|
|
21
22
|
let(:subdomains) { [] }
|
23
|
+
let(:uuids) { Array.new(2) { CassandraCQL::UUID.new }}
|
22
24
|
|
23
25
|
before do
|
24
26
|
cequel.batch do
|
@@ -36,7 +38,8 @@ describe Cequel::Model::RecordSet do
|
|
36
38
|
:blog_subdomain => 'cassandra',
|
37
39
|
:permalink => "cequel#{i}",
|
38
40
|
:title => "Cequel #{i}",
|
39
|
-
:body => "Post number #{i}"
|
41
|
+
:body => "Post number #{i}",
|
42
|
+
:author_id => uuids[i%2]
|
40
43
|
)
|
41
44
|
cequel[:posts].insert(
|
42
45
|
:blog_subdomain => 'postgres',
|
@@ -276,6 +279,34 @@ describe Cequel::Model::RecordSet do
|
|
276
279
|
end
|
277
280
|
end
|
278
281
|
|
282
|
+
describe '#where' do
|
283
|
+
it 'should correctly query for secondary indexed columns' do
|
284
|
+
Post.where(:author_id, uuids.first).map(&:permalink).
|
285
|
+
should == %w(cequel0 cequel2 cequel4)
|
286
|
+
end
|
287
|
+
|
288
|
+
it 'should raise ArgumentError if column is not recognized' do
|
289
|
+
expect { Post.where(:bogus, 'Business') }.
|
290
|
+
to raise_error(ArgumentError)
|
291
|
+
end
|
292
|
+
|
293
|
+
it 'should raise ArgumentError if column is not indexed' do
|
294
|
+
expect { Post.where(:title, 'Cequel 0') }.
|
295
|
+
to raise_error(ArgumentError)
|
296
|
+
end
|
297
|
+
|
298
|
+
it 'should raise ArgumentError if column is a key' do
|
299
|
+
expect { Post.where(:permalink, 'cequel0') }.
|
300
|
+
to raise_error(ArgumentError)
|
301
|
+
end
|
302
|
+
|
303
|
+
it 'should raise IllegalQuery if applied twice' do
|
304
|
+
expect { Post.where(:author_id, uuids.first).
|
305
|
+
where(:author_name, 'Mat Brown') }.
|
306
|
+
to raise_error(Cequel::Model::IllegalQuery)
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|
279
310
|
describe '#count' do
|
280
311
|
it 'should count records' do
|
281
312
|
Blog.count.should == 3
|
@@ -20,7 +20,7 @@ describe Cequel::Model::Schema do
|
|
20
20
|
context 'new model with simple primary key' do
|
21
21
|
before { model.synchronize_schema }
|
22
22
|
|
23
|
-
its(:
|
23
|
+
its(:partition_key_columns) { should == [Cequel::Schema::Column.new(:permalink, :text)] }
|
24
24
|
its(:data_columns) { should include(Cequel::Schema::Column.new(:title, :text)) }
|
25
25
|
its(:data_columns) { should include(Cequel::Schema::List.new(:categories, :text)) }
|
26
26
|
its(:data_columns) { should include(Cequel::Schema::Set.new(:tags, :text)) }
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require_relative 'spec_helper'
|
2
|
+
|
3
|
+
describe Cequel::Model::SecondaryIndexes do
|
4
|
+
model :Post do
|
5
|
+
key :blog_subdomain, :text
|
6
|
+
key :permalink, :text
|
7
|
+
column :title, :text
|
8
|
+
column :author_id, :uuid, :index => true
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:uuids) { Array.new(2) { CassandraCQL::UUID.new }}
|
12
|
+
|
13
|
+
let!(:posts) do
|
14
|
+
3.times.map do |i|
|
15
|
+
Post.create! do |post|
|
16
|
+
post.blog_subdomain = 'bigdata'
|
17
|
+
post.permalink = "cequel#{i}"
|
18
|
+
post.title = "Cequel #{i}"
|
19
|
+
post.author_id = uuids[i%2]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should create secondary index in schema' do
|
25
|
+
cequel.schema.read_table(:posts).data_columns.
|
26
|
+
find { |column| column.name == :author_id }.index_name.
|
27
|
+
should be
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should expose scope to query by secondary index' do
|
31
|
+
Post.with_author_id(uuids.first).map(&:permalink).
|
32
|
+
should == %w(cequel0 cequel2)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should expose method to retrieve first result by secondary index' do
|
36
|
+
Post.find_by_author_id(uuids.first).should == posts.first
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'should expose method to eagerly retrieve all results by secondary index' do
|
40
|
+
posts = Post.find_all_by_author_id(uuids.first)
|
41
|
+
disallow_queries!
|
42
|
+
posts.map(&:permalink).should == %w(cequel0 cequel2)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -14,11 +14,11 @@ describe Cequel::Schema::TableReader do
|
|
14
14
|
end
|
15
15
|
|
16
16
|
it 'should read name correctly' do
|
17
|
-
table.
|
17
|
+
table.partition_key_columns.first.name.should == :permalink
|
18
18
|
end
|
19
19
|
|
20
20
|
it 'should read type correctly' do
|
21
|
-
table.
|
21
|
+
table.partition_key_columns.first.type.should be_a(Cequel::Type::Text)
|
22
22
|
end
|
23
23
|
|
24
24
|
it 'should have no nonpartition keys' do
|
@@ -38,11 +38,11 @@ describe Cequel::Schema::TableReader do
|
|
38
38
|
end
|
39
39
|
|
40
40
|
it 'should read partition key name' do
|
41
|
-
table.
|
41
|
+
table.partition_key_columns.map(&:name).should == [:blog_subdomain]
|
42
42
|
end
|
43
43
|
|
44
44
|
it 'should read partition key type' do
|
45
|
-
table.
|
45
|
+
table.partition_key_columns.map(&:type).should == [Cequel::Type::Text.instance]
|
46
46
|
end
|
47
47
|
|
48
48
|
it 'should read non-partition key name' do
|
@@ -124,11 +124,11 @@ describe Cequel::Schema::TableReader do
|
|
124
124
|
end
|
125
125
|
|
126
126
|
it 'should read partition key names' do
|
127
|
-
table.
|
127
|
+
table.partition_key_columns.map(&:name).should == [:blog_subdomain, :permalink]
|
128
128
|
end
|
129
129
|
|
130
130
|
it 'should read partition key types' do
|
131
|
-
table.
|
131
|
+
table.partition_key_columns.map(&:type).
|
132
132
|
should == [Cequel::Type::Text.instance, Cequel::Type::Ascii.instance]
|
133
133
|
end
|
134
134
|
|
@@ -153,11 +153,11 @@ describe Cequel::Schema::TableReader do
|
|
153
153
|
end
|
154
154
|
|
155
155
|
it 'should read partition key names' do
|
156
|
-
table.
|
156
|
+
table.partition_key_columns.map(&:name).should == [:blog_subdomain, :permalink]
|
157
157
|
end
|
158
158
|
|
159
159
|
it 'should read partition key types' do
|
160
|
-
table.
|
160
|
+
table.partition_key_columns.map(&:type).
|
161
161
|
should == [Cequel::Type::Text.instance, Cequel::Type::Ascii.instance]
|
162
162
|
end
|
163
163
|
|
@@ -19,11 +19,11 @@ describe Cequel::Schema::TableWriter do
|
|
19
19
|
end
|
20
20
|
|
21
21
|
it 'should create key alias' do
|
22
|
-
table.
|
22
|
+
table.partition_key_columns.map(&:name).should == [:permalink]
|
23
23
|
end
|
24
24
|
|
25
25
|
it 'should set key validator' do
|
26
|
-
table.
|
26
|
+
table.partition_key_columns.map(&:type).should == [Cequel::Type[:ascii]]
|
27
27
|
end
|
28
28
|
|
29
29
|
it 'should set non-key columns' do
|
@@ -42,11 +42,11 @@ describe Cequel::Schema::TableWriter do
|
|
42
42
|
end
|
43
43
|
|
44
44
|
it 'should create key alias' do
|
45
|
-
table.
|
45
|
+
table.partition_key_columns.map(&:name).should == [:blog_subdomain]
|
46
46
|
end
|
47
47
|
|
48
48
|
it 'should set key validator' do
|
49
|
-
table.
|
49
|
+
table.partition_key_columns.map(&:type).should == [Cequel::Type[:ascii]]
|
50
50
|
end
|
51
51
|
|
52
52
|
it 'should create non-partition key components' do
|
@@ -68,11 +68,11 @@ describe Cequel::Schema::TableWriter do
|
|
68
68
|
end
|
69
69
|
|
70
70
|
it 'should create all partition key components' do
|
71
|
-
table.
|
71
|
+
table.partition_key_columns.map(&:name).should == [:blog_subdomain, :permalink]
|
72
72
|
end
|
73
73
|
|
74
74
|
it 'should set key validators' do
|
75
|
-
table.
|
75
|
+
table.partition_key_columns.map(&:type).
|
76
76
|
should == [Cequel::Type[:ascii], Cequel::Type[:ascii]]
|
77
77
|
end
|
78
78
|
end
|
@@ -88,12 +88,12 @@ describe Cequel::Schema::TableWriter do
|
|
88
88
|
end
|
89
89
|
|
90
90
|
it 'should create all partition key components' do
|
91
|
-
table.
|
91
|
+
table.partition_key_columns.map(&:name).
|
92
92
|
should == [:blog_subdomain, :permalink]
|
93
93
|
end
|
94
94
|
|
95
95
|
it 'should set key validators' do
|
96
|
-
table.
|
96
|
+
table.partition_key_columns.map(&:type).
|
97
97
|
should == [Cequel::Type[:ascii], Cequel::Type[:ascii]]
|
98
98
|
end
|
99
99
|
|
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.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mat Brown
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2013-09-
|
14
|
+
date: 2013-09-21 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: activesupport
|
@@ -126,9 +126,10 @@ dependencies:
|
|
126
126
|
- !ruby/object:Gem::Version
|
127
127
|
version: '0.6'
|
128
128
|
description: |
|
129
|
-
Cequel is
|
130
|
-
|
131
|
-
|
129
|
+
Cequel is an ActiveRecord-like domain model layer for Cassandra that exposes
|
130
|
+
the robust data modeling capabilities of CQL3, including parent-child
|
131
|
+
relationships via compound primary keys and in-memory atomic manipulation of
|
132
|
+
collection columns.
|
132
133
|
email: mat.a.brown@gmail.com
|
133
134
|
executables: []
|
134
135
|
extensions: []
|
@@ -154,6 +155,7 @@ files:
|
|
154
155
|
- lib/cequel/model/belongs_to_association.rb
|
155
156
|
- lib/cequel/model/callbacks.rb
|
156
157
|
- lib/cequel/model/collection.rb
|
158
|
+
- lib/cequel/model/dirty.rb
|
157
159
|
- lib/cequel/model/errors.rb
|
158
160
|
- lib/cequel/model/has_many_association.rb
|
159
161
|
- lib/cequel/model/mass_assignment.rb
|
@@ -163,6 +165,7 @@ files:
|
|
163
165
|
- lib/cequel/model/record_set.rb
|
164
166
|
- lib/cequel/model/schema.rb
|
165
167
|
- lib/cequel/model/scoped.rb
|
168
|
+
- lib/cequel/model/secondary_indexes.rb
|
166
169
|
- lib/cequel/model/validations.rb
|
167
170
|
- lib/cequel/model.rb
|
168
171
|
- lib/cequel/new_relic_instrumentation.rb
|
@@ -178,12 +181,14 @@ files:
|
|
178
181
|
- lib/cequel/schema/update_table_dsl.rb
|
179
182
|
- lib/cequel/schema.rb
|
180
183
|
- lib/cequel/type.rb
|
184
|
+
- lib/cequel/util.rb
|
181
185
|
- lib/cequel/version.rb
|
182
186
|
- lib/cequel.rb
|
183
187
|
- spec/environment.rb
|
184
188
|
- spec/examples/metal/data_set_spec.rb
|
185
189
|
- spec/examples/model/associations_spec.rb
|
186
190
|
- spec/examples/model/callbacks_spec.rb
|
191
|
+
- spec/examples/model/dirty_spec.rb
|
187
192
|
- spec/examples/model/list_spec.rb
|
188
193
|
- spec/examples/model/map_spec.rb
|
189
194
|
- spec/examples/model/mass_assignment_spec.rb
|
@@ -192,6 +197,7 @@ files:
|
|
192
197
|
- spec/examples/model/properties_spec.rb
|
193
198
|
- spec/examples/model/record_set_spec.rb
|
194
199
|
- spec/examples/model/schema_spec.rb
|
200
|
+
- spec/examples/model/secondary_index_spec.rb
|
195
201
|
- spec/examples/model/serialization_spec.rb
|
196
202
|
- spec/examples/model/set_spec.rb
|
197
203
|
- spec/examples/model/spec_helper.rb
|
@@ -240,11 +246,12 @@ rubyforge_project:
|
|
240
246
|
rubygems_version: 2.0.3
|
241
247
|
signing_key:
|
242
248
|
specification_version: 4
|
243
|
-
summary:
|
249
|
+
summary: Full-featured, ActiveModel-compliant ORM for Cassandra using CQL3
|
244
250
|
test_files:
|
245
251
|
- spec/examples/metal/data_set_spec.rb
|
246
252
|
- spec/examples/model/associations_spec.rb
|
247
253
|
- spec/examples/model/callbacks_spec.rb
|
254
|
+
- spec/examples/model/dirty_spec.rb
|
248
255
|
- spec/examples/model/list_spec.rb
|
249
256
|
- spec/examples/model/map_spec.rb
|
250
257
|
- spec/examples/model/mass_assignment_spec.rb
|
@@ -253,6 +260,7 @@ test_files:
|
|
253
260
|
- spec/examples/model/properties_spec.rb
|
254
261
|
- spec/examples/model/record_set_spec.rb
|
255
262
|
- spec/examples/model/schema_spec.rb
|
263
|
+
- spec/examples/model/secondary_index_spec.rb
|
256
264
|
- spec/examples/model/serialization_spec.rb
|
257
265
|
- spec/examples/model/set_spec.rb
|
258
266
|
- spec/examples/model/spec_helper.rb
|