whi-cassie 1.1.0 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/dependabot.yml +12 -0
- data/.github/workflows/continuous_integration.yml +64 -0
- data/.gitignore +7 -15
- data/.ruby-version +1 -0
- data/.standard.yml +11 -0
- data/Appraisals +17 -0
- data/Gemfile +10 -0
- data/HISTORY.md +12 -0
- data/README.md +14 -10
- data/Rakefile +13 -12
- data/VERSION +1 -1
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/activemodel_4.gemfile +14 -0
- data/gemfiles/activemodel_5.gemfile +14 -0
- data/gemfiles/activemodel_6.gemfile +14 -0
- data/gemfiles/activemodel_7.gemfile +14 -0
- data/lib/cassie/config.rb +8 -7
- data/lib/cassie/model.rb +143 -111
- data/lib/cassie/railtie.rb +7 -5
- data/lib/cassie/schema.rb +35 -28
- data/lib/cassie/subscribers.rb +10 -12
- data/lib/cassie/testing.rb +22 -20
- data/lib/cassie.rb +55 -57
- data/lib/whi-cassie.rb +2 -0
- data/spec/cassie/config_spec.rb +20 -22
- data/spec/cassie/model_spec.rb +333 -311
- data/spec/cassie/subscribers_spec.rb +19 -21
- data/spec/cassie_spec.rb +86 -87
- data/spec/models/thing.rb +11 -11
- data/spec/models/type_tester.rb +7 -7
- data/spec/spec_helper.rb +32 -13
- data/whi-cassie.gemspec +17 -19
- metadata +23 -27
data/lib/cassie/model.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model"
|
4
|
+
require "active_support/hash_with_indifferent_access"
|
3
5
|
|
4
6
|
# This module provides a simple interface for models backed by Cassandra tables.
|
5
7
|
#
|
@@ -15,19 +17,19 @@ require 'active_support/hash_with_indifferent_access'
|
|
15
17
|
#
|
16
18
|
# class Thing
|
17
19
|
# include Cassie::Model
|
18
|
-
#
|
20
|
+
#
|
19
21
|
# self.table_name = "things"
|
20
22
|
# self.keyspace = "test"
|
21
23
|
# self.primary_key = [:owner, :id]
|
22
|
-
#
|
24
|
+
#
|
23
25
|
# column :owner, :int
|
24
26
|
# column :id, :int, :as => :identifier
|
25
27
|
# column :val, :varchar, :as => :value
|
26
|
-
#
|
28
|
+
#
|
27
29
|
# ordering_key :id, :desc
|
28
|
-
#
|
30
|
+
#
|
29
31
|
# validates_presence_of :id, :value
|
30
|
-
#
|
32
|
+
#
|
31
33
|
# before_save do
|
32
34
|
# ...
|
33
35
|
# end
|
@@ -38,17 +40,17 @@ module Cassie::Model
|
|
38
40
|
include ActiveModel::Validations
|
39
41
|
include ActiveModel::Validations::Callbacks
|
40
42
|
extend ActiveModel::Callbacks
|
41
|
-
|
43
|
+
|
42
44
|
included do |base|
|
43
|
-
class_attribute :table_name, :
|
44
|
-
class_attribute :_keyspace, :
|
45
|
-
class_attribute :_primary_key, :
|
46
|
-
class_attribute :_columns, :
|
47
|
-
class_attribute :_column_aliases, :
|
48
|
-
class_attribute :_ordering_keys, :
|
49
|
-
class_attribute :_counter_table, :
|
50
|
-
class_attribute :find_subscribers, :
|
51
|
-
class_attribute :read_consistency, :
|
45
|
+
class_attribute :table_name, instance_reader: false, instance_writer: false
|
46
|
+
class_attribute :_keyspace, instance_reader: false, instance_writer: false
|
47
|
+
class_attribute :_primary_key, instance_reader: false, instance_writer: false
|
48
|
+
class_attribute :_columns, instance_reader: false, instance_writer: false
|
49
|
+
class_attribute :_column_aliases, instance_reader: false, instance_writer: false
|
50
|
+
class_attribute :_ordering_keys, instance_reader: false, instance_writer: false
|
51
|
+
class_attribute :_counter_table, instance_reader: false, instance_writer: false
|
52
|
+
class_attribute :find_subscribers, instance_reader: false, instance_writer: false
|
53
|
+
class_attribute :read_consistency, instance_reader: false, instance_writer: false
|
52
54
|
class_attribute :write_consistency
|
53
55
|
define_model_callbacks :create, :update, :save, :destroy
|
54
56
|
self._columns = {}
|
@@ -56,19 +58,19 @@ module Cassie::Model
|
|
56
58
|
self._ordering_keys = {}
|
57
59
|
self.find_subscribers = Cassie::Subscribers.new(Cassie::Model.find_subscribers)
|
58
60
|
end
|
59
|
-
|
61
|
+
|
60
62
|
class << self
|
61
63
|
@@find_subscribers = Cassie::Subscribers.new
|
62
|
-
|
64
|
+
|
63
65
|
def find_subscribers
|
64
66
|
@@find_subscribers
|
65
67
|
end
|
66
68
|
end
|
67
|
-
|
69
|
+
|
68
70
|
# Message sent to find subscribers for instrumenting find operations.
|
69
71
|
class FindMessage
|
70
72
|
attr_reader :cql, :args, :options, :elapsed_time, :rows
|
71
|
-
|
73
|
+
|
72
74
|
def initialize(cql, args, options, elapsed_time, rows)
|
73
75
|
@cql = cql
|
74
76
|
@args = args
|
@@ -77,8 +79,8 @@ module Cassie::Model
|
|
77
79
|
@rows = rows
|
78
80
|
end
|
79
81
|
end
|
80
|
-
|
81
|
-
module ClassMethods
|
82
|
+
|
83
|
+
module ClassMethods
|
82
84
|
# Define a column name and type from the table. Columns must be defined in order
|
83
85
|
# to be used. This method will handle defining the getter and setter methods as well.
|
84
86
|
#
|
@@ -108,49 +110,49 @@ module Cassie::Model
|
|
108
110
|
rescue NameError
|
109
111
|
raise ArgumentError.new("#{type.inspect} is not an allowed Cassandra type")
|
110
112
|
end
|
111
|
-
|
113
|
+
|
112
114
|
self._columns = _columns.merge(name => type_class)
|
113
|
-
self._column_aliases =
|
115
|
+
self._column_aliases = _column_aliases.merge(name => name)
|
114
116
|
|
115
117
|
aliased = (as && as.to_s != name.to_s)
|
116
118
|
if aliased
|
117
|
-
self._column_aliases =
|
119
|
+
self._column_aliases = _column_aliases.merge(as => name)
|
118
120
|
end
|
119
121
|
|
120
|
-
if type.to_s == "counter"
|
122
|
+
if type.to_s == "counter"
|
121
123
|
self._counter_table = true
|
122
|
-
|
123
|
-
define_method(name){ instance_variable_get(:"@#{name}") || 0 }
|
124
|
-
define_method("#{name}="){ |value| instance_variable_set(:"@#{name}", value.to_i) }
|
125
|
-
|
126
|
-
define_method("increment_#{name}!"){ |amount=1, ttl: nil| send(:adjust_counter!, name, amount, ttl: ttl) }
|
127
|
-
define_method("decrement_#{name}!"){ |amount=1, ttl: nil| send(:adjust_counter!, name, -amount, ttl: ttl) }
|
124
|
+
|
125
|
+
define_method(name) { instance_variable_get(:"@#{name}") || 0 }
|
126
|
+
define_method("#{name}=") { |value| instance_variable_set(:"@#{name}", value.to_i) }
|
127
|
+
|
128
|
+
define_method("increment_#{name}!") { |amount = 1, ttl: nil| send(:adjust_counter!, name, amount, ttl: ttl) }
|
129
|
+
define_method("decrement_#{name}!") { |amount = 1, ttl: nil| send(:adjust_counter!, name, -amount, ttl: ttl) }
|
128
130
|
if aliased
|
129
|
-
define_method(as){ send(name) }
|
130
|
-
define_method("increment_#{as}!"){ |amount=1, ttl: nil| send("increment_#{name}!", amount, ttl: ttl) }
|
131
|
-
define_method("decrement_#{as}!"){ |amount=1, ttl: nil| send("increment_#{name}!", amount, ttl: ttl) }
|
131
|
+
define_method(as) { send(name) }
|
132
|
+
define_method("increment_#{as}!") { |amount = 1, ttl: nil| send("increment_#{name}!", amount, ttl: ttl) }
|
133
|
+
define_method("decrement_#{as}!") { |amount = 1, ttl: nil| send("increment_#{name}!", amount, ttl: ttl) }
|
132
134
|
end
|
133
135
|
else
|
134
136
|
attr_reader name
|
135
|
-
define_method("#{name}="){ |value| instance_variable_set(:"@#{name}", self.class.send(:coerce, value, type_class)) }
|
137
|
+
define_method("#{name}=") { |value| instance_variable_set(:"@#{name}", self.class.send(:coerce, value, type_class)) }
|
136
138
|
attr_reader name
|
137
139
|
if aliased
|
138
|
-
define_method(as){ send(name) }
|
139
|
-
define_method("#{as}="){|value| send("#{name}=", value) }
|
140
|
+
define_method(as) { send(name) }
|
141
|
+
define_method("#{as}=") { |value| send("#{name}=", value) }
|
140
142
|
end
|
141
143
|
end
|
142
144
|
end
|
143
|
-
|
145
|
+
|
144
146
|
# Returns an array of the defined column names as symbols.
|
145
147
|
def column_names
|
146
148
|
_columns.keys
|
147
149
|
end
|
148
|
-
|
150
|
+
|
149
151
|
# Returns the internal column name after resolving any aliases.
|
150
152
|
def column_name(name_or_alias)
|
151
|
-
|
153
|
+
_column_aliases[name_or_alias] || name_or_alias
|
152
154
|
end
|
153
|
-
|
155
|
+
|
154
156
|
# Set the primary key for the table. The value should be set as an array with the
|
155
157
|
# clustering key first.
|
156
158
|
def primary_key=(value)
|
@@ -162,31 +164,31 @@ module Cassie::Model
|
|
162
164
|
end
|
163
165
|
}.flatten
|
164
166
|
end
|
165
|
-
|
167
|
+
|
166
168
|
# Return an array of column names for the table primary key.
|
167
169
|
def primary_key
|
168
170
|
_primary_key
|
169
171
|
end
|
170
|
-
|
172
|
+
|
171
173
|
# Define and ordering key for the table. The order attribute should be either :asc or :desc
|
172
174
|
def ordering_key(name, order)
|
173
175
|
order = order.to_sym
|
174
176
|
raise ArgumentError.new("order must be either :asc or :desc") unless order == :asc || order == :desc
|
175
177
|
_ordering_keys[name.to_sym] = order
|
176
178
|
end
|
177
|
-
|
179
|
+
|
178
180
|
# Set the keyspace for the table. The name should be an abstract keyspace name
|
179
181
|
# that is mapped to an actual keyspace name in the configuration. If the name
|
180
182
|
# provided is not mapped in the configuration, then the raw value will be used.
|
181
183
|
def keyspace=(name)
|
182
184
|
self._keyspace = name.to_s
|
183
185
|
end
|
184
|
-
|
186
|
+
|
185
187
|
# Return the keyspace name where the table is located.
|
186
188
|
def keyspace
|
187
189
|
connection.config.keyspace(_keyspace)
|
188
190
|
end
|
189
|
-
|
191
|
+
|
190
192
|
# Return the full table name including the keyspace.
|
191
193
|
def full_table_name
|
192
194
|
if _keyspace
|
@@ -195,7 +197,7 @@ module Cassie::Model
|
|
195
197
|
table_name
|
196
198
|
end
|
197
199
|
end
|
198
|
-
|
200
|
+
|
199
201
|
# Find all records.
|
200
202
|
#
|
201
203
|
# The +where+ argument can be a Hash, Array, or String WHERE clause to
|
@@ -215,27 +217,27 @@ module Cassie::Model
|
|
215
217
|
# record as it is foundto the block instead of returning them.
|
216
218
|
def find_all(where:, select: nil, order: nil, limit: nil, options: nil)
|
217
219
|
start_time = Time.now
|
218
|
-
columns = (select ? Array(select).collect{|c| column_name(c)} : column_names)
|
219
|
-
cql = "SELECT #{columns.join(
|
220
|
+
columns = (select ? Array(select).collect { |c| column_name(c) } : column_names)
|
221
|
+
cql = "SELECT #{columns.join(", ")} FROM #{full_table_name}"
|
220
222
|
values = nil
|
221
|
-
|
223
|
+
|
222
224
|
raise ArgumentError.new("Where clause cannot be blank. Pass :all to find all records.") if where.blank?
|
223
225
|
if where && where != :all
|
224
226
|
where_clause, values = cql_where_clause(where)
|
225
227
|
else
|
226
228
|
values = []
|
227
229
|
end
|
228
|
-
cql
|
229
|
-
|
230
|
+
cql += " WHERE #{where_clause}" if where_clause
|
231
|
+
|
230
232
|
if order
|
231
|
-
cql
|
233
|
+
cql += " ORDER BY #{order}"
|
232
234
|
end
|
233
|
-
|
235
|
+
|
234
236
|
if limit
|
235
|
-
cql
|
237
|
+
cql += " LIMIT ?"
|
236
238
|
values << Integer(limit)
|
237
239
|
end
|
238
|
-
|
240
|
+
|
239
241
|
results = connection.find(cql, values, consistency_options(read_consistency, options))
|
240
242
|
records = [] unless block_given?
|
241
243
|
row_count = 0
|
@@ -253,15 +255,15 @@ module Cassie::Model
|
|
253
255
|
break if results.last_page?
|
254
256
|
results = results.next_page
|
255
257
|
end
|
256
|
-
|
258
|
+
|
257
259
|
if find_subscribers && !find_subscribers.empty?
|
258
260
|
payload = FindMessage.new(cql, values, options, Time.now - start_time, row_count)
|
259
|
-
find_subscribers.each{|subscriber| subscriber.call(payload)}
|
261
|
+
find_subscribers.each { |subscriber| subscriber.call(payload) }
|
260
262
|
end
|
261
|
-
|
263
|
+
|
262
264
|
records
|
263
265
|
end
|
264
|
-
|
266
|
+
|
265
267
|
# Find a single record that matches the +where+ argument.
|
266
268
|
def find(where)
|
267
269
|
options = nil
|
@@ -271,7 +273,7 @@ module Cassie::Model
|
|
271
273
|
end
|
272
274
|
find_all(where: where, limit: 1, options: options).first
|
273
275
|
end
|
274
|
-
|
276
|
+
|
275
277
|
# Find a single record that matches the +where+ argument or raise an
|
276
278
|
# ActiveRecord::RecordNotFound error if none is found.
|
277
279
|
def find!(where)
|
@@ -279,7 +281,7 @@ module Cassie::Model
|
|
279
281
|
raise Cassie::RecordNotFound unless record
|
280
282
|
record
|
281
283
|
end
|
282
|
-
|
284
|
+
|
283
285
|
# Return the count of rows in the table. If the +where+ argument is specified
|
284
286
|
# then it will be added as the WHERE clause.
|
285
287
|
def count(where = nil)
|
@@ -288,21 +290,21 @@ module Cassie::Model
|
|
288
290
|
where = where.dup
|
289
291
|
options = where.delete(:options)
|
290
292
|
end
|
291
|
-
|
292
|
-
cql = "SELECT COUNT(*) FROM #{
|
293
|
+
|
294
|
+
cql = "SELECT COUNT(*) FROM #{full_table_name}"
|
293
295
|
values = nil
|
294
|
-
|
296
|
+
|
295
297
|
if where
|
296
298
|
where_clause, values = cql_where_clause(where)
|
297
|
-
cql
|
299
|
+
cql += " WHERE #{where_clause}"
|
298
300
|
else
|
299
|
-
|
301
|
+
connection.prepare(cql)
|
300
302
|
end
|
301
|
-
|
303
|
+
|
302
304
|
results = connection.find(cql, values, consistency_options(read_consistency, options))
|
303
305
|
results.rows.first["count"]
|
304
306
|
end
|
305
|
-
|
307
|
+
|
306
308
|
# Returns a newly created record. If the record is not valid then it won't be
|
307
309
|
# persisted.
|
308
310
|
def create(attributes)
|
@@ -310,7 +312,7 @@ module Cassie::Model
|
|
310
312
|
record.save
|
311
313
|
record
|
312
314
|
end
|
313
|
-
|
315
|
+
|
314
316
|
# Returns a newly created record or raises an ActiveRecord::RecordInvalid error
|
315
317
|
# if the record is not valid.
|
316
318
|
def create!(attributes)
|
@@ -318,7 +320,7 @@ module Cassie::Model
|
|
318
320
|
record.save!
|
319
321
|
record
|
320
322
|
end
|
321
|
-
|
323
|
+
|
322
324
|
# Delete all rows from the table that match the key hash. This method bypasses
|
323
325
|
# any destroy callbacks defined on the model.
|
324
326
|
def delete_all(key_hash)
|
@@ -326,9 +328,9 @@ module Cassie::Model
|
|
326
328
|
key_hash.each do |name, value|
|
327
329
|
cleanup_up_hash[column_name(name)] = value
|
328
330
|
end
|
329
|
-
connection.delete(full_table_name, cleanup_up_hash, :
|
331
|
+
connection.delete(full_table_name, cleanup_up_hash, consistency: write_consistency)
|
330
332
|
end
|
331
|
-
|
333
|
+
|
332
334
|
# All insert, update, and delete calls within the block will be sent as a single
|
333
335
|
# batch to Cassandra. The consistency level will default to the write consistency
|
334
336
|
# level if it's been set.
|
@@ -338,12 +340,12 @@ module Cassie::Model
|
|
338
340
|
yield
|
339
341
|
end
|
340
342
|
end
|
341
|
-
|
343
|
+
|
342
344
|
# Returns the Cassie instance used to communicate with Cassandra.
|
343
345
|
def connection
|
344
346
|
Cassie.instance
|
345
347
|
end
|
346
|
-
|
348
|
+
|
347
349
|
# Since Cassandra doesn't support offset we need to find the order key of record
|
348
350
|
# at the specified the offset.
|
349
351
|
#
|
@@ -360,7 +362,7 @@ module Cassie::Model
|
|
360
362
|
cluster_order = _ordering_keys[ordering_key] || :asc
|
361
363
|
order ||= cluster_order
|
362
364
|
order_cql = "#{ordering_key} #{order}" unless order == cluster_order
|
363
|
-
|
365
|
+
|
364
366
|
from = (order == :desc ? max : min)
|
365
367
|
to = (order == :desc ? min : max)
|
366
368
|
loop do
|
@@ -368,11 +370,11 @@ module Cassie::Model
|
|
368
370
|
conditions_cql = []
|
369
371
|
conditions = []
|
370
372
|
if from
|
371
|
-
conditions_cql << "#{ordering_key} #{order == :desc ?
|
373
|
+
conditions_cql << "#{ordering_key} #{order == :desc ? "<" : ">"} ?"
|
372
374
|
conditions << from
|
373
375
|
end
|
374
376
|
if to
|
375
|
-
conditions_cql << "#{ordering_key} #{order == :desc ?
|
377
|
+
conditions_cql << "#{ordering_key} #{order == :desc ? ">" : "<"} ?"
|
376
378
|
conditions << to
|
377
379
|
end
|
378
380
|
key.each do |name, value|
|
@@ -381,10 +383,10 @@ module Cassie::Model
|
|
381
383
|
end
|
382
384
|
conditions.unshift(conditions_cql.join(" AND "))
|
383
385
|
|
384
|
-
results = find_all(:
|
386
|
+
results = find_all(select: [ordering_key], where: conditions, limit: limit, order: order_cql)
|
385
387
|
last_row = results.last if results.size == limit
|
386
388
|
last_id = last_row.send(ordering_key) if last_row
|
387
|
-
|
389
|
+
|
388
390
|
if last_id.nil?
|
389
391
|
return nil
|
390
392
|
elsif limit >= offset
|
@@ -395,9 +397,9 @@ module Cassie::Model
|
|
395
397
|
end
|
396
398
|
end
|
397
399
|
end
|
398
|
-
|
400
|
+
|
399
401
|
private
|
400
|
-
|
402
|
+
|
401
403
|
# Turn a hash of column value, array of [cql, value] or a CQL string into
|
402
404
|
# a CQL where clause. Returns the values pulled out in an array for making
|
403
405
|
# a prepared statement.
|
@@ -409,8 +411,7 @@ module Cassie::Model
|
|
409
411
|
where.each do |column, value|
|
410
412
|
col_name = column_name(column)
|
411
413
|
if value.is_a?(Array)
|
412
|
-
q =
|
413
|
-
(value.size - 1).times{ q << ',?' }
|
414
|
+
q = "?#{",?" * (value.size - 1)}"
|
414
415
|
cql << "#{col_name} IN (#{q})"
|
415
416
|
values.concat(value)
|
416
417
|
else
|
@@ -418,7 +419,7 @@ module Cassie::Model
|
|
418
419
|
values << coerce(value, _columns[col_name])
|
419
420
|
end
|
420
421
|
end
|
421
|
-
[cql.join(
|
422
|
+
[cql.join(" AND "), values]
|
422
423
|
when Array
|
423
424
|
[where.first, where[1, where.size]]
|
424
425
|
when String
|
@@ -427,7 +428,7 @@ module Cassie::Model
|
|
427
428
|
raise ArgumentError.new("invalid CQL where clause #{where}")
|
428
429
|
end
|
429
430
|
end
|
430
|
-
|
431
|
+
|
431
432
|
# Force a value to be the correct Cassandra data type.
|
432
433
|
def coerce(value, type_class)
|
433
434
|
if value.nil?
|
@@ -450,41 +451,41 @@ module Cassie::Model
|
|
450
451
|
elsif type_class == Cassandra::Types::Set
|
451
452
|
Set.new(value)
|
452
453
|
elsif type_class == Cassandra::Types::Map
|
453
|
-
|
454
|
+
value.to_h
|
454
455
|
else
|
455
456
|
type_class.new(value)
|
456
457
|
end
|
457
458
|
end
|
458
|
-
|
459
|
+
|
459
460
|
def consistency_options(consistency, options)
|
460
461
|
if consistency
|
461
462
|
if options
|
462
|
-
options = options.merge(:
|
463
|
+
options = options.merge(consistency: consistency) if options[:consistency].nil?
|
463
464
|
options
|
464
465
|
else
|
465
|
-
{:
|
466
|
+
{consistency: consistency}
|
466
467
|
end
|
467
468
|
else
|
468
469
|
options
|
469
470
|
end
|
470
471
|
end
|
471
472
|
end
|
472
|
-
|
473
|
+
|
473
474
|
def initialize(attributes = {})
|
474
475
|
super
|
475
476
|
@persisted = false
|
476
477
|
end
|
477
|
-
|
478
|
+
|
478
479
|
# Return true if the record has been persisted to Cassandra.
|
479
480
|
def persisted?
|
480
481
|
@persisted
|
481
482
|
end
|
482
|
-
|
483
|
+
|
483
484
|
# Return true if the table is used for a counter.
|
484
485
|
def counter_table?
|
485
486
|
!!self.class._counter_table
|
486
487
|
end
|
487
|
-
|
488
|
+
|
488
489
|
# Save a record. Returns true if the record was persisted and false if it was invalid.
|
489
490
|
# This method will run the save callbacks as well as either the update or create
|
490
491
|
# callbacks as necessary.
|
@@ -493,7 +494,7 @@ module Cassie::Model
|
|
493
494
|
valid_record = (validate ? valid? : true)
|
494
495
|
if valid_record
|
495
496
|
run_callbacks(:save) do
|
496
|
-
options = {:
|
497
|
+
options = {consistency: write_consistency, ttl: (ttl || persistence_ttl)}
|
497
498
|
if persisted?
|
498
499
|
run_callbacks(:update) do
|
499
500
|
self.class.connection.update(self.class.full_table_name, values_hash, key_hash, options)
|
@@ -510,7 +511,7 @@ module Cassie::Model
|
|
510
511
|
false
|
511
512
|
end
|
512
513
|
end
|
513
|
-
|
514
|
+
|
514
515
|
# Save a record. Returns true if the record was saved and raises an ActiveRecord::RecordInvalid
|
515
516
|
# error if the record is invalid.
|
516
517
|
def save!(ttl: nil)
|
@@ -520,16 +521,28 @@ module Cassie::Model
|
|
520
521
|
raise Cassie::RecordInvalid.new(self)
|
521
522
|
end
|
522
523
|
end
|
523
|
-
|
524
|
+
|
524
525
|
# Delete a record and call the destroy callbacks.
|
525
526
|
def destroy
|
526
527
|
run_callbacks(:destroy) do
|
527
|
-
self.class.connection.delete(self.class.full_table_name, key_hash, :
|
528
|
+
self.class.connection.delete(self.class.full_table_name, key_hash, consistency: write_consistency)
|
528
529
|
@persisted = false
|
529
530
|
true
|
530
531
|
end
|
531
532
|
end
|
532
|
-
|
533
|
+
|
534
|
+
# Update a record with the specified attributes. Returns false the record is invalid.
|
535
|
+
def update(values)
|
536
|
+
self.attributes = values
|
537
|
+
save
|
538
|
+
end
|
539
|
+
|
540
|
+
# Update a record with the specified attributes and raise an error if it is invalid.
|
541
|
+
def update!(values)
|
542
|
+
self.attributes = values
|
543
|
+
save!
|
544
|
+
end
|
545
|
+
|
533
546
|
# Returns a hash of column to values. Column names will be symbols.
|
534
547
|
def attributes
|
535
548
|
hash = {}
|
@@ -538,38 +551,57 @@ module Cassie::Model
|
|
538
551
|
end
|
539
552
|
hash
|
540
553
|
end
|
541
|
-
|
554
|
+
|
555
|
+
# Mass set attributes from a hash.
|
556
|
+
def attributes=(values)
|
557
|
+
values.each do |name, value|
|
558
|
+
send("#{name}=", value)
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
# Returns a hash representing the primary key value.
|
563
|
+
def primary_key
|
564
|
+
self.class.primary_key.each_with_object({}) { |name, hash|
|
565
|
+
hash[name] = send(name)
|
566
|
+
}
|
567
|
+
end
|
568
|
+
|
569
|
+
# Reloads the record in memory.
|
570
|
+
def reload
|
571
|
+
self.class.find(primary_key)
|
572
|
+
end
|
573
|
+
|
542
574
|
# Subclasses can override this method to provide a TTL on the persisted record.
|
543
575
|
def persistence_ttl
|
544
576
|
nil
|
545
577
|
end
|
546
|
-
|
578
|
+
|
547
579
|
def eql?(other)
|
548
580
|
other.is_a?(self.class) && other.key_hash == key_hash
|
549
581
|
end
|
550
|
-
|
582
|
+
|
551
583
|
def ==(other)
|
552
584
|
eql?(other)
|
553
585
|
end
|
554
|
-
|
586
|
+
|
555
587
|
# Returns the primary key as a hash
|
556
588
|
def key_hash
|
557
589
|
hash = {}
|
558
590
|
self.class.primary_key.each do |key|
|
559
|
-
hash[key] =
|
591
|
+
hash[key] = send(key)
|
560
592
|
end
|
561
593
|
hash
|
562
594
|
end
|
563
|
-
|
595
|
+
|
564
596
|
private
|
565
|
-
|
597
|
+
|
566
598
|
# Used for updating counter columns.
|
567
599
|
def adjust_counter!(name, amount, ttl: nil)
|
568
600
|
amount = amount.to_i
|
569
601
|
if amount != 0
|
570
602
|
run_callbacks(:update) do
|
571
603
|
adjustment = (amount < 0 ? "#{name} = #{name} - #{amount.abs}" : "#{name} = #{name} + #{amount}")
|
572
|
-
options = {:
|
604
|
+
options = {consistency: write_consistency, ttl: (ttl || persistence_ttl)}
|
573
605
|
self.class.connection.update(self.class.full_table_name, adjustment, key_hash, options)
|
574
606
|
end
|
575
607
|
end
|
@@ -577,13 +609,13 @@ module Cassie::Model
|
|
577
609
|
value = (record ? record.send(name) : send(name) + amount)
|
578
610
|
send("#{name}=", value)
|
579
611
|
end
|
580
|
-
|
612
|
+
|
581
613
|
# Returns a hash of value except for the ones that constitute the primary key
|
582
614
|
def values_hash
|
583
615
|
pk = self.class.primary_key
|
584
616
|
hash = {}
|
585
617
|
self.class.column_names.each do |name|
|
586
|
-
hash[name] = send(name) unless pk.include?(name)
|
618
|
+
hash[name] = send(name) unless pk.include?(name)
|
587
619
|
end
|
588
620
|
hash
|
589
621
|
end
|
data/lib/cassie/railtie.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
# Initialize Cassie instance with default behaviors for a Rails environment.
|
2
4
|
#
|
3
5
|
# Configuration will be gotten from config/cassie.yml.
|
@@ -6,13 +8,13 @@
|
|
6
8
|
class Cassie::Railtie < Rails::Railtie
|
7
9
|
initializer "cassie.initialization" do
|
8
10
|
Cassie.logger = Rails.logger
|
9
|
-
|
10
|
-
config_file = Rails.root +
|
11
|
+
|
12
|
+
config_file = Rails.root + "config" + "cassie.yml"
|
11
13
|
if config_file.exist?
|
12
|
-
options = YAML
|
14
|
+
options = YAML.load(ERB.new(config_file.read).result)[Rails.env] # rubocop:disable Security/YAMLLoad
|
13
15
|
if Rails.env.development? || Rails.env.test?
|
14
|
-
schema_dir = Rails.root +
|
15
|
-
options[
|
16
|
+
schema_dir = Rails.root + "db" + "cassandra"
|
17
|
+
options["schema_directory"] = schema_dir.to_s if schema_dir.exist?
|
16
18
|
end
|
17
19
|
Cassie.configure!(options)
|
18
20
|
end
|