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
@@ -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?