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,31 @@
1
+ begin
2
+ require 'active_model/forbidden_attributes_protection'
3
+ rescue LoadError
4
+ require 'active_model/mass_assignment_security'
5
+ end
6
+
7
+ module Cequel
8
+
9
+ module Model
10
+
11
+ module MassAssignment
12
+
13
+ extend ActiveSupport::Concern
14
+
15
+ included do
16
+ if defined? ActiveModel::ForbiddenAttributesProtection
17
+ include ActiveModel::ForbiddenAttributesProtection
18
+ else
19
+ include ActiveModel::MassAssignmentSecurity
20
+ end
21
+ end
22
+
23
+ def attributes=(attributes)
24
+ super(sanitize_for_mass_assignment(attributes))
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -8,170 +8,174 @@ module Cequel
8
8
 
9
9
  module ClassMethods
10
10
 
11
- delegate :update_all, :destroy_all, :delete_all, :to => :all
12
-
13
- def index_preference(*columns)
14
- @_cequel.index_preference.concat(columns.map { |c| c.to_sym })
15
- end
16
-
17
- def index_preference_columns
18
- @_cequel.index_preference
19
- end
20
-
21
- def find(*keys)
22
- coerce_array = keys.first.is_a?(Array)
23
- keys.flatten!
24
- if keys.length == 1
25
- instance = find_one(keys.first)
26
- coerce_array ? [instance] : instance
27
- else
28
- find_many(keys)
29
- end
30
- end
31
-
32
11
  def create(attributes = {}, &block)
33
- new(attributes, &block).tap { |instance| instance.save }
12
+ new(attributes, &block).tap { |record| record.save }
34
13
  end
35
14
 
36
- def column_family_name
37
- @_cequel.column_family_name
15
+ def hydrate(row)
16
+ new_empty { hydrate(row) }
38
17
  end
39
18
 
40
- def column_family_name=(name)
41
- @_cequel.column_family_name = name
42
- end
19
+ end
43
20
 
44
- def column_family
45
- keyspace[column_family_name]
46
- end
21
+ def key_attributes
22
+ @attributes.slice(*self.class.key_column_names)
23
+ end
47
24
 
48
- def keyspace
49
- Cequel::Model.keyspace
50
- end
25
+ def key_values
26
+ key_attributes.values
27
+ end
28
+
29
+ def exists?
30
+ load!
31
+ true
32
+ rescue Cequel::Model::RecordNotFound
33
+ false
34
+ end
35
+ alias :exist? :exists?
51
36
 
52
- def with_consistency(consistency, &block)
53
- keyspace.with_consistency(consistency, &block)
37
+ def load
38
+ unless loaded?
39
+ row = metal_scope.first
40
+ hydrate(row) unless row.nil?
41
+ collection_proxies.each_value { |collection| collection.loaded! }
54
42
  end
43
+ self
44
+ end
55
45
 
56
- def _hydrate(row)
57
- type_column_name = @_cequel.type_column.try(:name)
58
- if type_column_name && row[type_column_name]
59
- clazz = row[type_column_name].constantize
60
- else
61
- clazz = self
46
+ def load!
47
+ load.tap do
48
+ if transient?
49
+ raise Cequel::Model::RecordNotFound,
50
+ "Couldn't find #{self.class.name} with #{key_attributes.inspect}"
62
51
  end
63
- clazz.allocate._hydrate(row.except(:type))
64
52
  end
53
+ end
65
54
 
66
- private
55
+ def loaded?(column = nil)
56
+ !!@loaded && (column.nil? || @attributes.key?(column.to_sym))
57
+ end
67
58
 
68
- def find_one(key)
69
- all.where!(key_alias => key).first.tap do |result|
70
- if result.nil?
71
- raise RecordNotFound,
72
- "Couldn't find #{name} with #{key_alias}=#{key}"
73
- end
74
- end
59
+ def save(options = {})
60
+ options.assert_valid_keys
61
+ if new_record? then create
62
+ else update
75
63
  end
64
+ @new_record = false
65
+ true
66
+ end
76
67
 
