cequel 1.0.0.pre.3 → 1.0.0.pre.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2f70439ec577904a2fbe7a6c8f8689f45636e800
4
- data.tar.gz: b0ede82f2cdafba89f308d1f744252803d824c56
3
+ metadata.gz: af518e804a2ce5cf3e021b7a2ba92302011c0cd4
4
+ data.tar.gz: dadafdee945a8c961ac58fcf29ed3a76f84e616e
5
5
  SHA512:
6
- metadata.gz: d5d46692ee7904443fb60418ff003d1c04c14f7b947b1685e08ba73945839028e09371b81368532881f3f230160314d946db1238d1652077cb6d4a11d6ffe52e
7
- data.tar.gz: 7ac1535e9338974c0b8e545c7715810b763c8f02f3e98225c94bef6bf3502287c33fcd67c422e272371248cd7d22cdc12a71cc1a24b6305f7ae48f496670ebfa
6
+ metadata.gz: f90d210ad3a9ab787fcd6e70bba0e950f5983ade9d41f5f17f160b91eab470a84e4f5ca4f828be938db04ccb4bafda1f1be5fba1ea2db75667a2a82ee0d6043b
7
+ data.tar.gz: 22436304a692c25bd34ce86b58d317630b81af527a28234e3d4bc4884617699cb0fc1092138e7c1a54867059f8118b183e8c4263bef175f735041948a5a53a93
@@ -32,11 +32,23 @@ module Cequel
32
32
  def_parent_association_accessors
33
33
  end
34
34
 
35
- def has_many(name)
35
+ def has_many(name, options = {})
36
+ options.assert_valid_keys(:dependent)
37
+
36
38
  association = HasManyAssociation.new(self, name.to_sym)
37
39
  self.child_associations =
38
40
  child_associations.merge(name => association)
39
41
  def_child_association_reader(association)
42
+
43
+ case options[:dependent]
44
+ when :destroy
45
+ after_destroy { delete_children(name, true) }
46
+ when :delete
47
+ after_destroy { delete_children(name) }
48
+ when nil
49
+ else
50
+ raise ArgumentError, "Invalid option #{options[:dependent].inspect} provided for :dependent. Specify :destroy or :delete."
51
+ end
40
52
  end
41
53
 
42
54
  private
@@ -117,6 +129,17 @@ module Cequel
117
129
  ivar, AssociationCollection.new(association_record_set))
118
130
  end
119
131
 
132
+ def delete_children(association_name, run_callbacks = false)
133
+ if run_callbacks
134
+ self.send(association_name).each do |c|
135
+ c.run_callbacks(:destroy)
136
+ end
137
+ end
138
+ connection[association_name].where(
139
+ send(association_name).scoped_key_attributes
140
+ ).delete
141
+ end
142
+
120
143
  end
121
144
 
122
145
  end
@@ -16,7 +16,7 @@ module Cequel
16
16
  extend Forwardable
17
17
 
18
18
  def_delegators :table_schema, :key_columns, :key_column_names,
19
- :partition_key_columns, :clustering_columns
19
+ :partition_key_columns, :clustering_columns, :compact_storage?
20
20
  def_delegator :table_schema, :column, :reflect_on_column
21
21
 
22
22
  def synchronize_schema
@@ -63,6 +63,10 @@ module Cequel
63
63
  table_schema.add_property(name, value)
64
64
  end
65
65
 
66
+ def compact_storage
67
+ table_schema.compact_storage = true
68
+ end
69
+
66
70
  end
67
71
 
68
72
  protected
@@ -56,6 +56,8 @@ module Cequel
56
56
  comparators = parse_composite_types(table_data['comparator'])
57
57
  unless comparators
58
58
  table.compact_storage = true
59
+ return unless column_data.empty?
60
+ column_aliases << nil if column_aliases.empty?
59
61
  comparators = [table_data['comparator']]
60
62
  end
61
63
  column_aliases.zip(comparators) do |column_alias, type|
@@ -64,7 +66,7 @@ module Cequel
64
66
  clustering_order = :desc
65
67
  end
66
68
  table.add_clustering_column(
67
- column_alias.to_sym,
69
+ column_alias.try(:to_sym),
68
70
  Type.lookup_internal(type),
69
71
  clustering_order
70
72
  )
@@ -72,19 +74,27 @@ module Cequel
72
74
  end
73
75
 
74
76
  def read_data_columns
