datastax_rails 2.0.18 → 2.1.23

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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/config/solrconfig.xml.erb +7 -7
  3. data/lib/datastax_rails/associations/collection_association.rb +4 -4
  4. data/lib/datastax_rails/associations/has_many_association.rb +1 -1
  5. data/lib/datastax_rails/attribute_methods/dirty.rb +6 -0
  6. data/lib/datastax_rails/attribute_methods/primary_key.rb +9 -1
  7. data/lib/datastax_rails/attribute_methods/read.rb +2 -2
  8. data/lib/datastax_rails/attribute_methods/typecasting.rb +4 -7
  9. data/lib/datastax_rails/attribute_methods.rb +25 -0
  10. data/lib/datastax_rails/autosave_association.rb +3 -3
  11. data/lib/datastax_rails/base.rb +33 -11
  12. data/lib/datastax_rails/column.rb +25 -15
  13. data/lib/datastax_rails/connection.rb +1 -1
  14. data/lib/datastax_rails/cql/base.rb +31 -19
  15. data/lib/datastax_rails/cql/select.rb +2 -2
  16. data/lib/datastax_rails/dynamic_model.rb +1 -1
  17. data/lib/datastax_rails/errors.rb +3 -0
  18. data/lib/datastax_rails/instrumentation/controller_runtime.rb +52 -0
  19. data/lib/datastax_rails/instrumentation/log_subscriber.rb +92 -0
  20. data/lib/datastax_rails/instrumentation.rb +4 -0
  21. data/lib/datastax_rails/persistence.rb +49 -17
  22. data/lib/datastax_rails/railtie.rb +7 -0
  23. data/lib/datastax_rails/reflection.rb +16 -12
  24. data/lib/datastax_rails/relation/batches.rb +1 -1
  25. data/lib/datastax_rails/relation/finder_methods.rb +5 -5
  26. data/lib/datastax_rails/relation/modification_methods.rb +11 -26
  27. data/lib/datastax_rails/relation/search_methods.rb +10 -11
  28. data/lib/datastax_rails/relation.rb +81 -50
  29. data/lib/datastax_rails/scoping.rb +1 -1
  30. data/lib/datastax_rails/serialization.rb +10 -6
  31. data/lib/datastax_rails/serializers/xml_serializer.rb +3 -1
  32. data/lib/datastax_rails/tasks/ds.rake +25 -26
  33. data/lib/datastax_rails/timestamps.rb +1 -1
  34. data/lib/datastax_rails/types/dynamic_set.rb +4 -0
  35. data/lib/datastax_rails/version.rb +1 -1
  36. data/lib/datastax_rails/wide_storage_model.rb +3 -1
  37. data/lib/datastax_rails.rb +1 -0
  38. data/spec/datastax_rails/attribute_methods/read_spec.rb +33 -0
  39. data/spec/datastax_rails/attribute_methods/typecasting_spec.rb +25 -0
  40. data/spec/datastax_rails/base_spec.rb +1 -1
  41. data/spec/datastax_rails/callbacks_spec.rb +6 -0
  42. data/spec/datastax_rails/column_spec.rb +20 -0
  43. data/spec/datastax_rails/cql/base_spec.rb +1 -1
  44. data/spec/datastax_rails/cql/select_spec.rb +7 -1
  45. data/spec/datastax_rails/persistence_spec.rb +22 -3
  46. data/spec/datastax_rails/relation/finder_methods_spec.rb +13 -0
  47. data/spec/datastax_rails/relation/search_methods_spec.rb +37 -12
  48. data/spec/datastax_rails/serialization_spec.rb +17 -0
  49. data/spec/dummy/log/development.log +0 -24195
  50. data/spec/dummy/log/test.log +20 -15610
  51. data/spec/factories/hobbies.rb +1 -0
  52. data/spec/factories/jobs.rb +7 -0
  53. data/spec/factories/people.rb +1 -0
  54. data/spec/factories/person_roles.rb +7 -0
  55. data/spec/factories/roles.rb +6 -0
  56. data/spec/factories/uuid_keys.rb +6 -0
  57. data/spec/feature/associations_spec.rb +26 -0
  58. data/spec/support/models.rb +82 -0
  59. metadata +19 -4
  60. data/spec/dummy/log/production.log +0 -2