77
- def find_many(keys)
78
- results = all.where!(key_alias => keys).reject do |result|
79
- result.attributes.keys == [key_alias.to_s]
80
- end
68
+ def update_attributes(attributes)
69
+ self.attributes = attributes
70
+ save
71
+ end
81
72
 
82
- if results.length < keys.length
83
- raise RecordNotFound,
84
- "Couldn't find all #{name.pluralize} with #{key_alias} (#{keys.join(', ')})" <<
85
- "(found #{results.length} results, but was looking for #{keys.length}"
86
- end
87
- results
88
- end
73
+ def destroy
74
+ metal_scope.delete
75
+ transient!
76
+ self
77
+ end
89
78
 
79
+ def new_record?
80
+ !!@new_record
90
81
  end
91
82
 
92
- def save
93
- persisted? ? update : insert
83
+ def persisted?
84
+ !!@persisted
94
85
  end
95
86
 
96
- def update_attributes(attributes)
97
- self.attributes = attributes
98
- save
87
+ def transient?
88
+ !persisted?
99
89
  end
100
90
 
101
- def update_attribute(column, value)
102
- update_attributes(column => value)
91
+ protected
92
+
93
+ def persisted!
94
+ @persisted = true
103
95
  end
104
96
 
105
- def insert
106
- raise MissingKey if @_cequel.key.nil?
107
- return if @_cequel.attributes.empty?
108
- self.class.column_family.insert(attributes)
97
+ def transient!
98
+ @persisted = false
99
+ end
100
+
101
+ def create
102
+ inserter.execute
103
+ loaded!
109
104
  persisted!
110
105
  end
111
106
 
112
107
  def update
113
- update_attributes, delete_attributes = {}, []
114
- changed.each do |attr|
115
- new = read_attribute(attr)
116
- if new.nil?
117
- delete_attributes << attr
118
- else
119
- update_attributes[attr] = new
120
- end
108
+ connection.batch do
109
+ updater.execute
110
+ deleter.execute
111
+ @updater, @deleter = nil
121
112
  end
122
- data_set.update(update_attributes) if update_attributes.any?
123
- data_set.delete(*delete_attributes) if delete_attributes.any?
124
- transient! if @_cequel.attributes.empty?
125
113
  end
126
114
 
127
- def destroy
128
- data_set.delete
115
+ def inserter
116
+ @inserter ||= metal_scope.inserter
129
117
  end
130
118
 
131
- def reload
132
- result = data_set.first
133
- key_alias = self.class.key_alias
134
- if result.keys == [key_alias.to_s]
135
- raise RecordNotFound,
136
- "Couldn't find #{self.class.name} with #{key_alias}=#{@_cequel.key}"
137
- end
138
- _hydrate(result)
139
- self
119
+ def updater
120
+ @updater ||= metal_scope.updater
121
+ end
122
+
123
+ def deleter
124
+ @deleter ||= metal_scope.deleter
140
125
  end
141
126
 
142
- def _hydrate(row)
143
- @_cequel = InstanceInternals.new(self)
144
- tap do
145
- key_alias = self.class.key_alias.to_s
146
- key_alias = 'KEY' if key_alias.upcase == 'KEY'
147
- @_cequel.key = row[key_alias]
148
- @_cequel.attributes = row.except(key_alias)
149
- persisted!
127
+ private
128
+
129
+ def read_attribute(attribute)
130
+ super
131
+ rescue MissingAttributeError
132
+ load
133
+ super
134
+ end
135
+
136
+ def write_attribute(attribute, value)
137
+ super.tap do
138
+ if !persisted?
139
+ inserter.insert(attribute => value) unless value.nil?
140
+ elsif !self.class.key_column_names.include?(attribute.to_sym)
141
+ if value.nil?
142
+ deleter.delete_columns(attribute)
143
+ else
144
+ updater.set(attribute => value)
145
+ end
146
+ end
150
147
  end
151
148
  end
152
149
 
153
- def persisted!
154
- @_cequel.persisted = true
150
+ def hydrate(row)
151
+ @attributes = row
152
+ loaded!
153
+ persisted!
154
+ self
155
155
  end
156
156
 
157
- def transient!
158
- @_cequel.persisted = false
157
+ def loaded!
158
+ @loaded = true
159
+ collection_proxies.each_value { |collection| collection.loaded! }
159
160
  end
