cequel 1.0.0.pre.2 → 1.0.0.pre.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/lib/cequel.rb +1 -1
  3. data/lib/cequel/record.rb +62 -0
  4. data/lib/cequel/{model → record}/association_collection.rb +1 -1
  5. data/lib/cequel/{model → record}/associations.rb +1 -1
  6. data/lib/cequel/{model → record}/belongs_to_association.rb +1 -1
  7. data/lib/cequel/{model → record}/callbacks.rb +1 -1
  8. data/lib/cequel/{model → record}/collection.rb +1 -1
  9. data/lib/cequel/{model → record}/dirty.rb +1 -1
  10. data/lib/cequel/{model → record}/errors.rb +1 -1
  11. data/lib/cequel/{model → record}/has_many_association.rb +1 -1
  12. data/lib/cequel/{model → record}/mass_assignment.rb +1 -1
  13. data/lib/cequel/{model → record}/persistence.rb +11 -11
  14. data/lib/cequel/{model → record}/properties.rb +69 -15
  15. data/lib/cequel/{model → record}/railtie.rb +3 -3
  16. data/lib/cequel/{model → record}/record_set.rb +67 -57
  17. data/lib/cequel/record/schema.rb +76 -0
  18. data/lib/cequel/record/scoped.rb +53 -0
  19. data/lib/cequel/{model → record}/secondary_indexes.rb +1 -1
  20. data/lib/cequel/{model → record}/validations.rb +1 -1
  21. data/lib/cequel/schema/table.rb +18 -9
  22. data/lib/cequel/version.rb +1 -1
  23. data/spec/examples/{model → record}/associations_spec.rb +8 -8
  24. data/spec/examples/{model → record}/callbacks_spec.rb +1 -1
  25. data/spec/examples/{model → record}/dirty_spec.rb +1 -1
  26. data/spec/examples/{model → record}/list_spec.rb +1 -1
  27. data/spec/examples/{model → record}/map_spec.rb +1 -1
  28. data/spec/examples/{model → record}/mass_assignment_spec.rb +1 -1
  29. data/spec/examples/{model → record}/naming_spec.rb +0 -0
  30. data/spec/examples/{model → record}/persistence_spec.rb +1 -1
  31. data/spec/examples/{model → record}/properties_spec.rb +25 -2
  32. data/spec/examples/{model → record}/record_set_spec.rb +28 -7
  33. data/spec/examples/{model → record}/schema_spec.rb +3 -2
  34. data/spec/examples/record/scoped_spec.rb +13 -0
  35. data/spec/examples/{model → record}/secondary_index_spec.rb +1 -1
  36. data/spec/examples/{model → record}/serialization_spec.rb +1 -1
  37. data/spec/examples/{model → record}/set_spec.rb +1 -1
  38. data/spec/examples/{model → record}/spec_helper.rb +0 -0
  39. data/spec/examples/{model → record}/validations_spec.rb +4 -4
  40. data/spec/examples/spec_helper.rb +2 -2
  41. data/spec/support/helpers.rb +2 -1
  42. metadata +54 -64
  43. data/lib/cequel/model.rb +0 -30
  44. data/lib/cequel/model/base.rb +0 -84
  45. data/lib/cequel/model/schema.rb +0 -34
  46. data/lib/cequel/model/scoped.rb +0 -19
  47. data/spec/models/asset.rb +0 -21
  48. data/spec/models/asset_observer.rb +0 -5
  49. data/spec/models/blog.rb +0 -14
  50. data/spec/models/blog_posts.rb +0 -6
  51. data/spec/models/category.rb +0 -9
  52. data/spec/models/comment.rb +0 -12
  53. data/spec/models/comment_counts.rb +0 -8
  54. data/spec/models/photo.rb +0 -5
  55. data/spec/models/post.rb +0 -88
  56. data/spec/models/post_comments.rb +0 -14
  57. data/spec/models/post_observer.rb +0 -43
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7888f93ffd3d74bdfef143a9c8d18c6c9f2f4ba1
4
- data.tar.gz: 831e5a90a03cf1c22ddf08421284107254aa59a6
3
+ metadata.gz: 2f70439ec577904a2fbe7a6c8f8689f45636e800
4
+ data.tar.gz: b0ede82f2cdafba89f308d1f744252803d824c56
5
5
  SHA512:
6
- metadata.gz: 41a8585f9c78171c34944551914e4e06b44fabe615b9192901782b6653be345314c63ad4e4b5b048818bb10e1d4085aad24006285a9bd573489153b24719ccc3
7
- data.tar.gz: 90297a4b794c2eb265ffdc7659f80c1b90ef8870c1d04ff910d4d015e425a47395b8151fa72c8cf85266538e5e7fe3c495b9433751776eb727ee5983f986314c
6
+ metadata.gz: d5d46692ee7904443fb60418ff003d1c04c14f7b947b1685e08ba73945839028e09371b81368532881f3f230160314d946db1238d1652077cb6d4a11d6ffe52e
7
+ data.tar.gz: 7ac1535e9338974c0b8e545c7715810b763c8f02f3e98225c94bef6bf3502287c33fcd67c422e272371248cd7d22cdc12a71cc1a24b6305f7ae48f496670ebfa
@@ -7,7 +7,7 @@ require 'cequel/metal'
7
7
  require 'cequel/schema'
8
8
  require 'cequel/type'
9
9
  require 'cequel/util'
10
- require 'cequel/model'
10
+ require 'cequel/record'
11
11
 
12
12
  module Cequel
13
13
  def self.connect(configuration = nil)
@@ -0,0 +1,62 @@
1
+ require 'active_model'
2
+
3
+ require 'cequel'
4
+ require 'cequel/record/errors'
5
+ require 'cequel/record/schema'
6
+ require 'cequel/record/properties'
7
+ require 'cequel/record/collection'
8
+ require 'cequel/record/persistence'
9
+ require 'cequel/record/record_set'
10
+ require 'cequel/record/scoped'
11
+ require 'cequel/record/secondary_indexes'
12
+ require 'cequel/record/associations'
13
+ require 'cequel/record/association_collection'
14
+ require 'cequel/record/belongs_to_association'
15
+ require 'cequel/record/has_many_association'
16
+ require 'cequel/record/mass_assignment'
17
+ require 'cequel/record/callbacks'
18
+ require 'cequel/record/validations'
19
+ require 'cequel/record/dirty'
20
+
21
+ require 'cequel/record'
22
+
23
+ if defined? Rails
24
+ require 'cequel/record/railtie'
25
+ end
26
+
27
+ module Cequel
28
+
29
+ module Record
30
+
31
+ extend ActiveSupport::Concern
32
+ extend Forwardable
33
+
34
+ included do
35
+ include Properties
36
+ include Schema
37
+ include Persistence
38
+ include Associations
39
+ include Scoped
40
+ extend SecondaryIndexes
41
+ include MassAssignment
42
+ include Callbacks
43
+ include Validations
44
+ include Dirty
45
+ extend ActiveModel::Naming
46
+ include ActiveModel::Serializers::JSON
47
+ include ActiveModel::Serializers::Xml
48
+
49
+ end
50
+
51
+ class <<self
52
+ attr_accessor :connection
53
+
54
+ def establish_connection(configuration)
55
+ self.connection = Cequel.connect(configuration)
56
+ end
57
+
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -1,6 +1,6 @@
1
1
  module Cequel
2
2
 
3
- module Model
3
+ module Record
4
4
 
5
5
  class AssociationCollection < DelegateClass(RecordSet)
6
6
 
@@ -1,6 +1,6 @@
1
1
  module Cequel
2
2
 
3
- module Model
3
+ module Record
4
4
 
5
5
  module Associations
6
6
 
@@ -1,6 +1,6 @@
1
1
  module Cequel
2
2
 
3
- module Model
3
+ module Record
4
4
 
5
5
  class BelongsToAssociation
6
6
 
@@ -1,6 +1,6 @@
1
1
  module Cequel
2
2
 
3
- module Model
3
+ module Record
4
4
 
5
5
  module Callbacks
6
6
 
