extendi-cassandra_object 1.0.17 → 1.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2eb80f8a2bc136c5333d17e73df717ed4d2d33f8e28fa31ff30fdf7c8648e645
4
- data.tar.gz: 57c5cb2c0939b8eca99790ac0b7f80a643e04617f31762138aea076a6e868bea
3
+ metadata.gz: c5523ad43210b62402fbfb637042415f14a7fe174fa3f3646556b1b1e3701df9
4
+ data.tar.gz: a9ed0b2512e264395a1340c2fbccbaf9259d893105c738c9724d2ee0eba2a944
5
5
  SHA512:
6
- metadata.gz: ccdf0d3c0599fd1373d9dc214f5ebd3409acd43b80f27ace5951d5d31fad059bee0f8a78f719b2075fbe472e2639c1d4a7f510ad4570b49fa0e3db6f3fa2be34
7
- data.tar.gz: c61f6366852d7bbff248c80dee0ae620c0b4b087ae4905b45982e255563f8c560fbfbee8d217cbf23292b9ef0c2395a392cb9470f154bb7da17820e32a703c23
6
+ metadata.gz: f038a28315870cef3b0bc1366e3baa46d4098b7a7c3a4733ec02a2a71281861f87a111e9dfbf1d5c9a0c167b8e1e72198662dded4c7a7b90acbd154b5131e305
7
+ data.tar.gz: 58ab0a6d6347d97533334ee017ef8d7f9aaf9890e770d1b2abf3629f7e1b80f0a372241985c7c38e47dc4cebd8a2efa100a5c98384ff6ca17dea6927f976ea76
data/.gitignore CHANGED
@@ -4,3 +4,7 @@ Gemfile.lock
4
4
  .byebug_history
5
5
  .ruby-version
6
6
  TAGS
7
+ .editorconfig
8
+ .rubocop.yml
9
+ .prettierrc.js
10
+ .eslintrc.js
@@ -1,20 +1,21 @@
1
+ dist: trusty
2
+
1
3
  language: ruby
2
4
  rvm:
3
- - 2.4.0
4
- - 2.4.1
5
- - 2.4.2
6
5
  - 2.5.1
6
+ - 2.6.5
7
+ - 2.7.0
7
8
  env:
8
- - CASSANDRA_VERSION=2.1.2
9
+ - SCYLLA_VERSION=3.0.2
9
10
  - CASSANDRA_VERSION=3.0.10
10
11
  - CASSANDRA_VERSION=3.9
11
- - CASSANDRA_VERSION=2.1.2 ACTIVEMODEL_VERSION='< 5'
12
- - CASSANDRA_VERSION=3.0.10 ACTIVEMODEL_VERSION='< 5'
13
- - CASSANDRA_VERSION=3.9 ACTIVEMODEL_VERSION='< 5'
14
12
 
15
13
  jdk:
16
14
  - oraclejdk8
17
15
 
16
+ services:
17
+ - docker
18
+
18
19
  before_install:
19
20
  - sudo apt-get install libjna-java
20
21
  - sudo apt-get install python-support
@@ -23,7 +24,18 @@ before_install:
23
24
  - sudo pip install ccm
24
25
 
25
26
  install:
26
- - ccm create -n 1 -v $CASSANDRA_VERSION -i 127.0.0. -s -b test-cluster
27
- - ccm start
28
- - if [ -n "$ACTIVEMODEL_VERSION" ];then bundle add activemodel --version "$ACTIVEMODEL_VERSION"; fi
29
- - bundle install
27
+ - |
28
+ if [ -n "$CASSANDRA_VERSION" ];then
29
+ ccm create -n 1 -v $CASSANDRA_VERSION -i 127.0.0. -s -b test-cluster;
30
+ ccm start;
31
+ fi
32
+ if [ -n "$SCYLLA_VERSION" ];then
33
+ SCYLLA_IMAGE=scylladb/scylla:$SCYLLA_VERSION
34
+ docker pull $SCYLLA_IMAGE
35
+ docker run --name cassandra_test -d -p "9042:9042" -p "9160:9160" $SCYLLA_IMAGE
36
+ function check_scylla(){ docker exec -it cassandra_test nodetool status | grep UN; }
37
+ until check_scylla; do
38
+ echo "waiting..."
39
+ done
40
+ fi
41
+ bundle install
data/CHANGELOG CHANGED
@@ -0,0 +1,9 @@
1
+ # Changelog
2
+
3
+ ## [1.1.0] - 2020-06-18
4
+ ### Changed
5
+ - load_balancing_policy now accept only Cassandra::LoadBalancing::Policies objects
6
+
7
+ ## [1.1.1] - 2020-06-18
8
+ ### Changed
9
+ - add write_consistency on cassandra object config
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # Cassandra Object
2
2
  [![Build Status](https://secure.travis-ci.org/giovannelli/cassandra_object.png)](http://travis-ci.org/giovannelli/cassandra_object) [![Code Climate](https://codeclimate.com/github/giovannelli/cassandra_object/badges/gpa.svg)](https://codeclimate.com/github/giovannelli/cassandra_object)
3
3
 
4
- Cassandra Object uses ActiveModel to mimic much of the behavior in ActiveRecord.
4
+ Cassandra Object uses ActiveModel to mimic much of the behavior in ActiveRecord.
5
5
  Use cql3 provided by ruby-driver gem and uses the old thrift structure with the possible option at [this link](https://docs.datastax.com/en/cql/3.1/cql/cql_reference/create_table_r.html?hl=create%2Ctable):
6
6
 
7
7
  ```shell
@@ -11,22 +11,20 @@ CREATE TABLE keyspace.table (
11
11
  column1 text,
12
12
  value blob,
13
13
  PRIMARY KEY (key, column1)
14
- ) WITH
15
- COMPACT STORAGE
16
- AND CLUSTERING ORDER BY (column1 ASC)
17
- AND bloom_filter_fp_chance = 0.001
18
- AND caching = '{"keys":"ALL", "rows_per_partition":"NONE"}'
14
+ ) WITH bloom_filter_fp_chance = 0.01
15
+ AND caching = {'keys': 'ALL', 'rows_per_partition': 'ALL'}
19
16
  AND comment = ''
20
- AND compaction = {'min_sstable_size': '52428800', 'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'}
21
- AND compression = {'chunk_length_kb': '64', 'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
22
- AND dclocal_read_repair_chance = 0.0
17
+ AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'}
18
+ AND compression = {'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
19
+ AND crc_check_chance = 1.0
20
+ AND dclocal_read_repair_chance = 0.1
23
21
  AND default_time_to_live = 0
24
22
  AND gc_grace_seconds = 864000
25
23
  AND max_index_interval = 2048
26
24
  AND memtable_flush_period_in_ms = 0
27
25
  AND min_index_interval = 128
28
- AND read_repair_chance = 1.0
29
- AND speculative_retry = 'NONE';
26
+ AND read_repair_chance = 0.0
27
+ AND speculative_retry = '99.0PERCENTILE';
30
28
  ```
31
29
 
32
30
  You can also use the a custom schema structure with the possible options at [this link](https://docs.datastax.com/en/cql/3.3/cql/cql_reference/cqlCreateTable.html#tabProp):
@@ -39,20 +37,20 @@ CREATE TABLE keyspace.table (
39
37
  field2 varchar,
40
38
  field3 float,
41
39
  PRIMARY KEY (key)
42
- ) WITH
43
- bloom_filter_fp_chance = 0.001
44
- AND caching = {'keys':'ALL', 'rows_per_partition':'NONE'}'
40
+ ) WITH bloom_filter_fp_chance = 0.01
41
+ AND caching = {'keys': 'ALL', 'rows_per_partition': 'ALL'}
45
42
  AND comment = ''
46
- AND compaction = {'min_sstable_size': '52428800', 'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'}
47
- AND compression = {'chunk_length_kb': '64', 'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
48
- AND dclocal_read_repair_chance = 0.0
43
+ AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'}
44
+ AND compression = {'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
45
+ AND crc_check_chance = 1.0
46
+ AND dclocal_read_repair_chance = 0.1
49
47
  AND default_time_to_live = 0
50
48
  AND gc_grace_seconds = 864000
51
49
  AND max_index_interval = 2048
52
50
  AND memtable_flush_period_in_ms = 0
53
51
  AND min_index_interval = 128
54
- AND read_repair_chance = 1.0
55
- AND speculative_retry = 'NONE';
52
+ AND read_repair_chance = 0.0
53
+ AND speculative_retry = '99.0PERCENTILE';
56
54
  ```
57
55
 
58
56
  ## Installation
@@ -120,16 +118,16 @@ You can define a custom configuration for the cassandra connection, allowing you
120
118
  ```ruby
121
119
  class Widget < CassandraObject::BaseSchema
122
120
  string :name
123
-
121
+
124
122
  def self.custom_config
125
- #return custom cassandra configuration
123
+ #return custom cassandra configuration
126
124
  { }
127
125
  end
128
126
  end
129
127
  ```
130
-
128
+
131
129
  ## Using with Cassandra
132
-
130
+
133
131
  Add a config/cassandra.yml:
134
132
 
135
133
  ```yaml
@@ -140,6 +138,7 @@ development:
140
138
  connect_timeout: 0.1,
141
139
  request_timeout: 0.1,
142
140
  consistency: :any/:one/:two/:three/:quorum/:all/:local_quorum/:each_quorum/:serial/:local_serial/:local_one,
141
+ write_consistency: :any/:one/:two/:three/:quorum/:all/:local_quorum/:each_quorum/:serial/:local_serial/:local_one,
143
142
  protocol_version: 3,
144
143
  page_size: 10000,
145
144
  trace: true/false
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'extendi-cassandra_object'
5
- s.version = '1.0.17'
5
+ s.version = '1.1.1'
6
6
  s.description = 'Cassandra ActiveModel'
7
7
  s.summary = 'Cassandra ActiveModel'
8
8
  s.authors = ['Duccio Giovannelli', 'gotime']
@@ -17,7 +17,7 @@ Gem::Specification.new do |s|
17
17
  s.test_files = `git ls-files -- {test}/*`.split("\n")
18
18
  s.require_paths = ['lib']
19
19
 
20
- s.add_runtime_dependency('activemodel', '<= 5.2')
20
+ s.add_runtime_dependency('activemodel', '>= 4.2.0', '< 7.0.0')
21
21
  s.add_runtime_dependency('cassandra-driver', '>= 3.2.3')
22
22
  s.add_runtime_dependency('lz4-ruby', '>= 0.3.3')
23
23
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  gem 'cassandra-driver'
2
4
  require 'cassandra'
3
5
  require 'logger'
@@ -6,7 +8,6 @@ module CassandraObject
6
8
  module Adapters
7
9
  class CassandraAdapter < AbstractAdapter
8
10
  class QueryBuilder
9
-
10
11
  def initialize(adapter, scope)
11
12
  @adapter = adapter
12
13
  @scope = scope
@@ -27,22 +28,27 @@ module CassandraObject
27
28
  "SELECT #{select_string} FROM #{@scope.klass.column_family}",
28
29
  where_string_async(nil)
29
30
  ]
30
- str << "ALLOW FILTERING" if @scope.klass.allow_filtering
31
+ str << 'ALLOW FILTERING' if @scope.klass.allow_filtering
31
32
  return [] << str.delete_if(&:blank?) * ' '
32
33
  end
33
- @scope.id_values.map do |id|
34
- str = [
35
- "SELECT #{select_string} FROM #{@scope.klass.column_family}",
36
- where_string_async(id)
37
- ]
38
- str << "ALLOW FILTERING" if @scope.klass.allow_filtering
39
- str.delete_if(&:blank?) * ' '
40
- end
34
+
35
+ str = [
36
+ "SELECT #{select_string} FROM #{@scope.klass.column_family}",
37
+ where_string_async(@scope.id_values)
38
+ ]
39
+ str << 'ALLOW FILTERING' if @scope.klass.allow_filtering
40
+ [str.delete_if(&:blank?) * ' ']
41
41
  end
42
42
 
43
- def where_string_async(id)
43
+ def where_string_async(ids)
44
44
  wheres = @scope.where_values.dup.select.each_with_index { |_, i| i.even? }
45
- wheres << "#{@scope._key} = '#{id}'" if !id.nil?
45
+ if ids.present?
46
+ wheres << if ids.size > 1
47
+ "#{@scope._key} IN (#{ids.map { |id| "'#{id}'" }.join(',')})"
48
+ else
49
+ "#{@scope._key} = '#{ids.first}'"
50
+ end
51
+ end
46
52
  "WHERE #{wheres * ' AND '}" if wheres.any?
47
53
  end
48
54
  end
@@ -57,6 +63,7 @@ module CassandraObject
57
63
  :connections_per_local_node,
58
64
  :connections_per_remote_node,
59
65
  :consistency,
66
+ :write_consistency,
60
67
  :credentials,
61
68
  :futures_factory,
62
69
  :hosts,
@@ -82,14 +89,14 @@ module CassandraObject
82
89
  ])
83
90
 
84
91
  {
85
- load_balancing_policy: 'Cassandra::LoadBalancing::Policies::%s',
92
+ # load_balancing_policy: 'Cassandra::LoadBalancing::Policies::%s',
86
93
  reconnection_policy: 'Cassandra::Reconnection::Policies::%s',
87
94
  retry_policy: 'Cassandra::Retry::Policies::%s'
88
95
  }.each do |policy_key, class_template|
89
96
  params = cluster_options[policy_key]
90
97
  if params
91
98
  if params.is_a?(Hash)
92
- cluster_options[policy_key] = (class_template % [params[:policy].classify]).constantize.new(*params[:params]||[])
99
+ cluster_options[policy_key] = (class_template % [params[:policy].classify]).constantize.new(*params[:params] || [])
93
100
  else
94
101
  cluster_options[policy_key] = (class_template % [params.classify]).constantize.new
95
102
  end
@@ -101,7 +108,8 @@ module CassandraObject
101
108
  heartbeat_interval: cluster_options.keys.include?(:heartbeat_interval) ? cluster_options[:heartbeat_interval] : 30,
102
109
  idle_timeout: cluster_options[:idle_timeout] || 60,
103
110
  max_schema_agreement_wait: 1,
104
- consistency: cluster_options[:consistency] || :one,
111
+ consistency: cluster_options[:consistency] || :local_one,
112
+ write_consistency: cluster_options[:write_consistency] || cluster_options[:consistency] || :local_one,
105
113
  protocol_version: cluster_options[:protocol_version] || 3,
106
114
  page_size: cluster_options[:page_size] || 10000
107
115
  })