@@ -3,7 +3,7 @@ require 'pp' if ENV['DEBUG_SOLR'] == 'true'
3
3
  # TODO: Move functionality into modules
4
4
  module DatastaxRails
5
5
  # = DatastaxRails Relation
6
- class Relation # rubocop:disable Style/ClassLength
6
+ class Relation # rubocop:disable Metrics/ClassLength
7
7
  MULTI_VALUE_METHODS = %i(order where where_not fulltext greater_than less_than select stats field_facet
8
8
  range_facet slow_order)
9
9
  SINGLE_VALUE_METHODS = %i(page per_page reverse_order query_parser consistency ttl use_solr escape group
@@ -263,7 +263,8 @@ module DatastaxRails
263
263
  return :solr unless page_value == 1
264
264
  @where_values.each do |wv|
265
265
  wv.each do |k, _v|
266
- next if klass.column_for_attribute(k).options[:cql_index]
266
+ col = klass.column_for_attribute(k)
267
+ next if col && (col.options[:cql_index] || col.primary)
267
268
  return :solr
268
269
  end
269
270
  end
@@ -290,7 +291,18 @@ module DatastaxRails
290
291
  cql = @cql.select(['count(*)'])
291
292
  cql.using(@consistency_value) if @consistency_value
292
293
  @where_values.each do |wv|
293
- cql.conditions(wv)
294
+ wv.each do |k, v|
295
+ attr = (k.to_s == 'id' ? @klass.primary_key : k)
296
+ col = klass.column_for_attribute(attr)
297
+ if col.primary
298
+ v.compact! if v.respond_to?(:compact)
299
+ return 0 if v.blank?
300
+ end
301
+ values = Array(v).map do |val|
302
+ col.type_cast_for_cql3(val)
303
+ end
304
+ cql.conditions(attr => values)
305
+ end
294
306
  end
295
307
  cql.allow_filtering if @allow_filtering_value
296
308
  cql.execute.first['count']
@@ -305,7 +317,18 @@ module DatastaxRails
305
317
  cql = @cql.select((select_columns + [@klass.primary_key]).uniq)
306
318
  cql.using(@consistency_value) if @consistency_value
307
319
  @where_values.each do |wv|
308
- cql.conditions(Hash[wv.map { |k, v| [(k.to_s == 'id' ? @klass.primary_key : k), v] }])
320
+ wv.each do |k, v|
321
+ attr = (k.to_s == 'id' ? @klass.primary_key : k)
322
+ col = klass.column_for_attribute(attr)
323
+ if col.primary
324
+ v.compact! if v.respond_to?(:compact!)
325
+ return [] if v.blank?
326
+ end
327
+ values = Array(v).map do |val|
328
+ col.type_cast_for_cql3(val)
329
+ end
330
+ cql.conditions(attr => values)
331
+ end
309
332
  end
310
333
  @greater_than_values.each do |gtv|
311
334
  gtv.each do |k, v|
@@ -322,9 +345,7 @@ module DatastaxRails
322
345
  end
323
346
  rescue ::Cql::CqlError => e # TODO: Break out the various exception types
324
347
  # If we get an exception about an empty key, ignore it. We'll return an empty set.
325
- if e.message =~ /Key may not be empty/
326
- # No-Op
327
- else
348
+ unless e.message =~ /Key may not be empty/
328
349
  raise
329
350
  end
330
351
  end
@@ -394,27 +415,35 @@ module DatastaxRails
394
415
  orders = []
395
416
  @where_values.each do |wv|
396
417
  wv.each do |k, v|
397
- # If v is blank, check that there is no value for the field in the document
398
- filter_queries << (v.blank? ? "-#{k}:#{full_solr_range(k)}" : "#{k}:(#{v})")
418
+ # If v is blank but not false, check that there is no value for the field in the document
419
+ if v.blank? && v != false
420
+ filter_queries << "-#{k}:#{full_solr_range(k)}"
421
+ else
422
+ filter_queries << "#{k}:(#{solr_format(k, v)})"
423
+ end
399
424
  end
400
425
  end
401
426
 
402
427
  @where_not_values.each do |wnv|
403
428
  wnv.each do |k, v|
404
- # If v is blank, check for any value for the field in document
405
- filter_queries << (v.blank? ? "#{k}:#{full_solr_range(k)}" : "-#{k}:(#{v})")
429
+ # If v is blank but not false, check for any value in the field in the document
430
+ if v.blank? && v != false
431
+ filter_queries << "#{k}:#{full_solr_range(k)}"
432
+ else
433
+ filter_queries << "-#{k}:(#{solr_format(k, v)})"
434
+ end
406
435
  end
407
436
  end
408
437
 
409
438
  @greater_than_values.each do |gtv|
410
439
  gtv.each do |k, v|
411
- filter_queries << "#{k}:[#{v} TO *]"
440
+ filter_queries << "#{k}:[#{solr_format(k, v)} TO *]"
412
441
  end
413
442
  end
414
443
 
415
444
  @less_than_values.each do |ltv|
416
445
  ltv.each do |k, v|
417
- filter_queries << "#{k}:[* TO #{v}]"
446
+ filter_queries << "#{k}:[* TO #{solr_format(k, v)}]"
418
447
  end
419
448
  end
420
449
 
@@ -497,43 +526,47 @@ module DatastaxRails
497
526
  params['stats.facet'] = @group_value
498
527
  end
499
528
  solr_response = nil
500
- if @group_value
501
- results = DatastaxRails::GroupedCollection.new
502
- params[:group] = 'true'
503
- params[:rows] = 10_000
504
- params['group.field'] = @group_value
505
- params['group.limit'] = @per_page_value
506
- params['group.offset'] = (@page_value - 1) * @per_page_value
507
- params['group.ngroups'] = 'false' # must be false due to issues with solr sharding
508
- solr_response = rsolr.post('select', data: params)
509
- response = solr_response['grouped'][@group_value.to_s]
510
- results.total_groups = response['groups'].size
511
- results.total_for_all = response['matches'].to_i
512
- results.total_entries = 0
513
- response['groups'].each do |group|
514
- results[group['groupValue']] = parse_docs(group['doclist'], select_columns)
515
- if results[group['groupValue']].total_entries > results.total_entries
516
- results.total_entries = results[group['groupValue']].total_entries
529
+ ActiveSupport::Notifications.instrument(
530
+ 'solr.datastax_rails',
531
+ name: 'Search',
532
+ klass: @klass.name,
533
+ search: params) do
534
+ if @group_value
535
+ results = DatastaxRails::GroupedCollection.new
536
+ params[:group] = 'true'
537
+ params[:rows] = 10_000
538
+ params['group.field'] = @group_value
539
+ params['group.limit'] = @per_page_value
540
+ params['group.offset'] = (@page_value - 1) * @per_page_value
541
+ params['group.ngroups'] = 'false' # must be false due to issues with solr sharding
542
+ solr_response = rsolr.post('select', data: params)
543
+ response = solr_response['grouped'][@group_value.to_s]
544
+ results.total_groups = response['groups'].size
545
+ results.total_for_all = response['matches'].to_i
546
+ results.total_entries = 0
547
+ response['groups'].each do |group|
548
+ results[group['groupValue']] = parse_docs(group['doclist'], select_columns)
549
+ if results[group['groupValue']].total_entries > results.total_entries
550
+ results.total_entries = results[group['groupValue']].total_entries
551
+ end
517
552
  end
553
+ else
554
+ solr_response = rsolr.paginate(@page_value, @per_page_value, 'select', data: params, method: :post)
555
+ response = solr_response['response']
556
+ results = parse_docs(response, select_columns)
557
+ results.highlights = solr_response['highlighting']
518
558
  end
519
- else
520
- solr_response = rsolr.paginate(@page_value, @per_page_value, 'select', data: params, method: :post)
521
- response = solr_response['response']
522
- pp solr_response if ENV['DEBUG_SOLR'] == 'true'
523
- results = parse_docs(response, select_columns)
524
- results.highlights = solr_response['highlighting']
525
- end
526
- if solr_response['stats']
527
- @stats = solr_response['stats']['stats_fields'].with_indifferent_access
528
- end
529
- # Apply Facets if they exist
530
- if solr_response['facet_counts']
531
- results.facets = {}
532
- results.facets = results.facets.merge(solr_response['facet_counts']['facet_fields'].to_h)
533
- results.facets = results.facets.merge(solr_response['facet_counts']['facet_ranges'].to_h)
559
+ if solr_response['stats']
560
+ @stats = solr_response['stats']['stats_fields'].with_indifferent_access
561
+ end
562
+ # Apply Facets if they exist
563
+ if solr_response['facet_counts']
564
+ results.facets = {}
565
+ results.facets = results.facets.merge(solr_response['facet_counts']['facet_fields'].to_h)
566
+ results.facets = results.facets.merge(solr_response['facet_counts']['facet_ranges'].to_h)
567
+ end
568
+ results
534
569
  end
535
- pp params if ENV['DEBUG_SOLR'] == 'true'
536
- results
537
570
  end
538
571
 
539
572
  # Parse out a set of documents and return the results
@@ -610,9 +643,7 @@ module DatastaxRails
610
643
  value.split(/\bAND\b/).map do |a|
611
644
  a.split(/\bOR\b/).map do |o|
612
645
  o.split(/\bNOT\b/).map do |n|
613
- n.split(/\bTO\b/).map do |t|
614
- t.downcase
615
- end.join('TO')
646
+ n.split(/\bTO\b/).map(&:downcase).join('TO')
616
647
  end.join('NOT')
617
648
  end.join('OR')
618
649
  end.join('AND').gsub(SOLR_DATE_REGEX) { Regexp.last_match[1].upcase }
@@ -57,7 +57,7 @@ module DatastaxRails
57
57
  # end
58
58
  #
59
59
  # *Note*: the +:find+ scope also has effect on update and deletion methods, like +update_all+ and +delete_all+.
60
- def with_scope(scope = {}, action = :merge, &_block) # rubocop:disable Style/CyclomaticComplexity
60
+ def with_scope(scope = {}, action = :merge, &_block) # rubocop:disable Metrics/CyclomaticComplexity
61
61
  # If another DatastaxRails class has been passed in, get its current scope
62
62
  scope = scope.current_scope if !scope.is_a?(Relation) && scope.respond_to?(:current_scope)
63
63
  previous_scope = current_scope
@@ -7,7 +7,7 @@ module DatastaxRails
7
7
  def serializable_hash(options = nil)
8
8
  options = options.try(:clone) || {}
9
9
 
10
- options[:except] = Array.wrap(options[:except]).map { |n| n.to_s }
10
+ options[:except] = Array.wrap(options[:except]).map(&:to_s)
11
11
 
12
12
  hash = super(options)
13
13
 
@@ -19,16 +19,20 @@ module DatastaxRails
19
19
  end
20
20
  end
21
21
 
22
- hash
23
- end
22
+ serializable_convert_uuids(hash)
24
23
 
25
- def as_json(options = {})
26
- json = super(options)
27
- json.each { |k, v| json[k] = v.to_s if v.is_a?(::Cql::Uuid) }
24
+ hash
28
25
  end
29
26
 
30
27
  private
31
28
 
29
+ def serializable_convert_uuids(hash)
30
+ hash.each do |k, v|
31
+ serializable_convert_uuids(v) if v.is_a?(Hash)
32
+ hash[k] = v.to_s if v.is_a?(::Cql::Uuid)
33
+ end
34
+ end
35
+
32
36
  # Add associations specified via the <tt>:include</tt> option.
33
37
  #
34
38
  # Expects a block that takes as arguments:
@@ -241,7 +241,9 @@ module DatastaxRails
241
241
  end
242
242
 
243
243
  { text: :string,
244
- time: :datetime }[type] || type
244
+ time: :datetime,
245
+ set: :array,
246
+ list: :array }[type] || type
245
247
  end