75
- column_data.each do |result|
76
- if COLLECTION_TYPE_PATTERN =~ result['validator']
77
- read_collection_column(
78
- result['column_name'],
79
- $1.underscore,
80
- *$2.split(',')
81
- )
82
- else
83
- table.add_data_column(
84
- result['column_name'].to_sym,
85
- Type.lookup_internal(result['validator']),
86
- result['index_name'].try(:to_sym)
87
- )
77
+ if column_data.empty?
78
+ table.add_data_column(
79
+ table_data['value_alias'],
80
+ Type.lookup_internal(table_data['default_validator']),
81
+ false
82
+ )
83
+ else
84
+ column_data.each do |result|
85
+ if COLLECTION_TYPE_PATTERN =~ result['validator']
86
+ read_collection_column(
87
+ result['column_name'],
88
+ $1.underscore,
89
+ *$2.split(',')
90
+ )
91
+ else
92
+ table.add_data_column(
93
+ result['column_name'].to_sym,
94
+ Type.lookup_internal(result['validator']),
95
+ result['index_name'].try(:to_sym)
96
+ )
97
+ end
88
98
  end
89
99
  end
90
100
  end
@@ -37,7 +37,7 @@ module Cequel
37
37
  "Can't change type of key column #{old_key.name} from #{old_key.type} to #{new_key.type}"
38
38
  end
39
39
  if old_key.name != new_key.name
40
- updater.rename_column(old_key.name, new_key.name)
40
+ updater.rename_column(old_key.name || :column1, new_key.name)
41
41
  end
42
42
  end
43
43
  end
@@ -64,6 +64,9 @@ module Cequel
64
64
  end
65
65
 
66
66
  def update_column(old_column, new_column)
67
+ if old_column.name != new_column.name
68
+ updater.rename_column(old_column.name || :value, new_column.name)
69
+ end
67
70
  if old_column.type != new_column.type
68
71
  updater.change_column(new_column.name, new_column.type)
69
72
  end
@@ -99,11 +102,15 @@ module Cequel
99
102
  end
100
103
 
101
104
  def each_column_pair(&block)
102
- old_columns = existing.data_columns.index_by { |col| col.name }
103
- new_columns = updated.data_columns.index_by { |col| col.name }
104
- all_column_names = (old_columns.keys + new_columns.keys).uniq!
105
- all_column_names.each do |name|
106
- yield old_columns[name], new_columns[name]
105
+ if existing.compact_storage? && existing.clustering_columns.any?
106
+ yield existing.data_columns.first, updated.data_columns.first
107
+ else
108
+ old_columns = existing.data_columns.index_by { |col| col.name }
109
+ new_columns = updated.data_columns.index_by { |col| col.name }
110
+ all_column_names = (old_columns.keys + new_columns.keys).tap(&:uniq!)
111
+ all_column_names.each do |name|
112
+ yield old_columns[name], new_columns[name]
113
+ end
107
114
  end
108
115
  end
109
116
 
@@ -41,7 +41,7 @@ module Cequel
41
41
  end
42
42
 
43
43
  def rename_column(old_name, new_name)
44
- alter_table("RENAME #{old_name} TO #{new_name}")
44
+ alter_table(%(RENAME "#{old_name}" TO "#{new_name}"))
45
45
  end
46
46
 
47
47
  def change_properties(options)
@@ -1,3 +1,3 @@
1
1
  module Cequel
2
- VERSION = '1.0.0.pre.3'
2
+ VERSION = '1.0.0.pre.4'
3
3
  end
@@ -18,6 +18,21 @@ describe Cequel::Record::Associations do
18
18
  belongs_to :blog
19
19
  key :id, :uuid, auto: true
20
20
  column :title, :text
21
+
22
+ has_many :comments, dependent: :destroy
23
+ has_many :attachments, dependent: :delete
24
+ end
25
+
26
+ model :Comment do
27
+ belongs_to :post
28
+ key :id, :uuid, auto: true
29
+ column :content, :text
30
+ end
31
+
32
+ model :Attachment do
33
+ belongs_to :post
34
+ key :id, :uuid, auto: true
35
+ column :caption, :text
21
36
  end
22
37
 
23
38
  describe '::belongs_to' do
@@ -114,6 +129,79 @@ describe Cequel::Record::Associations do
114
129
  posts.first.destroy
115
130
  blog.posts(true).map(&:title).should == ['Post 1', 'Post 2']
116
131
  end
117
- end
118
132
 