@@ -2,7 +2,7 @@ require 'delegate'
2
2
 
3
3
  module Cequel
4
4
 
5
- module Model
5
+ module Record
6
6
 
7
7
  module Collection
8
8
 
@@ -1,6 +1,6 @@
1
1
  module Cequel
2
2
 
3
- module Model
3
+ module Record
4
4
 
5
5
  module Dirty
6
6
 
@@ -1,6 +1,6 @@
1
1
  module Cequel
2
2
 
3
- module Model
3
+ module Record
4
4
 
5
5
  MissingAttributeError = Class.new(ArgumentError)
6
6
  UnknownAttributeError = Class.new(ArgumentError)
@@ -1,6 +1,6 @@
1
1
  module Cequel
2
2
 
3
- module Model
3
+ module Record
4
4
 
5
5
  class HasManyAssociation
6
6
 
@@ -6,7 +6,7 @@ end
6
6
 
7
7
  module Cequel
8
8
 
9
- module Model
9
+ module Record
10
10
 
11
11
  module MassAssignment
12
12
 
@@ -1,13 +1,17 @@
1
1
  module Cequel
2
2
 
3
- module Model
3
+ module Record
4
4
 
5
5
  module Persistence
6
6
 
7
7
  extend ActiveSupport::Concern
8
+ extend Forwardable
8
9
 
9
10
  module ClassMethods
10
11
 
12
+ extend Forwardable
13
+ def_delegator 'Cequel::Record', :connection
14
+
11
15
  def create(attributes = {}, &block)
12
16
  new(attributes, &block).tap { |record| record.save }
13
17
  end
@@ -18,6 +22,8 @@ module Cequel
18
22
 
19
23
  end
20
24
 
25
+ def_delegator 'self.class', :connection
26
+
21
27
  def key_attributes
22
28
  @attributes.slice(*self.class.key_column_names)
23
29
  end
@@ -29,7 +35,7 @@ module Cequel
29
35
  def exists?
30
36
  load!
31
37
  true
32
- rescue Cequel::Model::RecordNotFound
38
+ rescue RecordNotFound
33
39
  false
34
40
  end
35
41
  alias :exist? :exists?
@@ -46,7 +52,7 @@ module Cequel
46
52
  def load!
47
53
  load.tap do
48
54
  if transient?
49
- raise Cequel::Model::RecordNotFound,
55
+ raise RecordNotFound,
50
56
  "Couldn't find #{self.class.name} with #{key_attributes.inspect}"
51
57
  end
52
58
  end
@@ -99,7 +105,7 @@ module Cequel
99
105
  end
100
106
 
101
107
  def create
102
- inserter.execute
108
+ metal_scope.insert(attributes.reject { |attr, value| value.nil? })
103
109
  loaded!
104
110
  persisted!
105
111
  end
@@ -112,10 +118,6 @@ module Cequel
112
118
  end
113
119
  end
114
120
 
115
- def inserter
116
- @inserter ||= metal_scope.inserter
117
- end
118
-
119
121
  def updater
120
122
  @updater ||= metal_scope.updater
121
123
  end
@@ -135,9 +137,7 @@ module Cequel
135
137
 
136
138
  def write_attribute(attribute, value)
137
139
  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)
140
+ unless new_record?
141
141
  if value.nil?
142
142
  deleter.delete_columns(attribute)
143
143
  else
@@ -1,49 +1,68 @@
1
1
  module Cequel
2
2
 
3
- module Model
3
+ module Record
4
4
 
5
5
  module Properties
6
6
 
7
7
  extend ActiveSupport::Concern
8
8
 
9
+ included do
10
+ class_attribute :default_attributes, :instance_writer => false
11
+ self.default_attributes = {}
12
+
13
+ class <<self; alias_method :new_empty, :new; end
14
+ extend ConstructorMethods
15
+
16
+ attr_reader :collection_proxies
17
+ private :collection_proxies
18
+ end
19
+
20
+ module ConstructorMethods
21
+
22
+ def new(*args, &block)
23
+ new_empty.tap do |record|
24
+ record.__send__(:initialize_new_record, *args)
25
+ yield record if block_given?
26
+ end
27
+ end
28
+
29
+ end
30
+
9
31
  module ClassMethods
