cequel 0.5.6 → 1.0.0.pre.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.
Files changed (108) hide show
  1. checksums.yaml +7 -0
  2. data/lib/cequel.rb +5 -8
  3. data/lib/cequel/errors.rb +1 -0
  4. data/lib/cequel/metal.rb +17 -0
  5. data/lib/cequel/metal/batch.rb +62 -0
  6. data/lib/cequel/metal/cql_row_specification.rb +26 -0
  7. data/lib/cequel/metal/data_set.rb +461 -0
  8. data/lib/cequel/metal/deleter.rb +47 -0
  9. data/lib/cequel/metal/incrementer.rb +35 -0
  10. data/lib/cequel/metal/inserter.rb +53 -0
  11. data/lib/cequel/metal/keyspace.rb +213 -0
  12. data/lib/cequel/metal/row.rb +48 -0
  13. data/lib/cequel/metal/row_specification.rb +37 -0
  14. data/lib/cequel/metal/statement.rb +30 -0
  15. data/lib/cequel/metal/updater.rb +65 -0
  16. data/lib/cequel/metal/writer.rb +73 -0
  17. data/lib/cequel/model.rb +12 -84
  18. data/lib/cequel/model/association_collection.rb +23 -0
  19. data/lib/cequel/model/associations.rb +84 -80
  20. data/lib/cequel/model/base.rb +74 -0
  21. data/lib/cequel/model/belongs_to_association.rb +31 -0
  22. data/lib/cequel/model/callbacks.rb +14 -10
  23. data/lib/cequel/model/collection.rb +255 -0
  24. data/lib/cequel/model/errors.rb +6 -6
  25. data/lib/cequel/model/has_many_association.rb +26 -0
  26. data/lib/cequel/model/mass_assignment.rb +31 -0
  27. data/lib/cequel/model/persistence.rb +119 -115
  28. data/lib/cequel/model/properties.rb +89 -87
  29. data/lib/cequel/model/railtie.rb +21 -14
  30. data/lib/cequel/model/record_set.rb +285 -0
  31. data/lib/cequel/model/schema.rb +33 -0
  32. data/lib/cequel/model/scoped.rb +5 -48
  33. data/lib/cequel/model/validations.rb +18 -18
  34. data/lib/cequel/schema.rb +15 -0
  35. data/lib/cequel/schema/column.rb +135 -0
  36. data/lib/cequel/schema/create_table_dsl.rb +56 -0
  37. data/lib/cequel/schema/keyspace.rb +50 -0
  38. data/lib/cequel/schema/table.rb +120 -0
  39. data/lib/cequel/schema/table_property.rb +67 -0
  40. data/lib/cequel/schema/table_reader.rb +139 -0
  41. data/lib/cequel/schema/table_synchronizer.rb +114 -0
  42. data/lib/cequel/schema/table_updater.rb +83 -0
  43. data/lib/cequel/schema/table_writer.rb +80 -0
  44. data/lib/cequel/schema/update_table_dsl.rb +60 -0
  45. data/lib/cequel/type.rb +232 -0
  46. data/lib/cequel/version.rb +1 -1
  47. data/spec/environment.rb +5 -1
  48. data/spec/examples/metal/data_set_spec.rb +608 -0
  49. data/spec/examples/model/associations_spec.rb +84 -74
  50. data/spec/examples/model/callbacks_spec.rb +66 -59
  51. data/spec/examples/model/list_spec.rb +393 -0
  52. data/spec/examples/model/map_spec.rb +229 -0
  53. data/spec/examples/model/mass_assignment_spec.rb +55 -0
  54. data/spec/examples/model/naming_spec.rb +11 -4
  55. data/spec/examples/model/persistence_spec.rb +140 -150
  56. data/spec/examples/model/properties_spec.rb +122 -75
  57. data/spec/examples/model/record_set_spec.rb +285 -0
  58. data/spec/examples/model/schema_spec.rb +44 -0
  59. data/spec/examples/model/serialization_spec.rb +20 -14
  60. data/spec/examples/model/set_spec.rb +133 -0
  61. data/spec/examples/model/spec_helper.rb +0 -10
  62. data/spec/examples/model/validations_spec.rb +51 -38
  63. data/spec/examples/schema/table_reader_spec.rb +328 -0
  64. data/spec/examples/schema/table_synchronizer_spec.rb +172 -0
  65. data/spec/examples/schema/table_updater_spec.rb +157 -0
  66. data/spec/examples/schema/table_writer_spec.rb +225 -0
  67. data/spec/examples/spec_helper.rb +29 -0
  68. data/spec/examples/type_spec.rb +204 -0
  69. data/spec/support/helpers.rb +67 -8
  70. metadata +121 -152
  71. data/lib/cequel/batch.rb +0 -58
  72. data/lib/cequel/cql_row_specification.rb +0 -22
  73. data/lib/cequel/data_set.rb +0 -371
  74. data/lib/cequel/keyspace.rb +0 -205
  75. data/lib/cequel/model/class_internals.rb +0 -49
  76. data/lib/cequel/model/column.rb +0 -20
  77. data/lib/cequel/model/counter.rb +0 -35
  78. data/lib/cequel/model/dictionary.rb +0 -126
  79. data/lib/cequel/model/dirty.rb +0 -53
  80. data/lib/cequel/model/dynamic.rb +0 -31
  81. data/lib/cequel/model/inheritable.rb +0 -48
  82. data/lib/cequel/model/instance_internals.rb +0 -23
  83. data/lib/cequel/model/local_association.rb +0 -42
  84. data/lib/cequel/model/magic.rb +0 -79
  85. data/lib/cequel/model/mass_assignment_security.rb +0 -21
  86. data/lib/cequel/model/naming.rb +0 -17
  87. data/lib/cequel/model/observer.rb +0 -42
  88. data/lib/cequel/model/readable_dictionary.rb +0 -182
  89. data/lib/cequel/model/remote_association.rb +0 -40
  90. data/lib/cequel/model/scope.rb +0 -362
  91. data/lib/cequel/model/subclass_internals.rb +0 -45
  92. data/lib/cequel/model/timestamps.rb +0 -52
  93. data/lib/cequel/model/translation.rb +0 -17
  94. data/lib/cequel/row_specification.rb +0 -63
  95. data/lib/cequel/statement.rb +0 -23
  96. data/spec/examples/data_set_spec.rb +0 -444
  97. data/spec/examples/keyspace_spec.rb +0 -84
  98. data/spec/examples/model/counter_spec.rb +0 -94
  99. data/spec/examples/model/dictionary_spec.rb +0 -301
  100. data/spec/examples/model/dirty_spec.rb +0 -39
  101. data/spec/examples/model/dynamic_spec.rb +0 -41
  102. data/spec/examples/model/inheritable_spec.rb +0 -45
  103. data/spec/examples/model/magic_spec.rb +0 -199
  104. data/spec/examples/model/mass_assignment_security_spec.rb +0 -13
  105. data/spec/examples/model/observer_spec.rb +0 -86
  106. data/spec/examples/model/scope_spec.rb +0 -677
  107. data/spec/examples/model/timestamps_spec.rb +0 -52
  108. data/spec/examples/model/translation_spec.rb +0 -23