133
+ it "does not allow invalid :dependent options" do
134
+ expect {
135
+ Post.class_eval do
136
+ has_many :users, dependent: :bar
137
+ end
138
+ }.to raise_error(ArgumentError)
139
+ end
140
+
141
+ it "does not allow unrecognized options" do
142
+ expect {
143
+ Post.class_eval do
144
+ has_many :users, bogus: :buffalo
145
+ end
146
+ }.to raise_error(ArgumentError)
147
+ end
148
+
149
+ context "with dependent => destroy" do
150
+ let(:post_with_comments) { posts.first }
151
+
152
+ before :each do
153
+ 2.times.map do |i|
154
+ Comment.new do |comment|
155
+ comment.content = "cat #{i} is awesome"
156
+ comment.post = post_with_comments
157
+ end.tap(&:save)
158
+ end
159
+ @callback_count = 0
160
+ Comment.any_instance.stub(:run_callbacks).with(:destroy) do
161
+ @callback_count += 1
162
+ end
163
+ end
164
+
165
+ it "deletes all children when destroying the parent" do
166
+ expect {
167
+ post_with_comments.destroy
168
+ }.to change { Comment.count }.by(-2)
169
+ end
170
+
171
+ it "executes :destroy callbacks on the children" do
172
+ expect {
173
+ post_with_comments.destroy
174
+ }.to change { @callback_count }.by(2)
175
+ end
176
+ end
177
+
178
+ context "with dependent => delete" do
179
+ let(:post_with_attachments) { posts.first }
180
+
181
+ before :each do
182
+ 2.times.map do |i|
183
+ Attachment.new do |comment|
184
+ comment.caption = "cat #{i} is awesome"
185
+ comment.post = post_with_attachments
186
+ end.tap(&:save)
187
+ end
188
+ @callback_count = 0
189
+ Attachment.any_instance.stub(:run_callbacks).with(:destroy) do
190
+ @callback_count += 1
191
+ end
192
+ end
193
+
194
+ it "deletes all children when destroying the parent" do
195
+ expect {
196
+ post_with_attachments.destroy
197
+ }.to change { Attachment.count }.by(-2)
198
+ end
199
+
200
+ it "executes :destroy callbacks on the children" do
201
+ expect {
202
+ post_with_attachments.destroy
203
+ }.to change { @callback_count }.by(0)
204
+ end
205
+ end
206
+ end
119
207
  end
@@ -1,45 +1,88 @@
1
1
  require File.expand_path('../spec_helper', __FILE__)
2
2
 
3
3
  describe Cequel::Record::Schema do
4
- after { cequel.schema.drop_table(:posts) }
5
- subject { cequel.schema.read_table(:posts) }
6
-
7
- let(:model) do
8
- Class.new do
9
- include Cequel::Record
10
- self.table_name = 'posts'
11
-
12
- key :permalink, :text
13
- column :title, :text
14
- list :categories, :text
15
- set :tags, :text
16
- map :trackbacks, :timestamp, :text
17
- table_property :comment, 'Blog Posts'
18
- end
19
- end
20
-
21
- context 'new model with simple primary key' do
22
- before { model.synchronize_schema }
4
+ context 'CQL3 table' do
5
+ after { cequel.schema.drop_table(:posts) }
6
+ subject { cequel.schema.read_table(:posts) }
23
7
 
24
- its(:partition_key_columns) { should == [Cequel::Schema::Column.new(:permalink, :text)] }
25
- its(:data_columns) { should include(Cequel::Schema::Column.new(:title, :text)) }
26
- its(:data_columns) { should include(Cequel::Schema::List.new(:categories, :text)) }
27
- its(:data_columns) { should include(Cequel::Schema::Set.new(:tags, :text)) }
28
- its(:data_columns) { should include(Cequel::Schema::Map.new(:trackbacks, :timestamp, :text)) }
29
- specify { subject.property(:comment).should == 'Blog Posts' }
30
- end
8
+ let(:model) do
9
+ Class.new do
10
+ include Cequel::Record
11
+ self.table_name = 'posts'
31
12
 
32
- context 'existing model with additional attribute' do
33
- before do
34
- cequel.schema.create_table :posts do
35
13
  key :permalink, :text
36
14
  column :title, :text
37
15
  list :categories, :text
38
16
  set :tags, :text