10
32
 
11
33
  protected
12
34
 
13
- def key(name, type)
35
+ def key(name, type, options = {})
14
36
  def_accessors(name)
15
- table_schema.add_key(name, type)
16
- set_attribute_default(name, nil)
37
+ if options.fetch(:auto, false)
38
+ unless Type[type].is_a?(Cequel::Type::Uuid)
39
+ raise ArgumentError, ":auto option only valid for UUID columns"
40
+ end
41
+ default = -> { CassandraCQL::UUID.new } if options.fetch(:auto, false)
42
+ end
43
+ set_attribute_default(name, default)
17
44
  end
18
45
 
19
46
  def column(name, type, options = {})
20
47
  def_accessors(name)
21
- table_schema.add_data_column(name, type, options[:index])
22
48
  set_attribute_default(name, options[:default])
23
49
  end
24
50
 
25
51
  def list(name, type, options = {})
26
52
  def_collection_accessors(name, List)
27
- table_schema.add_list(name, type)
28
53
  set_attribute_default(name, options.fetch(:default, []))
29
54
  end
30
55
 
31
56
  def set(name, type, options = {})
32
57
  def_collection_accessors(name, Set)
33
- table_schema.add_set(name, type)
34
58
  set_attribute_default(name, options.fetch(:default, ::Set[]))
35
59
  end
36
60
 
37
61
  def map(name, key_type, value_type, options = {})
38
62
  def_collection_accessors(name, Map)
39
- table_schema.add_map(name, key_type, value_type)
40
63
  set_attribute_default(name, options.fetch(:default, {}))
41
64
  end
42
65
 
43
- def table_property(name, value)
44
- table_schema.add_property(name, value)
45
- end
46
-
47
66
  private
48
67
 
49
68
  def def_accessors(name)
@@ -92,6 +111,11 @@ module Cequel
92
111
 
93
112
  end
94
113
 
114
+ def initialize(&block)
115
+ @attributes, @collection_proxies = {}, {}
116
+ instance_eval(&block) if block
117
+ end
118
+
95
119
  def attribute_names
96
120
  @attributes.keys
97
121
  end
@@ -108,13 +132,30 @@ module Cequel
108
132
  end
109
133
  end
110
134
 
135
+ def ==(other)
136
+ if key_values.any? { |value| value.nil? }
137
+ super
138
+ else
139
+ self.class == other.class && key_values == other.key_values
140
+ end
141
+ end
142
+
143
+ def inspect
144
+ inspected_attributes = attributes.each_pair.map do |attr, value|
145
+ inspected_value = value.is_a?(CassandraCQL::UUID) ?
146
+ value.to_guid :
147
+ value.inspect
148
+ "#{attr}: #{inspected_value}"
149
+ end
150
+ "#<#{self.class} #{inspected_attributes.join(", ")}>"
151
+ end
152
+
111
153
  protected
112
- delegate :table_schema, :to => 'self.class'
113
154
 
114
155
  def read_attribute(name)
115
156
  @attributes.fetch(name)
116
157
  rescue KeyError
117
- if table_schema.column(name)
158
+ if self.class.reflect_on_column(name)
118
159
  raise MissingAttributeError, "missing attribute: #{name}"
119
160
  else
120
161
  raise UnknownAttributeError, "unknown attribute: #{name}"
@@ -122,7 +163,7 @@ module Cequel
122
163
  end
123
164
 
124
165
  def write_attribute(name, value)
125
- column = table_schema.column(name)
166
+ column = self.class.reflect_on_column(name)
126
167
  raise UnknownAttributeError,
127
168
  "unknown attribute: #{name}" unless column
128
169
  @attributes[name] = value.nil? ? nil : column.cast(value)
@@ -138,6 +179,19 @@ module Cequel
138
179
  collection_proxies.delete(name)
139
180
  end
140
181
 