@@ -0,0 +1,65 @@
1
+ module Cequel
2
+
3
+ module Metal
4
+
5
+ class Updater < Writer
6
+
7
+ def set(data)
8
+ data.each_pair do |column, value|
9
+ prepare_upsert_value(value) do |binding, *values|
10
+ statements << "#{column} = #{binding}"
11
+ bind_vars.concat(values)
12
+ end
13
+ end
14
+ end
15
+
16
+ def list_prepend(column, elements)
17
+ statements << "#{column} = [?] + #{column}"
18
+ bind_vars << elements
19
+ end
20
+
21
+ def list_append(column, elements)
22
+ statements << "#{column} = #{column} + [?]"
23
+ bind_vars << elements
24
+ end
25
+
26
+ def list_remove(column, value)
27
+ statements << "#{column} = #{column} - [?]"
28
+ bind_vars << value
29
+ end
30
+
31
+ def list_replace(column, index, value)
32
+ statements << "#{column}[#{index}] = ?"
33
+ bind_vars << value
34
+ end
35
+
36
+ def set_add(column, values)
37
+ statements << "#{column} = #{column} + {?}"
38
+ bind_vars << values
39
+ end
40
+
41
+ def set_remove(column, value)
42
+ statements << "#{column} = #{column} - {?}"
43
+ bind_vars << ::Kernel.Array(value)
44
+ end
45
+
46
+ def map_update(column, updates)
47
+ binding_pairs = ::Array.new(updates.length) { '?:?' }.join(',')
48
+ statements << "#{column} = #{column} + {#{binding_pairs}}"
49
+ bind_vars.concat(updates.flatten)
50
+ end
51
+
52
+ private
53
+
54
+ def write_to_statement(statement)
55
+ statement.append("UPDATE #{table_name}").
56
+ append(generate_upsert_options).
57
+ append(" SET ").
58
+ append(statements.join(', '), *bind_vars)
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,73 @@
1
+ require 'delegate'
2
+
3
+ module Cequel
4
+
5
+ module Metal
6
+
7
+ class Writer
8
+
9
+ extend Forwardable
10
+
11
+ def initialize(data_set, options = {}, &block)
12
+ @data_set, @options, @block = data_set, options, block
13
+ @statements, @bind_vars = [], []
14
+ SimpleDelegator.new(self).instance_eval(&block) if block
15
+ end
16
+
17
+ def execute
18
+ return if empty?
19
+ statement = Statement.new
20
+ write_to_statement(statement)
21
+ statement.append(*data_set.row_specifications_cql)
22
+ data_set.write(*statement.args)
23
+ end
24
+
25
+ private
26
+ attr_reader :data_set, :options, :statements, :bind_vars
27
+ def_delegator :data_set, :table_name
28
+ def_delegator :statements, :empty?
29
+
30
+ def prepare_upsert_value(value)
31
+ case value
32
+ when ::Array
33
+ yield '[?]', value
34
+ when ::Set then
35
+ yield '{?}', value.to_a
36
+ when ::Hash then
37
+ binding_pairs = ::Array.new(value.length) { '?:?' }.join(',')
38
+ yield "{#{binding_pairs}}", *value.flatten
39
+ else
40
+ yield '?', value
41
+ end
42
+ end
43
+
44
+ #
45
+ # Generate CQL option statement for inserts and updates
46
+ #
47
+ # @param [Hash] options options for insert
48
+ # @option options [Symbol,String] :consistency required consistency for the write
49
+ # @option options [Integer] :ttl time-to-live in seconds for the written data
50
+ # @option options [Time,Integer] :timestamp the timestamp associated with the column values
51
+ #
52
+ def generate_upsert_options
53
+ if options.empty?
54
+ ''
55
+ else
56
+ ' USING ' <<
57
+ options.map do |key, value|
58
+ serialized_value =
59
+ case key
60
+ when :consistency then value.to_s.upcase
61
+ when :timestamp then (value.to_f * 1_000_000).to_i
62
+ else value
63
+ end
64
+ "#{key.to_s.upcase} #{serialized_value}"
65
+ end.join(' AND ')
66
+ end
67
+ end
68
+
69
+ end
70
+
71
+ end
72
+
73
+ end
data/lib/cequel/model.rb CHANGED
@@ -1,100 +1,28 @@
1
1
  require 'active_model'