@@ -116,6 +124,8 @@ module CassandraObject
116
124
  end
117
125
 
118
126
  def execute(statement, arguments = [])
127
+ consistency = config[:write_consistency] || config[:consistency]
128
+ # puts "cassandra adapter: #{consistency}"
119
129
  ActiveSupport::Notifications.instrument('cql.cassandra_object', cql: statement) do
120
130
  type_hints = []
121
131
  arguments.each { |a| type_hints << CassandraObject::Types::TypeHelper.guess_type(a) } unless arguments.nil?
@@ -124,6 +134,8 @@ module CassandraObject
124
134
  end
125
135
 
126
136
  def execute_async(queries, arguments = [])
137
+ consistency = config[:consistency]
138
+ # puts "execute_async adapter: #{consistency}"
127
139
  retries = 0
128
140
  futures = queries.map do |q|
129
141
  ActiveSupport::Notifications.instrument('cql.cassandra_object', cql: q) do
@@ -146,8 +158,8 @@ module CassandraObject
146
158
  def select(scope)
147
159
  queries = QueryBuilder.new(self, scope).to_query_async
148
160
  # todo paginate
149
- arguments = scope.where_values.select.each_with_index{ |_, i| i.odd? }.reject{ |c| c.blank? }
150
- cql_rows = execute_async(queries, arguments).map{|item| item.rows.map{|x| x}}.flatten!
161
+ arguments = scope.where_values.select.each_with_index { |_, i| i.odd? }.reject { |c| c.blank? }
162
+ cql_rows = execute_async(queries, arguments).map { |item| item.rows.map { |x| x } }.flatten!
151
163
  cql_rows.each do |cql_row|
152
164
  attributes = cql_row.to_hash
153
165
  key = attributes.delete(scope._key)
@@ -165,31 +177,31 @@ module CassandraObject
165
177
 
166
178
  def write(table, id, attributes, ttl = nil)
167
179
  statement = "INSERT INTO #{table} (#{(attributes.keys).join(',')}) VALUES (#{(['?'] * attributes.size).join(',')})"
168
- statement += " USING TTL #{ttl.to_s}" if ttl.present?
180
+ statement += " USING TTL #{ttl}" if ttl.present?
169
181
  arguments = attributes.values
170
182
  execute(statement, arguments)
171
183
  end
172
184
 
173
185
  def write_update(table, id, attributes)
174
- queries =[]
186
+ queries = []
175
187
  # id here is the name of the key of the model
176
188
  id_value = attributes[id]
177
189
  if (not_nil_attributes = attributes.reject { |key, value| value.nil? }).any?
178
190
  statement = "INSERT INTO #{table} (#{(not_nil_attributes.keys).join(',')}) VALUES (#{(['?'] * not_nil_attributes.size).join(',')})"
179
- queries << {query: statement, arguments: not_nil_attributes.values}
191
+ queries << { query: statement, arguments: not_nil_attributes.values }
180
192
  end
181
193
  if (nil_attributes = attributes.select { |key, value| value.nil? }).any?
182
- queries << {query: "DELETE #{nil_attributes.keys.join(',')} FROM #{table} WHERE #{id} = ?", arguments: [id_value.to_s]}
194
+ queries << { query: "DELETE #{nil_attributes.keys.join(',')} FROM #{table} WHERE #{id} = ?", arguments: [id_value.to_s] }
183
195
  end
184
196
  execute_batchable(queries)
185
197
  end
186
198
 
187
199
  def delete(scope, ids, attributes = {})
188
200
  ids = [ids] if !ids.is_a?(Array)
189
- statement = "DELETE FROM #{scope.column_family} WHERE #{scope._key} IN (#{ids.map{|id| '?'}.join(',')})"
201
+ statement = "DELETE FROM #{scope.column_family} WHERE #{scope._key} IN (#{ids.map { |id| '?' }.join(',')})"
190
202
  arguments = ids
191
203
  unless attributes.blank?
192
- statement += " AND #{attributes.keys.map{ |k| "#{k} = ?" }.join(' AND ')}"
204
+ statement += " AND #{attributes.keys.map { |k| "#{k} = ?" }.join(' AND ')}"
193
205
  arguments += attributes.values
194
206
  end
195
207
  execute(statement, arguments)
@@ -197,20 +209,23 @@ module CassandraObject
197
209
 
198
210
  def delete_single(obj)
199
211
  keys = obj.class._keys
200
- wheres = keys.map{ |k| "#{k} = ?" }.join(' AND ')
201
- arguments = keys.map{ |k| obj.attributes[k] }
212
+ wheres = keys.map { |k| "#{k} = ?" }.join(' AND ')
213
+ arguments = keys.map { |k| obj.attributes[k] }
202
214
  statement = "DELETE FROM #{obj.class.column_family} WHERE #{wheres}"
203
215
  execute(statement, arguments)
204
216
  end
205
217
 
206
218
  def execute_batch(statements)
207
- raise 'No can do' if statements.empty?
219
+ raise 'Statements is empty!' if statements.empty?
220
+ consistency = config[:write_consistency] || config[:consistency]
221
+ # puts "cassandra adapter execute batch #{consistency}"
222
+
208
223
  batch = connection.batch do |b|
209
224
  statements.each do |statement|
210
225
  b.add(statement[:query], arguments: statement[:arguments])
211
226
  end
212
227
  end
