cequel 0.5.6 → 1.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
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