2
2
 
3
3
  require 'cequel'
4
- require 'cequel/model/associations'
5
- require 'cequel/model/callbacks'
6
- require 'cequel/model/class_internals'
7
- require 'cequel/model/column'
8
- require 'cequel/model/counter'
9
- require 'cequel/model/dictionary'
10
- require 'cequel/model/dirty'
11
- require 'cequel/model/dynamic'
12
4
  require 'cequel/model/errors'
13
- require 'cequel/model/inheritable'
14
- require 'cequel/model/instance_internals'
15
- require 'cequel/model/local_association'
16
- require 'cequel/model/mass_assignment_security'
17
- require 'cequel/model/magic'
18
- require 'cequel/model/naming'
19
- require 'cequel/model/observer'
20
- require 'cequel/model/persistence'
5
+ require 'cequel/model/schema'
21
6
  require 'cequel/model/properties'
22
- require 'cequel/model/remote_association'
23
- require 'cequel/model/scope'
7
+ require 'cequel/model/collection'
8
+ require 'cequel/model/persistence'
9
+ require 'cequel/model/record_set'
24
10
  require 'cequel/model/scoped'
25
- require 'cequel/model/subclass_internals'
26
- require 'cequel/model/timestamps'
27
- require 'cequel/model/translation'
11
+ require 'cequel/model/associations'
12
+ require 'cequel/model/association_collection'
13
+ require 'cequel/model/belongs_to_association'
14
+ require 'cequel/model/has_many_association'
15
+ require 'cequel/model/mass_assignment'
16
+ require 'cequel/model/callbacks'
28
17
  require 'cequel/model/validations'
29
18
 
19
+ require 'cequel/model/base'
20
+
30
21
  if defined? Rails
31
22
  require 'cequel/model/railtie'
32
23
  end
33
24
 
34
25
  module Cequel
35
-
36
- #
37
- # This module adds Cassandra persistence to a class using Cequel.
38
- #
39
26
  module Model