246
248
  protected :compute_type
247
249
  end
@@ -1,6 +1,6 @@
1
1
  namespace :ds do
2
- task :configure => :environment do
3
- datastax_config = ERB.new(Rails.root.join('config',"datastax.yml").read).result(binding)
2
+ task configure: :environment do
3
+ datastax_config = ERB.new(Rails.root.join('config', 'datastax.yml').read).result(binding)
4
4
  @configs = YAML.load(datastax_config)
5
5
  @config = @configs[Rails.env || 'development']
6
6
  @migrator = DatastaxRails::Schema::Migrator.new(@config['keyspace'])
@@ -8,72 +8,71 @@ namespace :ds do
8
8
 
9
9
  desc 'Create the keyspace in config/datastax.yml for the current environment'
10
10
  task :create do
11
- datastax_config = ERB.new(Rails.root.join('config',"datastax.yml").read).result(binding)
11
+ datastax_config = ERB.new(Rails.root.join('config', 'datastax.yml').read).result(binding)
12
12
  @configs = YAML.load(datastax_config)
13
13
  @config = @configs[Rails.env || 'development']
14
- DatastaxRails::Base.establish_connection(@config.with_indifferent_access.merge(:keyspace => 'system'))
14
+ DatastaxRails::Base.establish_connection(@config.with_indifferent_access.merge(keyspace: 'system'))
15
15
  DatastaxRails::Schema::Migrator.new('system').create_keyspace(@config['keyspace'], @config)