182
+ def initialize_new_record(attributes = {})
183
+ dynamic_defaults = default_attributes.
184
+ select { |name, value| value.is_a?(Proc) }
185
+ @attributes = Marshal.load(Marshal.dump(
186
+ default_attributes.except(*dynamic_defaults.keys)))
187
+ dynamic_defaults.each { |name, p| @attributes[name] = p.() }
188
+ @new_record = true
189
+ yield self if block_given?
190
+ self.attributes = attributes
191
+ loaded!
192
+ self
193
+ end
194
+
141
195
  end
142
196
 
143
197
  end
@@ -1,10 +1,10 @@
1
1
  module Cequel
2
2
 
3
- module Model
3
+ module Record
4
4
 
5
5
  class Railtie < Rails::Railtie
6
6
 
7
- config.cequel = Cequel::Model::Base
7
+ config.cequel = Record
8
8
 
9
9
  initializer "cequel.configure_rails" do
10
10
  app_name = Rails.application.railtie_name.sub(/_application$/, '')
@@ -31,7 +31,7 @@ module Cequel
31
31
  retry
32
32
  end
33
33
  connection.logger = Rails.logger
34
- Cequel::Model::Base.connection = connection
34
+ Record.connection = connection
35
35
  end
36
36
  end
37
37
 
@@ -1,8 +1,8 @@
1
1
  module Cequel
2
2
 
3
- module Model
3
+ module Record
4
4
 
5
- class RecordSet
5
+ class RecordSet < SimpleDelegator
6
6
 
7
7
  extend Forwardable
8
8
  extend Cequel::Util::HashAccessors
@@ -14,17 +14,10 @@ module Cequel
14
14
  {:scoped_key_values => [], :select_columns => []}
15
15
  end
16
16
 
17
- def self.create(clazz, attributes = {})
18
- attributes = default_attributes.merge!(attributes)
19
- if attributes[:scoped_key_values].length >= clazz.partition_key_columns.length
20
- SinglePartitionRecordSet.new(clazz, attributes)
21
- else
22
- RecordSet.new(clazz, attributes)
23
- end
24
- end
25
-
26
- def initialize(clazz, attributes)
17
+ def initialize(clazz, attributes = {})
18
+ attributes = self.class.default_attributes.merge!(attributes)
27
19
  @clazz, @attributes = clazz, attributes
20
+ super(clazz)
28
21
  end
29
22
 
30
23
  def all
@@ -95,10 +88,40 @@ module Cequel
95
88
  )
96
89
  end
97
90
 
91
+ def from(start_key)
92
+ unless single_partition?
93
+ raise IllegalQuery,
94
+ "Can't construct exclusive range on partition key #{range_key_name}"
95
+ end
96
+ scoped(lower_bound: Bound.new(start_key, true))
97
+ end
98
+
99
+ def upto(end_key)
100
+ unless single_partition?
101
+ raise IllegalQuery,
102
+ "Can't construct exclusive range on partition key #{range_key_name}"
103
+ end
104
+ scoped(upper_bound: Bound.new(end_key, true))
105
+ end
106
+
107
+ def reverse
108
+ unless single_partition?
109
+ raise IllegalQuery,
110
+ "Can't reverse without scoping to partition key #{range_key_name}"
111
+ end
112
+ scoped(reversed: !reversed?)
113
+ end
114
+
98
115
  def first(count = nil)
99
116
  count ? limit(count).entries : limit(1).each.first
100
117
  end
101
118
 
119
+ def last(count = nil)
120
+ reverse.first(count).tap do |results|
121
+ results.reverse! if count
122
+ end
123
+ end
124
+
102
125
  def count
103
126
  data_set.count
104
127
  end
@@ -139,13 +162,27 @@ module Cequel
139
162
  @data_set ||= construct_data_set
140
163
  end
141
164
 
165
+ def scoped_key_attributes
166
+ Hash[scoped_key_columns.map { |col| col.name }.zip(scoped_key_values)]
167
+ end
168
+
169
+ def_delegators :entries, :inspect
170
+
171
+ def ==(other)
172
+ entries == other.to_a
173
+ end
174
+
142
175
  protected
143
176
  attr_reader :attributes