40
-
41
- @lock = Monitor.new
42
-
43
- extend ActiveSupport::Concern
44
- extend ActiveModel::Observing::ClassMethods
45
-
46
- included do
47
- @_cequel = ClassInternals.new(self)
48
-
49
- include Properties
50
- include Persistence
51
- include Scoped
52
- include Naming
53
- include Callbacks
54
- include Validations
55
- include ActiveModel::Observing
56
- include Dirty
57
- include MassAssignmentSecurity
58
- include Associations
59
- extend Inheritable
60
- extend Magic
61
-
62
- include ActiveModel::Serializers::JSON
63
- include ActiveModel::Serializers::Xml
64
-
65
- extend Translation
66
- end
67
-
68
- def self.keyspace
69
- @lock.synchronize do
70
- @keyspace ||= Cequel.connect(@configuration).tap do |keyspace|
71
- keyspace.logger = @logger if @logger
72
- keyspace.slowlog = @slowlog if @slowlog
73
- keyspace.slowlog_threshold = @slowlog_threshold if @slowlog_threshold
74
- end
75
- end
76
- end
77
-
78
- def self.configure(configuration)
79
- @configuration = configuration
80
- end
81
-
82
- def self.logger=(logger)
83
- @logger = logger
84
- end
85
-
86
- def self.slowlog=(slowlog)
87
- @slowlog = slowlog
88
- end
89
-
90
- def self.slowlog_threshold=(slowlog_threshold)
91
- @slowlog_threshold = slowlog_threshold
92
- end
93
-
94
- def initialize
95
- @_cequel = InstanceInternals.new(self)
96
- end
97
-
98
27
  end
99
-
100
28
  end
@@ -0,0 +1,23 @@
1
+ module Cequel
2
+
3
+ module Model
4
+
5
+ class AssociationCollection < DelegateClass(RecordSet)
6
+
7
+ include Enumerable
8
+
9
+ def each(&block)
10
+ target.each(&block)
11
+ end
12
+
13
+ private
14
+
15
+ def target
16
+ @target ||= __getobj__.entries
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+
23
+ end
@@ -6,115 +6,119 @@ module Cequel
6
6
 
7
7
  extend ActiveSupport::Concern
8
8
 
9
- module ClassMethods
10
-
11
- def belongs_to(name, options = {})
12
- name = name.to_sym
13
- association = LocalAssociation.new(name, self, options.symbolize_keys)
14
- @_cequel.associations[name] = association
15
- column(association.foreign_key_name, association.primary_key.type)
9
+ included do
10
+ class_attribute :parent_association
11
+ class_attribute :child_associations
12
+ self.child_associations = {}
13
+ end
16
14
 