16
16
  end
17
17
 
18
18
  desc 'Drop keyspace in config/datastax.yml for the current environment'
19
- task :drop => :configure do
19
+ task drop: :configure do
20
20
  @migrator.drop_keyspace
21
21
  end
22
22
 
23
23
  desc 'Migrate keyspace to latest version -- pass in model name to force an upload of just that one (all force-uploads everything).'
24
- task :migrate, [:force_cf] => :configure do |t, args|
24
+ task :migrate, [:force_cf] => :configure do |_t, args|
25
25
  if args[:force_cf].blank?
26
26
  @migrator.migrate_all
27
27
  else
28
28
  args[:force_cf] == 'all' ? @migrator.migrate_all(true) : @migrator.migrate_one(args[:force_cf].constantize, true)
29
29
  end
30
30
  end
31
-
31
+
32
32
  desc 'Alias for ds:migrate to maintain backwards-compatibility'
33
33
  task :schema, [:force_cf] => :migrate
34
-
34
+
35
35
  desc 'Rebuild SOLR Index -- pass in a model name (all rebuilds everything)'
36
- task :reindex, [:model] => :configure do |t, args|
36
+ task :reindex, [:model] => :configure do |_t, args|
37
37
  if args[:model].blank?
38
38
  puts "\nUSAGE: rake ds:reindex[Model]"