213
- connection.execute(batch, page_size: config[:page_size])
228
+ connection.execute(batch, consistency: consistency, page_size: config[:page_size])
214
229
  end
215
230
 
216
231
  # SCHEMA
@@ -239,7 +254,7 @@ module CassandraObject
239
254
  def schema_execute(cql, keyspace)
240
255
  schema_db = Cassandra.cluster cassandra_cluster_options
241
256
  connection = schema_db.connect keyspace
242
- connection.execute cql, consistency: consistency
257
+ connection.execute cql, consistency: config[:write_consistency] || config[:consistency]
243
258
  end
244
259
 
245
260
  def cassandra_version
@@ -248,14 +263,6 @@ module CassandraObject
248
263
 
249
264
  # /SCHEMA
250
265
 
251
- def consistency
252
- defined?(@consistency) ? @consistency : nil
253
- end
254
-
255
- def consistency=(val)
256
- @consistency = val
257
- end
258
-
259
266
  def statement_create_with_options(stmt, options = '')
260
267
  if !options.nil?
261
268
  statement_with_options stmt, options
@@ -266,7 +273,7 @@ module CassandraObject
266
273
  AND caching = '{\"keys\":\"ALL\", \"rows_per_partition\":\"NONE\"}'
267
274
  AND comment = ''
268
275
  AND compaction = {'min_sstable_size': '52428800', 'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'}