17
+ map :trackbacks, :timestamp, :text
18
+ table_property :comment, 'Blog Posts'
19
+ end
20
+ end
21
+
22
+ context 'new model with simple primary key' do
23
+ before { model.synchronize_schema }
24
+
25
+ its(:partition_key_columns) { should == [Cequel::Schema::Column.new(:permalink, :text)] }
26
+ its(:data_columns) { should include(Cequel::Schema::Column.new(:title, :text)) }
27
+ its(:data_columns) { should include(Cequel::Schema::List.new(:categories, :text)) }
28
+ its(:data_columns) { should include(Cequel::Schema::Set.new(:tags, :text)) }
29
+ its(:data_columns) { should include(Cequel::Schema::Map.new(:trackbacks, :timestamp, :text)) }
30
+ specify { subject.property(:comment).should == 'Blog Posts' }
31
+ end
32
+
33
+ context 'existing model with additional attribute' do
34
+ before do
35
+ cequel.schema.create_table :posts do
36
+ key :permalink, :text
37
+ column :title, :text
38
+ list :categories, :text
39
+ set :tags, :text
40
+ end
41
+ model.synchronize_schema
39
42
  end
40
- model.synchronize_schema
43
+
44
+ its(:data_columns) { should include(Cequel::Schema::Map.new(:trackbacks, :timestamp, :text)) }
41
45
  end
46
+ end
42
47
 
43
- its(:data_columns) { should include(Cequel::Schema::Map.new(:trackbacks, :timestamp, :text)) }
48
+ context 'wide-row legacy table' do
49
+ let(:legacy_model) do
50
+ Class.new do
51
+ include Cequel::Record
52
+ self.table_name = 'legacy_posts'
53
+
54
+ key :blog_subdomain, :text
55
+ key :id, :uuid
56
+ column :data, :text
57
+
58
+ compact_storage
59
+ end
60
+ end
61
+ after { cequel.schema.drop_table(:legacy_posts) }
62
+ subject { cequel.schema.read_table(:legacy_posts) }
63
+
64
+ context 'new model' do
65
+ before { legacy_model.synchronize_schema }
66
+
67
+ its(:partition_key_columns) { should == [Cequel::Schema::Column.new(:blog_subdomain, :text)] }
68
+ its(:clustering_columns) { should == [Cequel::Schema::Column.new(:id, :uuid)] }
69
+ it { should be_compact_storage }
70
+ its(:data_columns) { should == [Cequel::Schema::Column.new(:data, :text)] }
71
+ end
72
+
73
+ context 'existing model' do
74
+ before do
75
+ legacy_connection.execute(<<-CQL2)
76
+ CREATE COLUMNFAMILY legacy_posts (blog_subdomain text PRIMARY KEY)
77
+ WITH comparator=uuid AND default_validation=text
78
+ CQL2
79
+ legacy_model.synchronize_schema
80
+ end
81
+
82
+ its(:partition_key_columns) { should == [Cequel::Schema::Column.new(:blog_subdomain, :text)] }
83
+ its(:clustering_columns) { should == [Cequel::Schema::Column.new(:id, :uuid)] }
84
+ it { should be_compact_storage }
85
+ its(:data_columns) { should == [Cequel::Schema::Column.new(:data, :text)] }
86
+ end
44
87
  end
45
88
  end
@@ -304,25 +304,86 @@ describe Cequel::Schema::TableReader do
304
304
  table.property(:compression)[:crc_check_chance].should == 0.5
305
305
  end
306
306
 
307
- end # describe 'reading storage properties'
307
+ it 'should recognize no compact storage' do
308
+ table.should_not be_compact_storage
309
+ end
308
310
 
309
- describe 'compact storage' do
311
+ end # describe 'reading storage properties'
310
312
 
311
- it 'should read non-compact storage status' do
313
+ describe 'skinny-row compact storage' do
314
+ before do
312
315
  cequel.execute <<-CQL
313
- CREATE TABLE posts (permalink text PRIMARY KEY, body text)
316
+ CREATE TABLE posts (permalink text PRIMARY KEY, title text, body text)
317
+ WITH COMPACT STORAGE
314
318
  CQL
315
- table.should_not be_compact_storage
316
319
  end
320
+ subject { table }
321
+
322
+ it { should be_compact_storage }
323
+ its(:partition_key_columns) { should ==
324
+ [Cequel::Schema::PartitionKey.new(:permalink, :text)] }
325
+ its(:clustering_columns) { should be_empty }
326
+ its(:data_columns) { should =~
327
+ [Cequel::Schema::DataColumn.new(:title, :text),
328
+ Cequel::Schema::DataColumn.new(:body, :text)] }
329
+ end
317
330
 
318
- it 'should read compact storage status' do
331
+ describe 'wide-row compact storage' do
332
+ before do
319
333
  cequel.execute <<-CQL