160
161
 
161
- def persisted?
162
- !!@_cequel.persisted
162
+ def metal_scope
163
+ connection[table_name].
164
+ where(key_attributes)
163
165
  end
164
166
 
165
- def transient?
166
- !persisted?
167
+ def attributes_for_create
168
+ @attributes.each_with_object({}) do |(column, value), attributes|
169
+ attributes[column] = value unless value.nil?
170
+ end
167
171
  end
168
172
 
169
- private
173
+ def attributes_for_update
174
+ @attributes_for_update ||= {}
175
+ end
170
176
 
171
- def data_set
172
- raise MissingKey if @_cequel.key.nil?
173
- self.class.column_family.
174
- where(self.class.key_alias => @_cequel.key)
177
+ def attributes_for_deletion
178
+ @attributes_for_deletion ||= []
175
179
  end
176
180
 
177
181
  end
@@ -6,134 +6,136 @@ module Cequel
6
6
 
7
7
  extend ActiveSupport::Concern
8
8
 
9
- included do
10
- include ActiveModel::Conversion
11
- end
12
-
13
9
  module ClassMethods
14
10
 
15
- def key(key_alias, type)
16
- key_alias = key_alias.to_sym
17
- @_cequel.key = Column.new(key_alias, type)
11
+ protected
18
12
 
19
- module_eval(<<-RUBY, __FILE__, __LINE__+1)
20
- def #{key_alias}
21
- @_cequel.key
22
- end
13
+ def key(name, type)
14
+ def_accessors(name)
15
+ table_schema.add_key(name, type)
16
+ set_attribute_default(name, nil)
17
+ end
23
18
 
24
- def #{key_alias}=(key)
25
- @_cequel.key = key
26
- end
19
+ def column(name, type, options = {})
20
+ def_accessors(name)
21
+ table_schema.add_data_column(name, type, options[:index])
22
+ set_attribute_default(name, options[:default])
23
+ end
27
24
 
28
- def to_key
29
- [@_cequel.key]
30
- end
31
- RUBY
25
+ def list(name, type, options = {})
26
+ def_collection_accessors(name, List)
27
+ table_schema.add_list(name, type)
28
+ set_attribute_default(name, options.fetch(:default, []))
32
29
  end
33
30
 
34
- def column(name, type, options = {})
35
- name = name.to_sym
36
- @_cequel.add_column(name, type, options.symbolize_keys)
31
+ def set(name, type, options = {})
32
+ def_collection_accessors(name, Set)
33
+ table_schema.add_set(name, type)
34
+ set_attribute_default(name, options.fetch(:default, ::Set[]))
35
+ end
37
36
 
