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
@@ -4,27 +4,34 @@ module Cequel
4
4
 
5
5
  class Railtie < Rails::Railtie
6
6
 
7
- config.cequel = Cequel::Model
7
+ config.cequel = Cequel::Model::Base
8
8
 
9
9
  initializer "cequel.configure_rails" do
10
+ app_name = Rails.application.railtie_name.sub(/_application$/, '')
10
11
  config_path = Rails.root.join('config/cequel.yml').to_s
11
12
 
12
13
  if File.exist?(config_path)
13
- yaml = YAML::load(ERB.new(IO.read(config_path)).result)[Rails.env]
14
- Cequel::Model.configure(yaml.symbolize_keys) if yaml
14
+ config = YAML::load(ERB.new(IO.read(config_path)).result)[Rails.env].
15
+ deep_symbolize_keys
16
+ else
17
+ config = {host: '127.0.0.1:9160'}
15
18
  end
16
-
17
- Cequel::Model.logger = Rails.logger
18
- end
19
-
20
- initializer "cequel.instantiate_observers" do
21
- config.after_initialize do
22
- ::Cequel::Model.instantiate_observers
23
-
24
- ActionDispatch::Callbacks.to_prepare do
25
- ::Cequel::Model.instantiate_observers
26
- end
19
+ config.reverse_merge!(keyspace: "#{app_name}_#{Rails.env}")
20
+ connection = Cequel.connect(config)
21
+
22
+ begin
23
+ connection = Cequel.connect(config)
24
+ rescue CassandraCQL::Error::InvalidRequestException
25
+ connection = Cequel.connect(config.except(:keyspace))
26
+ #XXX This should be read from the configuration
27
+ connection.execute(<<-CQL)
28
+ CREATE KEYSPACE #{keyspace}
29
+ WITH REPLICATION = {'class': 'SimpleStrategy', 'replication_factor': 1}
30
+ CQL
31
+ retry
27
32
  end
33
+ connection.logger = Rails.logger
34
+ Cequel::Model::Base.connection = connection
28
35
  end
29
36
  end
30
37
 