320
- CREATE TABLE posts (permalink text PRIMARY KEY, body text)
334
+ CREATE TABLE posts (
335
+ blog_subdomain text,
336
+ id uuid,
337
+ data text,
338
+ PRIMARY KEY (blog_subdomain, id)
339
+ )
321
340
  WITH COMPACT STORAGE
322
341
  CQL
323
- table.should be_compact_storage
324
342
  end
343
+ subject { table }
344
+
345
+ it { should be_compact_storage }
346
+ its(:partition_key_columns) { should ==
347
+ [Cequel::Schema::PartitionKey.new(:blog_subdomain, :text)] }
348
+ its(:clustering_columns) { should ==
349
+ [Cequel::Schema::ClusteringColumn.new(:id, :uuid)] }
350
+ its(:data_columns) { should ==
351
+ [Cequel::Schema::DataColumn.new(:data, :text)] }
352
+ end
325
353
 
354
+ describe 'skinny-row legacy table' do
355
+ before do
356
+ legacy_connection.execute <<-CQL
357
+ CREATE TABLE posts (permalink text PRIMARY KEY, title text, body text)
358
+ CQL
359
+ end
360
+ subject { table }
361
+
362
+ it { should be_compact_storage }
363
+ its(:partition_key_columns) { should ==
364
+ [Cequel::Schema::PartitionKey.new(:permalink, :text)] }
365
+ its(:clustering_columns) { should be_empty }
366
+ its(:data_columns) { should =~
367
+ [Cequel::Schema::DataColumn.new(:title, :text),
368
+ Cequel::Schema::DataColumn.new(:body, :text)] }
369
+ end
370
+
371
+ describe 'wide-row legacy table' do
372
+ before do
373
+ legacy_connection.execute(<<-CQL2)
374
+ CREATE COLUMNFAMILY posts (blog_subdomain text PRIMARY KEY)
375
+ WITH comparator=uuid AND default_validation=text
376
+ CQL2
377
+ end
378
+ subject { table }
379
+
380
+ it { should be_compact_storage }
381
+ its(:partition_key_columns) { should ==
382
+ [Cequel::Schema::PartitionKey.new(:blog_subdomain, :text)] }
383
+ its(:clustering_columns) { should ==
384
+ [Cequel::Schema::ClusteringColumn.new(nil, :uuid)] }
385
+ its(:data_columns) { should ==
386
+ [Cequel::Schema::DataColumn.new(nil, :text)] }
326
387
  end
327
388
 
328
389
  end
@@ -52,8 +52,9 @@ module Cequel
52
52
 
53
53
  def self.cequel
54
54
  @cequel ||= Cequel.connect(
55
- :host => host,
56
- :keyspace => keyspace_name
55
+ host: host,
56
+ keyspace: keyspace_name,
57
+ thrift: {retries: 5, cached_connections: true}
57
58
  ).tap do |cequel|
58
59
  cequel.logger = Logger.new(STDOUT) if ENV['CEQUEL_LOG_QUERIES']
59
60
  end
@@ -67,10 +68,22 @@ module Cequel
67
68
  ENV['CEQUEL_TEST_KEYSPACE'] || 'cequel_test'
68
69
  end
69
70
 
71
+ def self.legacy_connection
72
+ @legacy_connection ||= CassandraCQL::Database.new(
73
+ Cequel::SpecSupport::Helpers.host,
74
+ :keyspace => Cequel::SpecSupport::Helpers.keyspace_name,
75
+ :cql_version => '2.0.0'
76
+ )
77
+ end
78
+
70
79
  def cequel
71
80
  Helpers.cequel
72
81
  end
73
82
 
83
+ def legacy_connection
84
+ Helpers.legacy_connection
85
+ end
86
+
74
87
  def max_statements!(number)
75
88
  cequel.should_receive(:execute).at_most(number).times.and_call_original
76
89
  end
metadata CHANGED
@@ -1,17 +1,18 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cequel
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.3
4
+ version: 1.0.0.pre.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mat Brown
8
8
  - Aubrey Holland
9
9
  - Keenan Brock
10
10
  - Insoo Buzz Jung
11
+ - Louis Simoneau
11
12
  autorequire:
12
13
  bindir: bin
13
14
  cert_chain: []
14
- date: 2013-09-26 00:00:00.000000000 Z
15
+ date: 2013-09-30 00:00:00.000000000 Z
15
16
  dependencies:
16
17
  - !ruby/object:Gem::Dependency
17
18
  name: activesupport