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

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 (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