whi-cassie 1.1.1 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Simple configuration for connecting to Cassandra.
2
4
  #
3
5
  # :cluster should be a Hash of the options to initialize the Cassandra cluster.
@@ -19,7 +21,7 @@
19
21
  class Cassie::Config
20
22
  attr_reader :cluster
21
23
  attr_accessor :max_prepared_statements, :schema_directory, :default_keyspace
22
-
24
+
23
25
  def initialize(options = {})
24
26
  options = options.symbolize_keys
25
27
  @cluster = (options[:cluster] || {}).symbolize_keys
@@ -28,27 +30,27 @@ class Cassie::Config
28
30
  @schema_directory = options[:schema_directory]
29
31
  @default_keyspace = options[:default_keyspace]
30
32
  end
31
-
33
+
32
34
  # Get the actual keyspace mapped to the abstract name.
33
35
  def keyspace(name)
34
36
  @keyspaces[name.to_s] || name.to_s
35
37
  end
36
-
38
+
37
39
  # Get the list of keyspaces defined for the cluster.
38
40
  def keyspaces
39
41
  @keyspaces.values
40
42
  end
41
-
43
+
42
44
  # Get the list of abstract keyspace names.
43
45
  def keyspace_names
44
46
  @keyspaces.keys
45
47
  end
46
-
48
+
47
49
  # Add a mapping of a name to a keyspace.
48
50
  def add_keyspace(name, value)
49
51
  @keyspaces[name.to_s] = value
50
52
  end
51
-
53
+
52
54
  # Return the cluster options without passwords or tokens. Used for logging.
53
55
  def sanitized_cluster
54
56
  options = cluster.dup
@@ -59,5 +61,4 @@ class Cassie::Config
59
61
  options[:logger] = options[:logger].class.name if options.include?(:logger)
60
62
  options
61
63
  end
62
-
63
64
  end
@@ -1,5 +1,7 @@
1
- require 'active_model'
2
- require 'active_support/hash_with_indifferent_access'
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, :instance_reader => false, :instance_writer => false
44
- class_attribute :_keyspace, :instance_reader => false, :instance_writer => false
45
- class_attribute :_primary_key, :instance_reader => false, :instance_writer => false
46
- class_attribute :_columns, :instance_reader => false, :instance_writer => false
47
- class_attribute :_column_aliases, :instance_reader => false, :instance_writer => false
48
- class_attribute :_ordering_keys, :instance_reader => false, :instance_writer => false
49
- class_attribute :_counter_table, :instance_reader => false, :instance_writer => false
50
- class_attribute :find_subscribers, :instance_reader => false, :instance_writer => false
51
- class_attribute :read_consistency, :instance_reader => false, :instance_writer => false
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 = self._column_aliases.merge(name => name)
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 = self._column_aliases.merge(as => name)
119
+ self._column_aliases = _column_aliases.merge(as => name)
118
120
  end
119
121
 
120
- if type.to_s == "counter".freeze
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
- name = _column_aliases[name_or_alias] || name_or_alias
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(', ')} FROM #{full_table_name}"
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 << " WHERE #{where_clause}" if where_clause
229
-
230
+ cql += " WHERE #{where_clause}" if where_clause
231
+
230
232
  if order
231
- cql << " ORDER BY #{order}"
233
+ cql += " ORDER BY #{order}"
232
234
  end
233
-
235
+
234
236
  if limit
235
- cql << " LIMIT ?"
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 #{self.full_table_name}"
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 << " WHERE #{where_clause}"
299
+ cql += " WHERE #{where_clause}"
298
300
  else
299
- where = connection.prepare(cql)
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, :consistency => write_consistency)
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(:select => [ordering_key], :where => conditions, :limit => limit, :order => order_cql)
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(' AND '), values]
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?
@@ -455,36 +456,36 @@ module Cassie::Model
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(:consistency => consistency) if options[:consistency].nil?
463
+ options = options.merge(consistency: consistency) if options[:consistency].nil?
463
464
  options
464
465
  else
465
- {:consistency => consistency}
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 = {:consistency => write_consistency, :ttl => (ttl || persistence_ttl)}
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,16 @@ 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, :consistency => write_consistency)
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
+
533
534
  # Returns a hash of column to values. Column names will be symbols.
534
535
  def attributes
535
536
  hash = {}
@@ -538,38 +539,38 @@ module Cassie::Model
538
539
  end
539
540
  hash
540
541
  end
541
-
542
+
542
543
  # Subclasses can override this method to provide a TTL on the persisted record.
543
544
  def persistence_ttl
544
545
  nil
545
546
  end
546
-
547
+
547
548
  def eql?(other)
548
549
  other.is_a?(self.class) && other.key_hash == key_hash
549
550
  end
550
-
551
+
551
552
  def ==(other)
552
553
  eql?(other)
553
554
  end
554
-
555
+
555
556
  # Returns the primary key as a hash
556
557
  def key_hash
557
558
  hash = {}
558
559
  self.class.primary_key.each do |key|
559
- hash[key] = self.send(key)
560
+ hash[key] = send(key)
560
561
  end
561
562
  hash
562
563
  end
563
-
564
+
564
565
  private
565
-
566
+
566
567
  # Used for updating counter columns.
567
568
  def adjust_counter!(name, amount, ttl: nil)
568
569
  amount = amount.to_i
569
570
  if amount != 0
570
571
  run_callbacks(:update) do
571
572
  adjustment = (amount < 0 ? "#{name} = #{name} - #{amount.abs}" : "#{name} = #{name} + #{amount}")
572
- options = {:consistency => write_consistency, :ttl => (ttl || persistence_ttl)}
573
+ options = {consistency: write_consistency, ttl: (ttl || persistence_ttl)}
573
574
  self.class.connection.update(self.class.full_table_name, adjustment, key_hash, options)
574
575
  end
575
576
  end
@@ -577,13 +578,13 @@ module Cassie::Model
577
578
  value = (record ? record.send(name) : send(name) + amount)
578
579
  send("#{name}=", value)
579
580
  end
580
-
581
+
581
582
  # Returns a hash of value except for the ones that constitute the primary key
582
583
  def values_hash
583
584
  pk = self.class.primary_key
584
585
  hash = {}
585
586
  self.class.column_names.each do |name|
586
- hash[name] = send(name) unless pk.include?(name)
587
+ hash[name] = send(name) unless pk.include?(name)
587
588
  end
588
589
  hash
589
590
  end