17
- module_eval <<-RUBY, __FILE__, __LINE__+1
18
- def #{name}
19
- if @_cequel.associations.key?(#{name.inspect})
20
- return @_cequel.associations[#{name.inspect}]
21
- end
22
- key = __send__(:#{name}_id)
23
- if key
24
- @_cequel.associations[#{name.inspect}] =
25
- self.class.reflect_on_association(#{name.inspect}).
26
- scope(self).first
27
- else
28
- @_cequel.associations[#{name.inspect}] = nil
29
- end
30
- end
15
+ module ClassMethods
31
16
 
32
- def #{name}=(instance)
33
- @_cequel.associations[#{name.inspect}] = instance
34
- if instance.nil?
35
- key = nil
36
- else
37
- key = instance.__send__(instance.class.key_alias)
38
- end
39
- write_attribute(#{association.foreign_key_name.inspect}, key)
40
- end
17
+ include Forwardable
41
18
 
42
- def #{association.foreign_key_name}=(key)
43
- @_cequel.associations.delete(#{name.inspect})
44
- write_attribute(#{association.foreign_key_name.inspect}, key)
45
- end
46
- RUBY
19
+ def belongs_to(name)
20
+ if parent_association
21
+ raise InvalidRecordConfiguration,
22
+ "Can't declare more than one belongs_to association"
23
+ end
24
+ if table_schema.key_columns.any?
25
+ raise InvalidRecordConfiguration,
26
+ "belongs_to association must be declared before declaring key(s)"
27
+ end
28
+ self.parent_association = BelongsToAssociation.new(self, name.to_sym)
29
+ parent_association.association_key_columns.each do |column|
30
+ key :"#{name}_#{column.name}", column.type
31
+ end
32
+ def_parent_association_accessors
47
33
  end
48
34
 
49
- def has_many(name, options = {})
50
- name = name.to_sym
51
- @_cequel.associations[name] =
52
- RemoteAssociation.new(name, self, options.symbolize_keys)
53
-
54
- module_eval <<-RUBY, __FILE__, __LINE__+1
55
- def #{name}
56
- self.class.reflect_on_association(#{name.inspect}).scope(self)
57
- end
58
- RUBY
35
+ def has_many(name)
36
+ association = HasManyAssociation.new(self, name.to_sym)
37
+ self.child_associations =
38
+ child_associations.merge(name => association)
39
+ def_child_association_reader(association)
59
40
  end
60
41
 
61
- def has_one(name, options = {})
62
- name = name.to_sym
63
- @_cequel.associations[name] =
64
- RemoteAssociation.new(name, self, options.symbolize_keys)
42
+ private
65
43
 
66
- module_eval <<-RUBY, __FILE__, __LINE__+1
67
- def #{name}
68
- self.class.reflect_on_association(#{name.inspect}).scope(self).first
69
- end
70
- RUBY
44
+ def def_parent_association_accessors
45
+ def_parent_association_reader
46
+ def_parent_association_writer
71
47
  end
72
48
 
73
- def reflect_on_association(name)
74
- @_cequel.association(name.to_sym)
49
+ def def_parent_association_reader
50
+ def_delegator 'self', :read_parent_association,
51
+ parent_association.name
75
52
  end
76
53
 
77
- def reflect_on_associations
78
- @_cequel.associations.values
54
+ def def_parent_association_writer
55
+ def_delegator 'self', :write_parent_association,
56
+ "#{parent_association.name}="
79
57
  end
80
58
 
81
- end
82
-
83
- def save(*args)
84
- save_transient_associated
85
- super
86
- end
59
+ def def_child_association_reader(association)
60
+ module_eval <<-RUBY, __FILE__, __LINE__+1
61
+ def #{association.name}(reload = false)
62
+ read_child_association(#{association.name.inspect}, reload)
63
+ end
64
+ RUBY
65
+ end
87
66
 
88
- def destroy(*args)
89
- destroy_associated
90
- super
91
67
  end
92
68
 
93
69
  private
94
70
 
95
- def save_transient_associated
96
- self.class.reflect_on_associations.each do |association|
97
- if LocalAssociation === association
98
- associated = @_cequel.associations[association.name]
99
- if associated && associated.transient?
100
- associated.save
101
- end
71
+ def read_parent_association
72
+ ivar_name = parent_association.instance_variable_name
73
+ if instance_variable_defined?(ivar_name)
74
+ return instance_variable_get(ivar_name)
75
+ end
76
+ parent_key_values = key_values.
77
+ first(parent_association.association_key_columns.length)
78
+ if parent_key_values.none? { |value| value.nil? }
79
+ clazz = parent_association.association_class
80
+ parent = parent_key_values.inject(clazz) do |record_set, key_value|
81
+ record_set[key_value]
102
82
  end
83
+ instance_variable_set(ivar_name, parent)
103
84
  end
104
85
  end
105
86
 
106
- def destroy_associated
107
- self.class.reflect_on_associations.each do |association|
108
- if association.dependent == :destroy
109
- association.scope(self).each do |associated|
110
- associated.destroy
87
+ def write_parent_association(parent)
88
+ unless parent.is_a?(parent_association.association_class)
89
+ raise ArgumentError,
90
+ "Wrong class for #{parent_association.name}; expected " +
91
+ "#{parent_association.association_class.name}, got " +
92
+ "#{parent.class.name}"
93
+ end
94
+ instance_variable_set "@#{parent_association.name}", parent
95
+ key_column_names = self.class.key_column_names
96
+ parent.key_attributes.
97
+ zip(key_column_names) do |(parent_column_name, value), column_name|
98
+ if value.nil?
99
+ raise ArgumentError,
100
+ "Can't set parent association #{parent_association.name.inspect} " +
101
+ "without value in key #{parent_column_name.inspect}"
111
102
  end
103
+ write_attribute(column_name, value)
112
104
  end
105
+ end
106
+
107
+ def read_child_association(association_name, reload = false)
108
+ association = child_associations[association_name]
109
+ ivar = association.instance_variable_name
110
+ if !reload && instance_variable_defined?(ivar)
111
+ return instance_variable_get(ivar)
113
112
  end
113
+ association_record_set = key_values.inject(association.association_class) do |record_set, key_value|
114
+ record_set[key_value]
115
+ end
116
+ instance_variable_set(
117
+ ivar, AssociationCollection.new(association_record_set))
114
118
  end
115
119
 
116
120
  end
117
-
121
+
118
122
  end
119
123
 
120
124
  end