39
39
  else
40
40
  @migrator.reindex_solr(args[:model].constantize)
41
41
  end
42
42
  end
43
-
43
+
44
44
  desc 'Create SOLR Core (Normally not needed) -- pass in a model name (all creates everything)'
45
- task :create_core, [:model] => :configure do |t, args|
45
+ task :create_core, [:model] => :configure do |_t, args|
46
46
  if args[:model].blank?
47
47
  puts "\nUSAGE: rake ds:create_core[Model]"
48
48
  else
49
49
  @migrator.create_solr_core(args[:model].constantize)
50
50
  end
51
51
  end
52
-
52
+
53
53
  desc 'Load the seed data from ks/seeds.rb'
54
- task :seed => :environment do
55
- seed_file = Rails.root.join("ks","seeds.rb")
54
+ task seed: :environment do
55
+ seed_file = Rails.root.join('ks', 'seeds.rb')
56
56
  load(seed_file) if seed_file.exist?
57
57
  end
58
-
59
- if(defined?(ParallelTests))
58
+
59
+ if defined?(ParallelTests)
60
60
  namespace :parallel do
61
- desc "create test keyspaces via ds:create --> ds:parallel:create[num_cpus]"
62
- task :create, :count do |t,args|
61
+ desc 'create test keyspaces via ds:create --> ds:parallel:create[num_cpus]'
62
+ task :create, :count do |_t, args|
63
63
  ParallelTests::Tasks.run_in_parallel("rake ds:create RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
64
64
  end
65
-
66
- desc "drop test keyspaces via ds:drop --> ds:parallel:drop[num_cpus]"
67
- task :drop, :count do |t,args|
65
+
66
+ desc 'drop test keyspaces via ds:drop --> ds:parallel:drop[num_cpus]'
67
+ task :drop, :count do |_t, args|
68
68
  ParallelTests::Tasks.run_in_parallel("rake ds:drop RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
69
69
  end
70
-
71
- desc "update test keyspaces via ds:migrate --> ds:parallel:migrate[num_cpus]"
72
- task :migrate, :count do |t,args|
73
- args = args.to_hash.merge(:non_parallel => true)
70
+
71
+ desc 'update test keyspaces via ds:migrate --> ds:parallel:migrate[num_cpus]'
72
+ task :migrate, :count do |_t, args|
73
+ args = args.to_hash.merge(non_parallel: true)
74
74
  ParallelTests::Tasks.run_in_parallel("rake ds:migrate RAILS_ENV=#{ParallelTests::Tasks.rails_env}", args)
75
75
  end
76
76
  end
77
77
  end
78
78
  end
79
-
@@ -79,7 +79,7 @@ module DatastaxRails
79
79
 
80
80
  def max_updated_column_timestamp
81
81
  if (timestamps = timestamp_attributes_for_update.map { |attr| self[attr] }.compact).present?
82
- timestamps.map { |ts| ts.to_time }.max
82
+ timestamps.map(&:to_time).max
83
83
  end
84
84
  end
85
85
 
@@ -5,6 +5,10 @@ module DatastaxRails
5
5
  # built-in change tracking.
6
6
  class DynamicSet < Set
7
7
  include DirtyCollection
8
+
9
+ delegate :join, :[], :to_xml, to: :to_a
10
+
11
+ alias_method :to_ary, :to_a
8
12
  end
9
13
  end
10
14
  end
@@ -1,4 +1,4 @@
1
1
  # rubocop:disable Style/Documentation
2
2
  module DatastaxRails
3
- VERSION = '2.0.18'
3
+ VERSION = '2.1.23'
4
4
  end
@@ -27,7 +27,9 @@ module DatastaxRails
27
27
 
28
28
  # Returns a primary key hash for updates that includes the cluster key
29
29
  def id_for_update
30
- { self.class.primary_key.to_s => id, self.class.cluster_by.to_s => read_attribute(self.class.cluster_by.to_s) }
30
+ cc = self.class.column_for_attribute(self.class.cluster_by)
31
+ { self.class.primary_key.to_s => __id,
32
+ self.class.cluster_by.to_s => cc.type_cast_for_cql3(read_attribute(self.class.cluster_by.to_s)) }
31
33
  end
32
34
  end
33
35
  end
@@ -99,6 +99,7 @@ end
99
99
 
100
100
  require 'datastax_rails/railtie' if defined?(Rails)
101
101
  require 'datastax_rails/errors'
102
+ require 'datastax_rails/instrumentation' if defined?(Rails)
102
103
  require 'cql-rb_extensions'
103
104
 
104
105
  ActiveSupport.run_load_hooks(:datastax_rails, DatastaxRails::Base)
@@ -0,0 +1,33 @@
1
+ require 'spec_helper'
2
+
3
+ describe DatastaxRails::Base do
4
+ context 'attribute methods' do
5
+ context 'read' do
6
+ context 'overrides' do
7
+ subject { CollectionOverride.new }
8
+
9
+ context 'lists' do
10
+ before do
11
+ subject.list1 = ['foo']
12
+ subject.list2 = ['bar']
13
+ end
14
+
15
+ it 'returns the correct values before a save' do
16
+ expect(subject.read_attribute(:list1)).to eq(['foo'])
17
+ expect(subject.read_attribute(:list2)).to eq(['bar'])
18
+ expect(subject.list1).to eq(['FOO'])
19
+ expect(subject.list2).to eq(['BAR'])
20
+ end
21
+
22
+ it 'returns the correct values after a save' do
23
+ subject.save
24
+ expect(subject.read_attribute(:list1)).to eq(['FOO'])
25
+ expect(subject.read_attribute(:list2)).to eq(['bar'])
26
+ expect(subject.list1).to eq(['FOO'])
27
+ expect(subject.list2).to eq(['BAR'])
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -9,10 +9,21 @@ describe DatastaxRails::Base do
9
9
  its(:bool) { is_expected.to be(true) }
10
10
  its(:bool2) { is_expected.to be(false) }
11
11
  its(:bool3) { is_expected.to be_nil }
12
+ its(:version) { is_expected.to be(1) }
13
+ its(:complexity) { is_expected.to be(0.0) }
14
+ its(:previous_id) { is_expected.to eq('00000000-0000-0000-0000-000000000000') }
15
+ its(:epoch) { is_expected.to eq(Date.parse('1970-01-01')) }
16
+ its(:epoch2) { is_expected.to eq(Time.parse('1970-01-01 00:00:00')) }
17
+
12
18
  its(:changed_attributes) { is_expected.to include('str') }
13
19
  its(:changed_attributes) { is_expected.to include('bool') }
14
20
  its(:changed_attributes) { is_expected.to include('bool2') }
15
21
  its(:changed_attributes) { is_expected.not_to include('bool3') }
22
+ its(:changed_attributes) { is_expected.to include('version') }
23
+ its(:changed_attributes) { is_expected.to include('complexity') }
24
+ its(:changed_attributes) { is_expected.to include('previous_id') }
25
+ its(:changed_attributes) { is_expected.to include('epoch') }
26
+ its(:changed_attributes) { is_expected.to include('epoch2') }
16
27
 
17
28
  context 'setting the attribute to the default' do
18
29
  before { subject.bool = true }
@@ -20,6 +31,20 @@ describe DatastaxRails::Base do
20
31
  its(:bool) { is_expected.to be(true) }
21
32
  its(:changed_attributes) { is_expected.to include('bool') }
22
33
  end
34
+
35
+ context 'collections' do
36
+ context 'when not specified' do
37
+ its(:m) { is_expected.to eq({}) }
38
+ its(:s) { is_expected.to eq(Set.new) }
39
+ its(:l) { is_expected.to eq([]) }
40
+ end
41
+
42
+ context 'when specified' do
43
+ its(:m2) { is_expected.to eq('m2test' => 'string') }
44
+ its(:s2) { is_expected.to eq(Set.new(['unique string'])) }
45
+ its(:l2) { is_expected.to eq(['ordered string']) }
46
+ end
47
+ end
23
48
  end
24
49
  end
25
50
  end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe DatastaxRails::Base do
4
4
  it 'should raise RecordNotFound when finding a bogus ID' do
5
- expect { Person.find('xyzzy') }.to raise_exception(DatastaxRails::RecordNotFound)
5
+ expect { Person.find(::Cql::TimeUuid::Generator.new.next) }.to raise_exception(DatastaxRails::RecordNotFound)
6
6
  end
7
7
 
8
8
  describe 'equality' do
@@ -33,6 +33,12 @@ describe DatastaxRails::Base do
33
33
  expect(subject).to receive(callback + '_callback')
34
34
  subject.destroy
35
35
  end
36
+
37
+ it 'provides a method to run without callbacks' do
38
+ subject.save
39
+ expect(subject).not_to receive(callback + '_callback')
40
+ subject.destroy_without_callbacks
41
+ end
36
42
  end
37
43
 
38
44
  it 'runs after_find' do
@@ -212,6 +212,10 @@ describe DatastaxRails::Column do
212
212
  expect(c.type_cast({ 'field_key' => '7' }, record)).to be_a(DatastaxRails::Types::DynamicMap)
213
213
  end
214
214
 
215
+ it 'casts nil to an empty hash' do
216
+ expect(c.type_cast(nil, record)).to eq({})
217
+ end
218
+
215
219
  describe 'to cql' do
216
220
  it 'casts map values to the appropriate type' do
217
221
  date = Date.parse('1980-10-19')
@@ -231,6 +235,14 @@ describe DatastaxRails::Column do
231
235
  it 'wraps list values in a DynamicList' do
232
236
  expect(c.type_cast([1, '2', 6.minutes], record)).to be_a(DatastaxRails::Types::DynamicList)
233
237
  end
238
+
239
+ it 'casts nil to an empty array' do
240
+ expect(c.type_cast(nil, record)).to eq([])
241
+ end
242
+
243
+ it 'casts a scalar value to a list' do
244
+ expect(c.type_cast(1, record)).to eq([1])
245
+ end
234
246
  end
235
247
 
236
248
  describe 'set' do
@@ -243,6 +255,14 @@ describe DatastaxRails::Column do
243
255
  it 'wraps list values in a DynamicSet' do
244
256
  expect(c.type_cast([1, '2', 6.minutes, 2], record)).to be_a(DatastaxRails::Types::DynamicSet)
245
257
  end
258
+
259
+ it 'casts nil to an empty set' do
260
+ expect(c.type_cast(nil, record)).to eq(Set.new)
261
+ end
262
+
263
+ it 'casts a scalar value to a set' do
264
+ expect(c.type_cast(1, record)).to eq(Set.new([1]))
265
+ end
246
266
  end
247
267
  end
248
268
  end
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe DatastaxRails::Cql::Base do
4
4
  it 'caches prepared statements' do
5
- expect(DatastaxRails::Base.connection).to receive(:prepare).once.and_return(double('statement', execute: true))
5
+ expect(DatastaxRails::Base.connection).to receive(:prepare).once.and_return(double('statement', execute: [true]))
6
6
  cql = DatastaxRails::Cql::ColumnFamily.new(Person)
7
7
  cql.select(['*']).conditions(name: 'John').execute
8
8
  cql.select(['*']).conditions(name: 'John').execute
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe DatastaxRails::Cql::Select do
4
- let(:model_class) { double('Model Class', column_family: 'users', default_consistency: DatastaxRails::Cql::Consistency::QUORUM) }
4
+ let(:model_class) { double('Model Class', column_family: 'users', primary_key: 'id', default_consistency: DatastaxRails::Cql::Consistency::QUORUM) }
5
5
 
6
6
  it 'should generate valid CQL' do
7
7
  cql = DatastaxRails::Cql::Select.new(model_class, ['*'])
@@ -9,5 +9,11 @@ describe DatastaxRails::Cql::Select do
9
9
  expect(cql.to_cql).to eq("SELECT * FROM users WHERE \"key\" = ? LIMIT 1 ")
10
10
  end
11
11
 
12
+ it 'ignores multiple values for non-primary-key' do
13
+ cql = DatastaxRails::Cql::Select.new(model_class, ['*'])
14
+ cql.using(DatastaxRails::Cql::Consistency::QUORUM).conditions(key: %w(12345 67890)).limit(1)
15
+ expect(cql.to_cql).to eq("SELECT * FROM users WHERE \"key\" = ? LIMIT 1 ")
16
+ end
17
+
12
18
  it_has_behavior 'default_consistency'
13
19
  end
@@ -72,14 +72,24 @@ describe 'DatastaxRails::Base' do
72
72
  end
73
73
 
74
74
  it 'should successfully remove columns that are set to nil' do
75
- skip
76
75
  Person.create!(name: 'Steven', birthdate: Date.today)
77
76
  Person.commit_solr
78
- p = Person.find_by_name('Steven')
77
+ p = Person.find_by(name: 'Steven')
79
78
  p.birthdate = nil
80
79
  p.save
81
80
  Person.commit_solr
82
- Person.find by_name('Steven').birthdate.should be_nil
81
+ expect(Person.find_by(name: 'Steven').birthdate).to be_nil
82
+ end
83
+
84
+ it 'keeps existing attributes from being deleted' do
85
+ p = Person.create!(name: 'Jacob', birthdate: Date.today)
86
+ p.nickname = 'Jake'
87
+ p.save
88
+ Person.commit_solr
89
+ p2 = Person.find_by(name: 'Jacob')
90
+ expect(p2.name).to eql('Jacob')
91
+ expect(p2.nickname).to eql('Jake')
92
+ expect(p2.birthdate).to eql(Date.today)
83
93
  end
84
94
  end
85
95
  end
@@ -106,5 +116,14 @@ describe 'DatastaxRails::Base' do
106
116
  expect(CarPayload.find('limo').payload).to eq(smallfile)
107
117
  end
108
118
  end
119
+
120
+ describe '#write_attribute' do
121
+ let(:person) { create(:person) }
122
+
123
+ it 'writes an attribute directly to the database' do
124
+ Person.write_attribute(person, birthdate: Date.today)
125
+ expect(Person.find(person.id).birthdate).to eq(Date.today)
126
+ end
127
+ end
109
128
  end
110
129
  end
@@ -116,5 +116,18 @@ describe DatastaxRails::Relation do
116
116
  Boat.commit_solr
117
117
  expect(Boat.find_by(name: 'Dumb: Name')).not_to be_nil
118
118
  end
119
+
120
+ it 'does not interpret wildcards' do
121
+ Boat.create(name: 'Water Lily')
122
+ Boat.commit_solr
123
+ expect(Boat.find_by(name: 'Wat*')).to be_nil
124
+ end
125
+
126
+ it 'finds a record by an attribute with a wildcard in it' do
127
+ Boat.create(name: 'L*ly')
128
+ Boat.commit_solr
129
+ Boat.commit_solr
130
+ expect(Boat.find_by(name: 'L*ly')).not_to be_nil
131
+ end
119
132
  end
120
133
  end