@@ -0,0 +1,285 @@
1
+ module Cequel
2
+
3
+ module Model
4
+
5
+ class RecordSet
6
+
7
+ extend Forwardable
8
+ include Enumerable
9
+
10
+ Bound = Struct.new(:value, :inclusive)
11
+
12
+ def initialize(clazz)
13
+ @clazz = clazz
14
+ @select_columns = []
15
+ @scoped_key_values = []
16
+ end
17
+
18
+ def all
19
+ self
20
+ end
21
+
22
+ def select(*columns)
23
+ return super if block_given?
24
+ scoped { |record_set| record_set.select_columns.concat(columns) }
25
+ end
26
+
27
+ def limit(count)
28
+ scoped { |record_set| record_set.row_limit = count }
29
+ end
30
+
31
+ def at(*scoped_key_values)
32
+ record_set_class = next_key_column.partition_key? ?
33
+ RecordSet : SortableRecordSet
34
+ scoped(record_set_class) do |record_set|
35
+ record_set.scoped_key_values.concat(scoped_key_values)
36
+ end
37
+ end
38
+
39
+ def [](scoped_key_value)
40
+ if next_key_column
41
+ at(scoped_key_value)
42
+ else
43
+ attributes = {}
44
+ key_values = [*scoped_key_values, scoped_key_value]
45
+ clazz.key_column_names.zip(key_values) do |key_name, key_value|
46
+ attributes[key_name] = key_value
47
+ end
48
+ clazz.new_empty { @attributes = attributes }
49
+ end
50
+ end
51
+
52
+ def find(*scoped_key_values)
53
+ self[*scoped_key_values].load!
54
+ end
55
+
56
+ def /(scoped_key_value)
57
+ at(scoped_key_value)
58
+ end
59
+
60
+ def after(start_key)
61
+ scoped do |record_set|
62
+ record_set.lower_bound = Bound.new(start_key, false)
63
+ end
64
+ end
65
+
66
+ def before(end_key)
67
+ scoped do |record_set|
68
+ record_set.upper_bound = Bound.new(end_key, false)
69
+ end
70
+ end
71
+
72
+ def in(range)
73
+ scoped do |record_set|
74
+ record_set.lower_bound = Bound.new(range.first, true)
75
+ record_set.upper_bound = Bound.new(range.last, !range.exclude_end?)
76
+ end
77
+ end
78
+
79
+ def first(count = nil)
80
+ count ? limit(count).entries : limit(1).each.first
81
+ end
82
+
83
+ def count
84
+ data_set.count
85
+ end
86
+
87
+ def each(&block)
88
+ find_each(&block)
89
+ end
90
+
91
+ def find_each(options = {})
92
+ return enum_for(:find_each, options) unless block_given?
93
+ find_each_row(options) { |row| yield clazz.hydrate(row) }
94
+ end
95
+
96
+ def find_each_row(options = {}, &block)
97
+ return enum_for(:find_each_row, options) unless block
98
+ find_rows_in_batches(options) { |row| row.each(&block) }
99
+ end
100
+
101
+ def find_rows_in_batches(options = {}, &block)
102
+ return find_rows_in_single_batch(options, &block) if row_limit
103
+ batch_size = options.fetch(:batch_size, 1000)
104
+ batch_record_set = base_record_set = limit(batch_size)
105
+ more_results = true
106
+
107
+ while more_results
108
+ rows = batch_record_set.find_rows_in_single_batch
109
+ yield rows if rows.any?
110
+ more_results = rows.length == batch_size
111
+ last_row = rows.last
112
+ if more_results
113
+ find_nested_batches_from(last_row, options, &block)
114
+ batch_record_set = base_record_set.next_batch_from(last_row)
115
+ end
116
+ end
117
+ end
118
+
119
+ protected
120
+ attr_accessor :row_limit
121
+ attr_reader :select_columns, :scoped_key_values,
122
+ :lower_bound, :upper_bound
123
+
124
+ def reversed?
125
+ false
126
+ end
127
+
128
+ def lower_bound=(bound)
129
+ @lower_bound = bound
130
+ end
131
+
132
+ def upper_bound=(bound)
133
+ @upper_bound = bound
134
+ end
135
+
136
+ def data_set
137
+ @data_set ||= construct_data_set
138
+ end
139
+
140
+ def next_batch_from(row)
141
+ reversed? ? before(row[range_key_name]) : after(row[range_key_name])
142
+ end
143
+
144
+ def find_nested_batches_from(row, options, &block)
145
+ if next_key_column
146
+ at(row[range_key_name]).
147
+ next_batch_from(row).
148
+ find_rows_in_batches(options, &block)
149
+ end
150
+ end
151
+
152
+ def find_rows_in_single_batch(options = {})
153
+ if options.key?(:batch_size)
154
+ raise ArgumentError,
155
+ "Can't pass :batch_size argument with a limit in the scope"
156
+ else
157
+ data_set.entries.tap do |batch|
158
+ yield batch if batch.any? && block_given?
159
+ end
160
+ end
161
+ end
162
+
163
+ def range_key
164
+ clazz.key_columns[scoped_key_values.length]
165
+ end
166
+
167
+ def range_key_name
168
+ range_key.name
169
+ end
170
+
171
+ def scoped_key_columns
172
+ clazz.key_columns.first(scoped_key_values.length)
173
+ end
174
+
175
+ def scoped_key_names
176
+ scoped_key_columns.map { |column| column.name }
177
+ end
178
+
179
+ def chain_from(collection)
180
+ @select_columns = collection.select_columns.dup
181
+ @scoped_key_values = collection.scoped_key_values.dup
182
+ @lower_bound = collection.lower_bound
183
+ @upper_bound = collection.upper_bound
184
+ @row_limit = collection.row_limit
185
+ self
186
+ end
187
+
188
+ private
189
+ attr_reader :clazz
190
+ def_delegators :clazz, :connection
191
+
192
+ def scoped(record_set_class = self.class, &block)
193
+ record_set_class.new(clazz).chain_from(self).tap(&block)
194
+ end
195
+
196
+ def next_key_column
197
+ clazz.key_columns[scoped_key_values.length + 1]
198
+ end
199
+
200
+ def next_key_name
201
+ next_key_column.name if next_key_column
202
+ end
203
+
204
+ def construct_data_set
205
+ data_set = connection[clazz.table_name]
206
+ data_set = data_set.limit(row_limit) if row_limit
207
+ data_set = data_set.select(*select_columns) if select_columns
208
+ if scoped_key_values
209
+ key_conditions = Hash[scoped_key_names.zip(scoped_key_values)]
210
+ data_set = data_set.where(key_conditions)
211
+ end
212
+ if lower_bound
213
+ fragment = construct_bound_fragment(lower_bound, '>')
214
+ data_set = data_set.where(fragment, lower_bound.value)
215
+ end
216
+ if upper_bound
217
+ fragment = construct_bound_fragment(upper_bound, '<')
218
+ data_set = data_set.where(fragment, upper_bound.value)
219
+ end
220
+ data_set
221
+ end
222
+
223
+ def construct_bound_fragment(bound, base_operator)
224
+ operator = bound.inclusive ? "#{base_operator}=" : base_operator
225
+ "TOKEN(#{range_key_name}) #{operator} TOKEN(?)"
226
+ end
227
+
228
+ end
229
+
230
+ class SortableRecordSet < RecordSet
231
+
232
+ def initialize(clazz)
233
+ super
234
+ @reversed = false
235
+ end
236
+
237
+ def from(start_key)
238
+ scoped do |record_set|
239
+ record_set.lower_bound = Bound.new(start_key, true)
240
+ end
241
+ end
242
+
243
+ def upto(end_key)
244
+ scoped do |record_set|
245
+ record_set.upper_bound = Bound.new(end_key, true)
246
+ end
247
+ end
248
+
249
+ def reverse
250
+ scoped { |scope| scope.reversed = !reversed? }
251
+ end
252
+
253
+ def last
254
+ reverse.first
255
+ end
256
+
257
+ def chain_from(collection)
258
+ super
259
+ @reversed = collection.reversed?
260
+ self
261
+ end
262
+
263
+ protected
264
+ attr_writer :reversed
265
+
266
+ def construct_data_set
267
+ data_set = super
268
+ data_set = data_set.order(range_key_name => :desc) if reversed?
269
+ data_set
270
+ end
271
+
272
+ def reversed?
273
+ @reversed
274
+ end
275
+
276
+ def construct_bound_fragment(bound, base_operator)
277
+ operator = bound.inclusive ? "#{base_operator}=" : base_operator
278
+ "#{range_key_name} #{operator} ?"
279
+ end
280
+
281
+ end
282
+
283
+ end
284
+
285
+ end
@@ -0,0 +1,33 @@
1
+ module Cequel
2
+
3
+ module Model
4
+
5
+ module Schema
6
+
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+ extend Forwardable
11
+
12
+ def_delegators :table_schema, :key_columns, :key_column_names
13
+
14
+ def synchronize_schema
15
+ Cequel::Schema::TableSynchronizer.
16
+ apply(connection, read_schema, table_schema)
17
+ end
18
+
19
+ def read_schema
20
+ connection.schema.read_table(table_name)
21
+ end
22
+
23
+ def table_schema
24
+ @table_schema ||= Cequel::Schema::Table.new(table_name)
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+
33
+ end
@@ -4,55 +4,12 @@ module Cequel
4
4
 