144
177
  hattr_reader :attributes, :select_columns, :scoped_key_values, :row_limit,
145
178
  :lower_bound, :upper_bound, :scoped_indexed_column
179
+ protected :select_columns, :scoped_key_values, :row_limit, :lower_bound,
180
+ :upper_bound, :scoped_indexed_column
181
+ hattr_inquirer :attributes, :reversed
182
+ protected :reversed?
146
183
 
147
184
  def next_batch_from(row)
148
- after(row[range_key_name])
185
+ reversed? ? before(row[range_key_name]) : after(row[range_key_name])
149
186
  end
150
187
 
151
188
  def find_nested_batches_from(row, options, &block)
@@ -167,12 +204,12 @@ module Cequel
167
204
  end
168
205
  end
169
206
 
170
- def range_key
207
+ def range_key_column
171
208
  clazz.key_columns[scoped_key_values.length]
172
209
  end
173
210
 
174
211
  def range_key_name
175
- range_key.name
212
+ range_key_column.name
176
213
  end
177
214
 
178
215
  def scoped_key_columns
@@ -183,9 +220,18 @@ module Cequel
183
220
  scoped_key_columns.map { |column| column.name }
184
221
  end
185
222
 
223
+ def single_partition?
224
+ scoped_key_values.length >= clazz.partition_key_columns.length
225
+ end
226
+
186
227
  private
187
228
  attr_reader :clazz
188
229
  def_delegators :clazz, :connection
230
+ private :connection
231
+
232
+ def method_missing(method, *args, &block)
233
+ clazz.with_scope(self) { super }
234
+ end
189
235
 
190
236
  def next_key_column
191
237
  clazz.key_columns[scoped_key_values.length + 1]
@@ -211,60 +257,24 @@ module Cequel
211
257
  fragment = construct_bound_fragment(upper_bound, '<')
212
258
  data_set = data_set.where(fragment, upper_bound.value)
213
259
  end
260
+ data_set = data_set.order(range_key_name => :desc) if reversed?
214
261
  data_set = data_set.where(scoped_indexed_column) if scoped_indexed_column
215
262
  data_set
216
263
  end
217
264
 
218
265
  def construct_bound_fragment(bound, base_operator)
219
266
  operator = bound.inclusive ? "#{base_operator}=" : base_operator
220
- "TOKEN(#{range_key_name}) #{operator} TOKEN(?)"
267
+ single_partition? ?
268
+ "#{range_key_name} #{operator} ?" :
269
+ "TOKEN(#{range_key_name}) #{operator} TOKEN(?)"
270
+
221
271
  end
222
272
 
223
273
  def scoped(new_attributes = {}, &block)
224
274
  attributes_copy = Marshal.load(Marshal.dump(attributes))
225
275
  attributes_copy.merge!(new_attributes)
226
276
  attributes_copy.tap(&block) if block
227
- RecordSet.create(clazz, attributes_copy)
228
- end
229
-
230
- end
231
-
232
- class SinglePartitionRecordSet < RecordSet
233
-
234
- hattr_inquirer :attributes, :reversed
235
-
236
- def from(start_key)
237
- scoped(lower_bound: Bound.new(start_key, true))
238
- end
239
-
240
- def upto(end_key)
241
- scoped(upper_bound: Bound.new(end_key, true))
242
- end
243
-
244
- def reverse
245
- scoped(reversed: !reversed?)
246
- end
247
-
248
- def last
249
- reverse.first
250
- end
251
-
252
- # @api private
253
- def next_batch_from(row)
254
- reversed? ? before(row[range_key_name]) : super
255
- end
256
-
257
- protected
258
-
259
- def construct_data_set
260
- data_set = super
261
- data_set = data_set.order(range_key_name => :desc) if reversed?
262
- data_set
263
- end
264
-
265
- def construct_bound_fragment(bound, base_operator)
266
- operator = bound.inclusive ? "#{base_operator}=" : base_operator
267
- "#{range_key_name} #{operator} ?"
277
+ RecordSet.new(clazz, attributes_copy)
268
278
  end
269
279
 
270
280
  end