38
- module_eval <<-RUBY, __FILE__, __LINE__+1
39
- def #{name}
40
- read_attribute(#{name.inspect})
41
- end
37
+ def map(name, key_type, value_type, options = {})
38
+ def_collection_accessors(name, Map)
39
+ table_schema.add_map(name, key_type, value_type)
40
+ set_attribute_default(name, options.fetch(:default, {}))
41
+ end
42
42
 
43
- def #{name}=(value)
44
- write_attribute(#{name.inspect}, value)
45
- end
46
- RUBY
43
+ def table_property(name, value)
44
+ table_schema.add_property(name, value)
45
+ end
47
46
 
48
- if type == :boolean
49
- module_eval <<-RUBY, __FILE__, __LINE__+1 if type == :boolean
50
- def #{name}?
51
- !!read_attribute(#{name.inspect})
52
- end
53
- RUBY
54
- end
47
+ private
48
+
49
+ def def_accessors(name)
50
+ name = name.to_sym
51
+ def_reader(name)
52
+ def_writer(name)
55
53
  end
56
54
 
57
- def key_alias
58
- key_column.name
55
+ def def_reader(name)
56
+ module_eval <<-RUBY
57
+ def #{name}; read_attribute(#{name.inspect}); end
58
+ RUBY
59
59
  end
60
60
 
61
- def key_column
62
- @_cequel.key
61
+ def def_writer(name)
62
+ module_eval <<-RUBY
63
+ def #{name}=(value); write_attribute(#{name.inspect}, value); end
64
+ RUBY
63
65
  end
64
66
 
65
- def column_names
66
- [@_cequel.key.name, *@_cequel.columns.keys]
67
+ def def_collection_accessors(name, collection_proxy_class)
68
+ def_collection_reader(name, collection_proxy_class)
69
+ def_collection_writer(name)
67
70
  end
68
71
 
69
- def columns
70
- [@_cequel.key, *@_cequel.columns.values]
72
+ def def_collection_reader(name, collection_proxy_class)
73
+ module_eval <<-RUBY
74
+ def #{name}
75
+ proxy_collection(#{name.inspect}, #{collection_proxy_class})
76
+ end
77
+ RUBY
78
+ end
79
+
80
+ def def_collection_writer(name)
81
+ module_eval <<-RUBY
82
+ def #{name}=(value)
83
+ reset_collection_proxy(#{name.inspect})
84
+ write_attribute(#{name.inspect}, value)
85
+ end
86
+ RUBY
71
87
  end
72
88
 
73
- def type_column
74
- @_cequel.type_column
89
+ def set_attribute_default(name, default)
90
+ default_attributes[name.to_sym] = default
75
91
  end
76
92
 
77
93
  end
78
94
 
79
- def initialize(attributes = {})
80
- super()
81
- @_cequel.key = generate_key
82
- self.class.columns.each do |column|
83
- default = column.default
84
- @_cequel.attributes[column.name] = default unless default.nil?
85
- end
86
- self.attributes = attributes
87
- yield self if block_given?
95
+ def attribute_names
96
+ @attributes.keys
88
97
  end
89
98
 
90
99
  def attributes
91
- {self.class.key_alias => @_cequel.key}.with_indifferent_access.
92
- merge(@_cequel.attributes)
100
+ attribute_names.each_with_object({}) do |name, attributes|
101
+ attributes[name] = read_attribute(name)
102
+ end
93
103
  end
94
104
 
95
105
  def attributes=(attributes)
96
- attributes.each_pair do |column_name, value|
97
- __send__("#{column_name}=", value)
106
+ attributes.each_pair do |attribute, value|
107
+ __send__(:"#{attribute}=", value)
98
108
  end
99
109
  end
100
110
 
101
- def ==(other)
102
- return false if self.class != other.class
103
- self_key = self.__send__(self.class.key_column.name)
104
- other_key = other.__send__(self.class.key_column.name)
105
- self_key && other_key && self_key == other_key
106
- end
111
+ protected
112
+ delegate :table_schema, :to => 'self.class'
107
113
 
108
- def inspect
109
- "#<#{self.class.name}".tap do |inspected|
110
- attributes.each_pair do |column, value|
111
- inspected_value =
112
- case value
113
- when CassandraCQL::UUID then value.to_guid
114
- else value.inspect
115
- end
116
- inspected << " #{column}:#{inspected_value}"
117
- end
114
+ def read_attribute(name)
115
+ @attributes.fetch(name)
116
+ rescue KeyError
117
+ if table_schema.column(name)
118
+ raise MissingAttributeError, "missing attribute: #{name}"
119
+ else
120
+ raise UnknownAttributeError, "unknown attribute: #{name}"
118
121
  end
119
122
  end
120
123
 
121
- private
122
-
123
- def write_attribute(column_name, value)
124
- if value.nil?
125
- @_cequel.attributes.delete(column_name)
126
- else
127
- @_cequel.attributes[column_name] = value
128
- end
124
+ def write_attribute(name, value)
125
+ column = table_schema.column(name)
126
+ raise UnknownAttributeError,
127
+ "unknown attribute: #{name}" unless column
128
+ @attributes[name] = value.nil? ? nil : column.cast(value)
129
129
  end
130
130
 
131
- def read_attribute(column_name)
132
- @_cequel.attributes[column_name.to_sym]
131
+ private
132
+
133
+ def proxy_collection(name, proxy_class)
134
+ collection_proxies[name] ||= proxy_class.new(self, name)
133
135
  end
134
136
 
135
- def generate_key
136
- # Noop -- model classes can override if desired
137
+ def reset_collection_proxy(name)
138
+ collection_proxies.delete(name)
137
139
  end
138
140
 
139
141
  end