5
5
  module Scoped
6
6
 
7
- extend ActiveSupport::Concern
8
-
9
- module ClassMethods
10
- delegate :consistency, :count, :first, :limit, :select, :where,
11
- :find_in_batches, :find_each, :find_rows_in_batches, :find_each_row,
12
- :to => :all
13
-
14
- def default_scope(scope)
15
- @_cequel.default_scope = scope
16
- end
17
-
18
- def all
19
- current_scope || @_cequel.default_scope || empty_scope
20
- end
21
-
22
- def select(*rows)
23
- all.select(*rows)
24
- end
25
-
26
- def with_scope(scope)
27
- @_cequel.synchronize do
28
- old_scope = current_scope
29
- begin
30
- self.current_scope = scope
31
- yield
32
- ensure
33
- self.current_scope = old_scope
34
- end
35
- end
36
- end
37
-
38
- private
39
-
40
- def empty_scope
41
- Scope.new(self, [column_family])
42
- end
43
-
44
- def current_scope
45
- ::Thread.current[current_scope_key]
46
- end
47
-
48
- def current_scope=(scope)
49
- ::Thread.current[current_scope_key] = scope
50
- end
51
-
52
- def current_scope_key
53
- :"cequel-current_scope-#{object_id}"
54
- end
7
+ extend Forwardable
55
8
 
9
+ def_delegators :current_scope, *RecordSet.instance_methods(false)
10
+
11
+ def current_scope
12
+ RecordSet.new(self)
56
13
  end
57
14
 
58
15
  end
@@ -1,5 +1,5 @@
1
1
  module Cequel
2
-
2
+
3
3
  module Model
4
4
 
5
5
  module Validations
@@ -8,35 +8,35 @@ module Cequel
8
8
 
9
9
  included do
10
10
  include ActiveModel::Validations
11
- alias_method_chain :valid?, :callbacks # XXX is there no better way?
11
+ define_model_callbacks :validation
12
+ alias_method_chain :valid?, :callbacks
12
13
  end
13
14
 
14
15
  module ClassMethods
15
16
 
16
- def create!(attributes = {}, &block)
17
- instance = new(attributes, &block)
18
- instance.save!
17
+ def create!(attributes, &block)
18
+ new(attributes, &block).save!
19
19
  end
20
20
 
21
21
  end
22
22
 
23
- def save(*args)
24
- if valid?
25
- super
26
- true
27
- else
28
- false
29
- end
23
+ def save(options = {})
24
+ validate = options.fetch(:validate, true)
25
+ options.delete(:validate)
26
+ (!validate || valid?) && super
30
27
  end
31
28
 
32
- def save!(*args)
33
- raise RecordInvalid, errors.full_messages.join("; ") unless save
34
- self
29
+ def save!(options = {})
30
+ tap do
31
+ unless save(options)
32
+ raise RecordInvalid, errors.full_messages.join("; ")
33
+ end
34
+ end
35
35
  end
36
36
 
37
- def update_attributes!(*args)
38
- raise RecordInvalid, errors.full_messages.join("; ") unless update_attributes(*args)
39
- self
37
+ def update_attributes!(attributes)
38
+ self.attributes = attributes
39
+ save!
40
40
  end
41
41
 
42
42
  def valid_with_callbacks?