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

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