269
- AND compression = {'chunk_length_kb': '64', 'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
276
+ AND compression = {'chunk_length_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'}
270
277
  AND dclocal_read_repair_chance = 0.0
271
278
  AND default_time_to_live = 0
272
279
  AND gc_grace_seconds = 864000
@@ -276,24 +283,24 @@ module CassandraObject
276
283
  AND read_repair_chance = 1.0
277
284
  AND speculative_retry = 'NONE';"
278
285
  else
279
- "#{stmt} WITH read_repair_chance = 0.0
280
- AND dclocal_read_repair_chance = 0.1
281
- AND gc_grace_seconds = 864000
282
- AND bloom_filter_fp_chance = 0.01
283
- AND caching = { 'keys' : 'ALL', 'rows_per_partition' : 'NONE' }
286
+ "#{stmt} WITH bloom_filter_fp_chance = 0.01
287
+ AND caching = {'keys': 'ALL', 'rows_per_partition': 'ALL'}
284
288
  AND comment = ''
285
- AND compaction = { 'class' : 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold' : 32, 'min_threshold' : 4 }
286
- AND compression = { 'chunk_length_in_kb' : 64, 'class' : 'org.apache.cassandra.io.compress.LZ4Compressor' }
289
+ AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'}
290
+ AND compression = {'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
291
+ AND crc_check_chance = 1.0
292
+ AND dclocal_read_repair_chance = 0.1
287
293
  AND default_time_to_live = 0
288
- AND speculative_retry = '99PERCENTILE'
289
- AND min_index_interval = 128
294
+ AND gc_grace_seconds = 864000
290
295
  AND max_index_interval = 2048
291
- AND crc_check_chance = 1.0;
296
+ AND memtable_flush_period_in_ms = 0
297
+ AND min_index_interval = 128
298
+ AND read_repair_chance = 0.0
299
+ AND speculative_retry = '99.0PERCENTILE';
292
300
  "
293
301
  end
294
302
  end
295
303
  end
296
-
297
304
  end
298
305
  end
299
306
  end
@@ -26,34 +26,39 @@ module CassandraObject
26
26
 
27
27
  if @scope.id_values.empty?
28
28
  str = [
29
- "SELECT #{select_string} FROM #{@scope.klass.column_family}",
30
- where_string_async(nil)
29
+ "SELECT #{select_string} FROM #{@scope.klass.column_family}",
30
+ where_string_async(nil)
31
31
  ]
32
32
  str << 'ALLOW FILTERING' if @scope.klass.allow_filtering
33
33
  return [] << str.delete_if(&:blank?) * ' '
34
34
  end
35
- @scope.id_values.map do |id|
36
- str = [
37
- "SELECT #{select_string} FROM #{@scope.klass.column_family}",
38
- where_string_async(id)
39
- ]
40
- str << 'ALLOW FILTERING' if @scope.klass.allow_filtering
41
- str.delete_if(&:blank?) * ' '
42
- end
35
+ str = [
36
+ "SELECT #{select_string} FROM #{@scope.klass.column_family}",
37
+ where_string_async(@scope.id_values)
38
+ ]
39
+ str << 'ALLOW FILTERING' if @scope.klass.allow_filtering
40
+ [str.delete_if(&:blank?) * ' ']
43
41
  end
44
42
 
45
- def where_string_async(id)
43
+ def where_string_async(ids)
46
44
  conditions = []
47
- conditions << "#{@adapter.primary_key_column} = '#{id}'" if !id.nil?
45
+
46
+ if ids.present?
47
+ conditions << if ids.size > 1
48
+ "#{@adapter.primary_key_column} IN (#{ids.map { |id| "'#{id}'" }.join(',')})"
49
+ else
50
+ "#{@adapter.primary_key_column} = '#{ids.first}'"
51
+ end
52
+ end
53
+
48
54
  select_values = @scope.select_values.select { |sv| sv != :column1 }
49
55
  if select_values.size > 0
50
56
  select_str = select_values.size > 1 ? "column1 IN (#{select_values.map { |sv| '?' }.join(',')})" : 'column1 = ?'
51
57
  conditions << select_str
52
58
  end
53
59
  conditions += @scope.where_values.select.each_with_index { |_, i| i.even? }
54
- return conditions.any? ? "WHERE #{conditions.join(' AND ')}" : nil
60
+ conditions.any? ? "WHERE #{conditions.join(' AND ')}" : nil
55
61
  end
56
-
57
62
  end
58
63
 
59
64
  def primary_key_column
@@ -70,6 +75,7 @@ module CassandraObject
70
75
  :connections_per_local_node,
71
76
  :connections_per_remote_node,
72
77
  :consistency,
78
+ :write_consistency,
73
79
  :credentials,
74
80
  :futures_factory,
75
81
  :hosts,
@@ -95,26 +101,27 @@ module CassandraObject
95
101
  ])
96
102
 
97
103
  {
98
- load_balancing_policy: 'Cassandra::LoadBalancing::Policies::%s',
104
+ # load_balancing_policy: 'Cassandra::LoadBalancing::Policies::%s',
99
105
  reconnection_policy: 'Cassandra::Reconnection::Policies::%s',
100
106
  retry_policy: 'Cassandra::Retry::Policies::%s'
101
107
  }.each do |policy_key, class_template|
102
108
  params = cluster_options[policy_key]
103
109
  if params
104
110
  if params.is_a?(Hash)
105
- cluster_options[policy_key] = (class_template % [params[:policy].classify]).constantize.new(*params[:params]||[])
111
+ cluster_options[policy_key] = (class_template % [params[:policy].classify]).constantize.new(*params[:params] || [])
106
112
  else
107
113
  cluster_options[policy_key] = (class_template % [params.classify]).constantize.new
108
114
  end
109
115
  end
110
116
  end
111
-
117
+
112
118
  # Setting defaults
113
119
  cluster_options.merge!({
114
120
  heartbeat_interval: cluster_options.keys.include?(:heartbeat_interval) ? cluster_options[:heartbeat_interval] : 30,
115
121
  idle_timeout: cluster_options[:idle_timeout] || 60,
116
122
  max_schema_agreement_wait: 1,
117
- consistency: cluster_options[:consistency] || :one,
123
+ consistency: cluster_options[:consistency] || :local_one,
124
+ write_consistency: cluster_options[:write_consistency] || cluster_options[:consistency] || :local_one,
118
125
  protocol_version: cluster_options[:protocol_version] || 3,
119
126
  page_size: cluster_options[:page_size] || 10000
120
127
  })
@@ -129,12 +136,17 @@ module CassandraObject
129
136
  end
130
137
 
131
138
  def execute(statement, arguments = [])
139
+ consistency = config[:write_consistency] || config[:consistency]
140
+ # puts "schemaless adapter: #{consistency}"
132
141
  ActiveSupport::Notifications.instrument('cql.cassandra_object', cql: statement) do
133
142
  connection.execute statement, arguments: arguments, consistency: consistency, page_size: config[:page_size]
134
143
  end
135
144
  end
136
145
 
137
146
  def execute_async(queries, arguments = [], per_page = nil, next_cursor = nil)
147
+ consistency = config[:consistency]
148
+ # puts "schemaless adapter async: #{consistency}"
149
+
138
150
  retries = 0
139
151
  per_page ||= config[:page_size]
140
152
  futures = queries.map { |q|
@@ -164,7 +176,7 @@ module CassandraObject
164
176
  item.rows.each { |x| ids << x[primary_key_column] }
165
177
  new_next_cursor = item.paging_state unless item.last_page?
166
178
  end
167
- return {ids: ids, new_next_cursor: new_next_cursor}
179
+ { ids: ids, new_next_cursor: new_next_cursor }
168
180
  end
169
181
 
170
182
  def select(scope)
@@ -172,19 +184,19 @@ module CassandraObject
172
184
  queries.compact! if queries.present?
173
185
  raise CassandraObject::RecordNotFound if !queries.present?
174
186
 
175
- arguments = scope.select_values.select{ |sv| sv != :column1 }.map(&:to_s)
176
- arguments += scope.where_values.select.each_with_index{ |_, i| i.odd? }.reject{ |c| c.empty? }.map(&:to_s)
187
+ arguments = scope.select_values.select { |sv| sv != :column1 }.map(&:to_s)
188
+ arguments += scope.where_values.select.each_with_index { |_, i| i.odd? }.reject { |c| c.empty? }.map(&:to_s)
177
189
  records = execute_async(queries, arguments).map do |item|
178
190
  # pagination
179
191
  elems = []
180
192
  loop do
181
- item.rows.each{ |x| elems << x }
193
+ item.rows.each { |x| elems << x }
182
194
  break if item.last_page?
183
195
  item = item.next_page
184
196
  end
185
197
  elems
186
198
  end
187
- {results: records.flatten!}
199
+ { results: records.flatten! }
188
200
  end
189
201
 
190
202
  def select_paginated(scope)
@@ -192,15 +204,15 @@ module CassandraObject
192
204
  queries.compact! if queries.present?
193
205
  raise CassandraObject::RecordNotFound if !queries.present?
194
206
 
195
- arguments = scope.select_values.select{ |sv| sv != :column1 }.map(&:to_s)
196
- arguments += scope.where_values.select.each_with_index{ |_, i| i.odd? }.reject{ |c| c.empty? }.map(&:to_s)
207
+ arguments = scope.select_values.select { |sv| sv != :column1 }.map(&:to_s)
208
+ arguments += scope.where_values.select.each_with_index { |_, i| i.odd? }.reject { |c| c.empty? }.map(&:to_s)
197
209
  new_next_cursor = nil
198
210
  records = []
199
211
  execute_async(queries, arguments, scope.limit_value, scope.next_cursor).each do |item|
200
212
  new_next_cursor = item.paging_state unless item.last_page?
201
- item.rows.each{ |x| records << x }
213
+ item.rows.each { |x| records << x }
202
214
  end
203
- {results: records, new_next_cursor: new_next_cursor}
215
+ { results: records, new_next_cursor: new_next_cursor }
204
216
  end
205
217
 
206
218
  def insert(table, id, attributes, ttl = nil)
@@ -213,16 +225,15 @@ module CassandraObject
213
225
 
214
226
  def write(table, id, attributes, ttl)
215
227
  queries = []
216
- # puts attributes
217
228
  attributes.each do |column, value|
218
229
  if !value.nil?
219
230
  query = "INSERT INTO #{table} (#{primary_key_column},column1,value) VALUES (?,?,?)"
220
- query += " USING TTL #{ttl.to_s}" if !ttl.nil?
231
+ query += " USING TTL #{ttl}" if !ttl.nil?
221
232
  args = [id.to_s, column.to_s, value.to_s]
222
233
 
223
- queries << {query: query, arguments: args}
234
+ queries << { query: query, arguments: args }
224
235
  else
225
- queries << {query: "DELETE FROM #{table} WHERE #{primary_key_column} = ? AND column1= ?", arguments: [id.to_s, column.to_s]}
236
+ queries << { query: "DELETE FROM #{table} WHERE #{primary_key_column} = ? AND column1= ?", arguments: [id.to_s, column.to_s] }
226
237
  end
227
238
  end
228
239
  execute_batchable(queries)
@@ -232,18 +243,20 @@ module CassandraObject
232
243
  ids = [ids] if !ids.is_a?(Array)
233
244
  arguments = nil
234
245
  arguments = ids if ids.size == 1
235
- statement = "DELETE FROM #{table} WHERE #{create_ids_where_clause(ids)}" #.gsub('?', ids.map { |id| "'#{id}'" }.join(','))
246
+ statement = "DELETE FROM #{table} WHERE #{create_ids_where_clause(ids)}" # .gsub('?', ids.map { |id| "'#{id}'" }.join(','))
236
247
  execute(statement, arguments)
237
248
  end
238
249
 
239
250
  def execute_batch(statements)
240
- raise 'No can do' if statements.empty?
251
+ consistency = config[:write_consistency] || config[:consistency]
252
+ # puts "schemaless execute batch #{consistency}"
253
+ raise 'Statements is empty!' if statements.empty?
241
254
  batch = connection.batch do |b|
242
255
  statements.each do |statement|
243
256
  b.add(statement[:query], arguments: statement[:arguments])
244
257
  end
245
258
  end
246
- connection.execute(batch, page_size: config[:page_size])
259
+ connection.execute(batch, consistency: consistency, page_size: config[:page_size])
247
260
  end
248
261
 
249
262
  # SCHEMA
@@ -270,7 +283,7 @@ module CassandraObject
270
283
  def schema_execute(cql, keyspace)
271
284
  schema_db = Cassandra.cluster cassandra_cluster_options
272
285
  connection = schema_db.connect keyspace
273
- connection.execute cql, consistency: consistency
286
+ connection.execute cql, consistency: config[:write_consistency] || config[:consistency]
274
287
  end
275
288
 
276
289
  def cassandra_version
@@ -279,14 +292,6 @@ module CassandraObject
279
292
 
280
293
  # /SCHEMA
281
294
 
282
- def consistency
283
- defined?(@consistency) ? @consistency : nil
284
- end
285
-
286
- def consistency=(val)
287
- @consistency = val
288
- end
289
-
290
295
  def statement_create_with_options(stmt, options)
291
296
  if !options.nil?
292
297
  statement_with_options stmt, options
@@ -299,7 +304,7 @@ module CassandraObject
299
304
  AND caching = '{\"keys\":\"ALL\", \"rows_per_partition\":\"NONE\"}'
300
305
  AND comment = ''
301
306
  AND compaction = {'min_sstable_size': '52428800', 'class': 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy'}
302
- AND compression = {'chunk_length_kb': '64', 'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
307
+ AND compression = {'chunk_length_kb': '64', 'class': 'org.apache.cassandra.io.compress.LZ4Compressor'}
303
308
  AND dclocal_read_repair_chance = 0.0
304
309
  AND default_time_to_live = 0
305
310
  AND gc_grace_seconds = 864000
@@ -309,19 +314,20 @@ module CassandraObject
309
314
  AND read_repair_chance = 1.0
310
315
  AND speculative_retry = 'NONE';"
311
316
  else
312
- "#{stmt} WITH read_repair_chance = 0.0
313
- AND dclocal_read_repair_chance = 0.1
314
- AND gc_grace_seconds = 864000
315
- AND bloom_filter_fp_chance = 0.01
316
- AND caching = { 'keys' : 'ALL', 'rows_per_partition' : 'NONE' }
317
+ "#{stmt} WITH bloom_filter_fp_chance = 0.01
318
+ AND caching = {'keys': 'ALL', 'rows_per_partition': 'ALL'}
317
319
  AND comment = ''
318
- AND compaction = { 'class' : 'org.apache.cassandra.db.compaction.SizeTieredCompactionStrategy', 'max_threshold' : 32, 'min_threshold' : 4 }
319
- AND compression = { 'chunk_length_in_kb' : 64, 'class' : 'org.apache.cassandra.io.compress.LZ4Compressor' }
320
+ AND compaction = {'class': 'SizeTieredCompactionStrategy', 'max_threshold': '32', 'min_threshold': '4'}
321
+ AND compression = {'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
322
+ AND crc_check_chance = 1.0
323
+ AND dclocal_read_repair_chance = 0.1
320
324
  AND default_time_to_live = 0
321
- AND speculative_retry = '99PERCENTILE'
322
- AND min_index_interval = 128
325
+ AND gc_grace_seconds = 864000
323
326
  AND max_index_interval = 2048
324
- AND crc_check_chance = 1.0;
327
+ AND memtable_flush_period_in_ms = 0
328
+ AND min_index_interval = 128
329
+ AND read_repair_chance = 0.0
330
+ AND speculative_retry = '99.0PERCENTILE';
325
331
  "
326
332
 
327
333
  end
@@ -332,9 +338,8 @@ module CassandraObject
332
338
  return ids if ids.empty?
333
339
  ids = ids.first if ids.is_a?(Array) && ids.one?
334
340
  sql = ids.is_a?(Array) ? "#{primary_key_column} IN (#{ids.map { |id| "'#{id}'" }.join(',')})" : "#{primary_key_column} = ?"
335
- return sql
341
+ sql
336
342
  end
337
-
338
343
  end
339
344
  end
340
345
  end
@@ -5,9 +5,9 @@ module CassandraObject
5
5
 
6
6
  included do
7
7
  if ActiveModel::VERSION::STRING < '3.2'
8
- attribute_method_suffix("", "=")
8
+ attribute_method_suffix('', '=')
9
9
  else
10
- attribute_method_suffix("=")
10
+ attribute_method_suffix('=')
11
11
  end
12
12
 
13
13
  # (Alias for the protected read_attribute method).
@@ -4,10 +4,10 @@ module CassandraObject
4
4
 
5
5
  included do
6
6
  class_attribute :batch_statements
7
+ attr_accessor :store_updated_at
7
8
  end
8
9
 
9
10
  module ClassMethods
10
-
11
11
  def ttl=(value)
12
12
  @ttl = value
13
13
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CassandraObject
2
4
  class Scope
3
5
  module FinderMethods
@@ -16,50 +18,60 @@ module CassandraObject
16
18
  end
17
19
 
18
20
  def find_in_batches(id, next_cursor = nil)
19
- obj = self.clone
21
+ obj = clone
20
22
  obj.is_all = true
21
23
  obj.next_cursor = next_cursor
22
24
  obj.where_ids(id).execute_paged
23
25
  end
24
26
 
25
27
  def find_all_in_batches(next_cursor = nil)
26
- obj = self.clone
28
+ obj = clone
27
29
  obj.is_all = true
28
30
  obj.next_cursor = next_cursor
29
31
  obj.execute
30
32
  end
31
33
 
32
34
  def first
33
- return limit(1).find_all_in_batches[:results].first if self.schema_type == :dynamic_attributes || self.schema_type == :schemaless
35
+ return limit(1).find_all_in_batches[:results].first if schema_type == :dynamic_attributes || schema_type == :schemaless
34
36
  limit(1).execute.first
35
37
  end
36
38
 
37
39
  private
38
40
 
39
- def find_one(id)
40
- if id.blank?
41
- not_found(id)
42
- elsif self.schema_type == :dynamic_attributes
43
- record = where_ids(id).execute
44
- not_found(id) if record.empty?
45
- record
46
- elsif record = where_ids(id)[0]
47
- record
48
- else
49
- not_found(id)
41
+ def find_one(id)
42
+ if id.blank?
43
+ not_found(id)
44
+ elsif schema_type == :dynamic_attributes
45
+ record = where_ids(id).execute
46
+ not_found(id) if record.empty?
47
+ record
48
+ elsif record = where_ids(id)[0]
49
+ record
50
+ else
51
+ not_found(id)
52
+ end
50
53
  end
51
- end
52
54
 
53
- def find_some(ids)
54
- ids = ids.flatten
55
- return [] if ids.empty?
56
- ids = ids.compact.map(&:to_s).uniq
57
- where_ids(ids).execute
58
- end
55
+ def find_some(pids)
56
+ ids = pids.flatten.compact.uniq.map(&:to_s)
57
+ return [] if ids.empty?
59
58
 
60
- def not_found(id)
61
- raise CassandraObject::RecordNotFound, "Couldn't find #{self.name} with key #{id.inspect}"
62
- end
59
+ qr = where_ids(ids).execute
60
+ is_dymnamic = qr.is_a?(Hash)
61
+
62
+ results = qr.sort_by do |r|
63
+ id = r.keys.first if r.is_a?(Hash)
64
+ id = r[0] if r.is_a?(Array)
65
+ id = r.id if id.nil?
66
+ ids.index(id)
67
+ end
68
+
69
+ is_dymnamic ? Hash[results] : results
70
+ end
71
+
72
+ def not_found(id)
73
+ raise CassandraObject::RecordNotFound, "Couldn't find #{name} with key #{id.inspect}"
74
+ end
63
75
  end
64
76
  end
65
77
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module CassandraObject
2
4
  module Scoping
3
5
  extend ActiveSupport::Concern
@@ -15,7 +15,11 @@ module CassandraObject
15
15
 
16
16
  before_update if: :changed? do
17
17
  if self.class.timestamps
18
- self.updated_at = Time.current
18
+ if store_updated_at.present?
19
+ self.updated_at = store_updated_at
20
+ else
21
+ self.updated_at = Time.current
22
+ end
19
23
  end
20
24
  end
21
25
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  Bundler.require :cassandra
2
4
 
3
5
  CassandraObject::Base.config = {
@@ -13,7 +15,7 @@ CassandraObject::Base.config = {
13
15
  connections_per_local_node: 4,
14
16
  schema_refresh_delay: 0.1,
15
17
  schema_refresh_timeout: 0.1,
16
- load_balancing_policy: 'RoundRobin',
18
+ load_balancing_policy: Cassandra::LoadBalancing::Policies::RoundRobin.new,
17
19
  reconnection_policy: { policy: 'Constant', params: [5] },
18
20
  retry_policy: 'Default',
19
21
  # connections_per_remote_node: nil,
@@ -30,15 +32,12 @@ end
30
32
  sleep 1
31
33
  CassandraObject::Schema.create_keyspace 'cassandra_object_test'
32
34
  CassandraObject::Schemaless.create_column_family 'Issues'
33
- CassandraObject::Schema.create_column_family 'IssueSchemas', {attributes: 'id text, title text, description text, field float, intero int, created_at timestamp, updated_at timestamp, PRIMARY KEY (id)', options: {}}
34
- CassandraObject::Schema.create_column_family 'IssueSchemaCks', {attributes: 'id text, type text, date timestamp, value float, PRIMARY KEY (id, type, date)', options: {}}
35
+ CassandraObject::Schema.create_column_family 'IssueSchemas', { attributes: 'id text, title text, description text, field float, intero int, created_at timestamp, updated_at timestamp, PRIMARY KEY (id)', options: {} }
36
+ CassandraObject::Schema.create_column_family 'IssueSchemaCks', { attributes: 'id text, type text, date timestamp, value float, PRIMARY KEY (id, type, date)', options: {} }
35
37
  CassandraObject::Schemaless.create_column_family 'IssueDynamics'
36
38
  CassandraObject::Schemaless.create_column_family 'IssuesCustomConfig'
37
- CassandraObject::Schema.create_column_family 'IssueSchemaFathers', {attributes: 'id text, title text, field float, created_at timestamp, updated_at timestamp, PRIMARY KEY (id)', options: {}}
38
- CassandraObject::Schema.create_column_family 'IssueSchemaChildren', {attributes: 'id text, title text, description text, field float, created_at timestamp, updated_at timestamp, issue_schema_father_id text, PRIMARY KEY (id)', options: {}}
39
- CassandraObject::BaseSchemaless.adapter.consistency = :quorum
40
- CassandraObject::BaseSchemalessDynamic.adapter.consistency = :quorum
41
- CassandraObject::BaseSchema.adapter.consistency = :quorum
39
+ CassandraObject::Schema.create_column_family 'IssueSchemaFathers', { attributes: 'id text, title text, field float, created_at timestamp, updated_at timestamp, PRIMARY KEY (id)', options: {} }
40
+ CassandraObject::Schema.create_column_family 'IssueSchemaChildren', { attributes: 'id text, title text, description text, field float, created_at timestamp, updated_at timestamp, issue_schema_father_id text, PRIMARY KEY (id)', options: {} }
42
41
 
43
42
  CassandraObject::Base.class_eval do
44
43
  class_attribute :created_records
@@ -60,7 +59,6 @@ end
60
59
 
61
60
  module ActiveSupport
62
61
  class TestCase
63
-
64
62
  self.test_order = :random
65
63
 
66
64
  def after_setup
@@ -16,8 +16,6 @@ class CassandraObject::ConnectionsTest < CassandraObject::TestCase
16
16
  threads = []
17
17
 
18
18
  (0..10).collect do |i|
19
-
20
- # puts "spawn thread #{i}"
21
19
  thr = Thread.new do
22
20
  begin
23
21
  IssueSchema.find(ids)
@@ -4,7 +4,6 @@
4
4
  require 'test_helper'
5
5
 
6
6
  class CassandraObject::PersistenceSchemaCkTest < CassandraObject::TestCase
7
-
8
7
  test 'composite key' do
9
8
  time1 = Time.now
10
9
  time2 = time1 + 1.second
@@ -25,19 +24,17 @@ class CassandraObject::PersistenceSchemaCkTest < CassandraObject::TestCase
25
24
  item = res[1]
26
25
  assert_equal '1', item.id
27
26
  assert_equal time2.to_i, item.date.to_i
28
-
29
27
  end
30
28
 
31
29
  test 'delete' do
32
30
  IssueSchemaCk.create(id: '1', type: 'first', date: Time.now, value: 1.to_f)
33
31
  IssueSchemaCk.create(id: '1', type: 'second', date: Time.now, value: 1.to_f)
34
-
35
32
  IssueSchemaCk.delete('1')
36
33
  assert_equal 0, IssueSchemaCk.find_by_id([1]).size
37
34
  end
38
35
 
39
36
  test 'delete with attributes' do
40
- time = Time.now
37
+ time = Time.now - 10.days
41
38
  IssueSchemaCk.create(id: '1', type: 'first', date: time, value: 1.to_f)
42
39
  IssueSchemaCk.create(id: '1', type: 'first', date: Time.now, value: 1.to_f)
43
40
  IssueSchemaCk.create(id: '2', type: 'first', date: time, value: 1.to_f)
@@ -65,5 +62,4 @@ class CassandraObject::PersistenceSchemaCkTest < CassandraObject::TestCase
65
62
  IssueSchemaCk.find_by_id(['1']).first.destroy
66
63
  assert_equal 1, IssueSchemaCk.find_by_id([1]).size
67
64
  end
68
-
69
65
  end
@@ -26,6 +26,38 @@ class CassandraObject::FinderMethodsTest < CassandraObject::TestCase
26
26
  assert_equal [first_issue, second_issue].to_set, Issue.find([first_issue.id, second_issue.id]).to_set
27
27
  end
28
28
 
29
+ test 'IssueDynamic: find with ids sorted' do
30
+ ids = (0..999).to_a.map(&:to_s)
31
+ ids.each do |i|
32
+ IssueDynamic.create(key: i, title: "foo_title_#{i}")
33
+ end
34
+ ids_to_find = ids.sample(10)
35
+ assert_equal ids_to_find, IssueDynamic.find(ids_to_find).keys
36
+ IssueDynamic.delete_all
37
+ end
38
+
39
+ test 'Issue: find with ids sorted' do
40
+ ids = (0..999).to_a.map(&:to_s)
41
+ ids.each do |i|
42
+ Issue.create(id: i, title: "foo_title_#{i}")
43
+ end
44
+ ids_to_find = ids.sample(10)
45
+ assert_equal ids_to_find, Issue.find(ids_to_find).map(&:id)
46
+ Issue.delete_all
47
+ end
48
+
49
+ test 'IssueSchemaCk: find with ids sorted' do
50
+ ids = (0..999).to_a.map(&:to_s)
51
+ ids.each do |i|
52
+ IssueSchemaCk.create(id: i, type: 'first', date: Date.yesterday.to_time, value: 1.0)
53
+ IssueSchemaCk.create(id: i, type: 'first', date: Date.today.to_time, value: 2.0)
54
+ end
55
+ ids_to_find = ids.sample(10)
56
+ assert_equal ids_to_find.size * 2, IssueSchemaCk.find(ids_to_find).size
57
+ assert_equal ids_to_find, IssueSchemaCk.find(ids_to_find).map(&:id).uniq
58
+ IssueSchemaCk.delete_all
59
+ end
60
+
29
61
  test 'find_by_id' do
30
62
  Issue.create.tap do |issue|
31
63
  assert_equal issue, Issue.find_by_id(issue.id)
@@ -64,9 +96,7 @@ class CassandraObject::FinderMethodsTest < CassandraObject::TestCase
64
96
  IssueDynamic.delete(['1', '2'])
65
97
  end
66
98
 
67
-
68
99
  test 'find all in batches dynamic paged' do
69
-
70
100
  issues = []
71
101
  100.times.each do |i|
72
102
  issues << IssueDynamic.create(key: i, title: 'tit', dynamic_field1: 'one', dynamic_field2: 'two')
@@ -135,5 +165,4 @@ class CassandraObject::FinderMethodsTest < CassandraObject::TestCase
135
165
  # first_issue = IssueDynamic.create(key: '1', title: 'tit', dynamic_field1: 'one', dynamic_field2: 'two')
136
166
  # f = IssueDynamic.first
137
167
  # end
138
-
139
168
  end
@@ -37,5 +37,4 @@ class CassandraObject::Scope::QueryMethodsTest < CassandraObject::TestCase
37
37
  foo_issue_columns = Issue.columns.first
38
38
  assert_equal ['created_at', 'description', 'title', 'updated_at'], foo_issue_columns[foo_issue_columns.keys.first]
39
39
  end
40
-
41
40
  end
@@ -21,7 +21,30 @@ class CassandraObject::TimestampsTest < CassandraObject::TestCase
21
21
  test 'created_at sets only if nil' do
22
22
  time = 5.days.ago
23
23
  issue = Issue.create created_at: time
24
-
25
24
  assert_equal time, issue.created_at
26
25
  end
26
+
27
+ test 'set updated_at to now when not passed as an attribute' do
28
+ udate = 1.year.ago
29
+ issue = Issue.create(description: 'foo', updated_at: udate)
30
+ assert_equal udate, issue.updated_at
31
+ issue.update_attributes(description: 'test')
32
+ assert_not_equal udate, issue.updated_at
33
+ end
34
+
35
+ test 'set updated_at to passed value' do
36
+ issue = Issue.create(description: 'foo')
37
+ updated_at = issue.updated_at
38
+ new_updated_at = updated_at + 5.days
39
+ issue.update_attributes(description: 'bar', store_updated_at: new_updated_at)
40
+ assert_equal new_updated_at, issue.updated_at
41
+ end
42
+
43
+ test 'set updated_at to passed value even if is equal to the stored value' do
44
+ udate = 1.year.ago
45
+ issue = Issue.create(description: 'foo', updated_at: udate)
46
+ assert_equal udate, issue.updated_at
47
+ issue.update_attributes(description: 'bar', store_updated_at: issue.updated_at)
48
+ assert_equal udate, issue.updated_at
49
+ end
27
50
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: extendi-cassandra_object
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.17
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Duccio Giovannelli
@@ -9,22 +9,28 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2018-11-27 00:00:00.000000000 Z
12
+ date: 2020-06-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activemodel
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  requirements:
18
- - - "<="
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: 4.2.0
21
+ - - "<"
19
22
  - !ruby/object:Gem::Version
20
- version: '5.2'
23
+ version: 7.0.0
21
24
  type: :runtime
22
25
  prerelease: false
23
26
  version_requirements: !ruby/object:Gem::Requirement
24
27
  requirements:
25
- - - "<="
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ version: 4.2.0
31
+ - - "<"
26
32
  - !ruby/object:Gem::Version
27
- version: '5.2'
33
+ version: 7.0.0
28
34
  - !ruby/object:Gem::Dependency
29
35
  name: cassandra-driver
30
36
  requirement: !ruby/object:Gem::Requirement
@@ -198,8 +204,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
198
204
  - !ruby/object:Gem::Version
199
205
  version: 1.3.5
200
206
  requirements: []
201
- rubyforge_project:
202
- rubygems_version: 2.7.6
207
+ rubygems_version: 3.1.2
203
208
  signing_key:
204
209
  specification_version: 4
205
210
  summary: Cassandra ActiveModel