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
@@ -0,0 +1,229 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Cequel::Model::Map do
4
+ model :Post do
5
+ key :permalink, :text
6
+ column :title, :text
7
+ map :likes, :text, :int
8
+ end
9
+
10
+ let(:scope) { cequel[:posts].where(:permalink => 'cequel') }
11
+ subject { scope.first }
12
+
13
+ let! :post do
14
+ Post.new do |post|
15
+ post.permalink = 'cequel'
16
+ post.likes = {'alice' => 1, 'bob' => 2}
17
+ end.tap(&:save)
18
+ end
19
+
20
+ let! :unloaded_post do
21
+ Post['cequel']
22
+ end
23
+
24
+ context 'new record' do
25
+ it 'should save set as-is' do
26
+ subject[:likes].should == {'alice' => 1, 'bob' => 2}
27
+ end
28
+ end
29
+
30
+ describe 'atomic modification' do
31
+ before { scope.map_update(:likes, 'charles' => 3) }
32
+
33
+ describe '#[]=' do
34
+ it 'should atomically update' do
35
+ post.likes['david'] = 4
36
+ post.save
37
+ subject[:likes].should ==
38
+ {'alice' => 1, 'bob' => 2, 'charles' => 3, 'david' => 4}
39
+ post.likes.should == {'alice' => 1, 'bob' => 2, 'david' => 4}
40
+ end
41
+
42
+ it 'should write without reading' do
43
+ max_statements! 2
44
+ unloaded_post.likes['david'] = 4
45
+ unloaded_post.save
46
+ subject[:likes].should ==
47
+ {'alice' => 1, 'bob' => 2, 'charles' => 3, 'david' => 4}
48
+ end
49
+
50
+ it 'should set key value post-hoc' do
51
+ unloaded_post.likes['david'] = 4
52
+ unloaded_post.likes.should ==
53
+ {'alice' => 1, 'bob' => 2, 'charles' => 3, 'david' => 4}
54
+ end
55
+ end
56
+
57
+ describe '#clear' do
58
+ it 'should atomically clear' do
59
+ post.likes.clear
60
+ post.save
61
+ subject[:likes].should be_blank
62
+ post.likes.should == {}
63
+ end
64
+
65
+ it 'should clear without reading' do
66
+ max_statements! 2
67
+ unloaded_post.likes.clear
68
+ unloaded_post.save
69
+ subject[:likes].should be_blank
70
+ end
71
+
72
+ it 'should clear post-hoc' do
73
+ unloaded_post.likes.clear
74
+ unloaded_post.likes.should be_blank
75
+ end
76
+ end
77
+
78
+ describe '#delete' do
79
+ it 'should delete element atomically' do
80
+ post.likes.delete('bob')
81
+ post.save
82
+ subject[:likes].should == {'alice' => 1, 'charles' => 3}
83
+ post.likes.should == {'alice' => 1}
84
+ end
85
+
86
+ it 'should delete without reading' do
87
+ max_statements! 2
88
+ unloaded_post.likes.delete('bob')
89
+ unloaded_post.save
90
+ subject[:likes].should == {'alice' => 1, 'charles' => 3}
91
+ end
92
+
93
+ it 'should delete post-hoc' do
94
+ unloaded_post.likes.delete('bob')
95
+ unloaded_post.likes.should == {'alice' => 1, 'charles' => 3}
96
+ end
97
+ end
98
+
99
+ describe '#merge!' do
100
+ it 'should atomically update' do
101
+ post.likes.merge!('david' => 4, 'emily' => 5)
102
+ post.save
103
+ subject[:likes].should ==
104
+ {'alice' => 1, 'bob' => 2, 'charles' => 3, 'david' => 4, 'emily' => 5}
105
+ post.likes.should ==
106
+ {'alice' => 1, 'bob' => 2, 'david' => 4, 'emily' => 5}
107
+ end
108
+
109
+ it 'should write without reading' do
110
+ max_statements! 2
111
+ unloaded_post.likes.merge!('david' => 4, 'emily' => 5)
112
+ unloaded_post.save
113
+ subject[:likes].should ==
114
+ {'alice' => 1, 'bob' => 2, 'charles' => 3, 'david' => 4, 'emily' => 5}
115
+ end
116
+
117
+ it 'should merge post-hoc' do
118
+ unloaded_post.likes.merge!('david' => 4, 'emily' => 5)
119
+ unloaded_post.likes.should ==
120
+ {'alice' => 1, 'bob' => 2, 'charles' => 3, 'david' => 4, 'emily' => 5}
121
+ end
122
+ end
123
+
124
+ describe '#replace' do
125
+ it 'should automatically overwrite' do
126
+ post.likes.replace('david' => 4, 'emily' => 5)
127
+ post.save
128
+ subject[:likes].should == {'david' => 4, 'emily' => 5}
129
+ post.likes.should == {'david' => 4, 'emily' => 5}
130
+ end
131
+
132
+ it 'should overwrite without reading' do
133
+ max_statements! 2
134
+ unloaded_post.likes.replace('david' => 4, 'emily' => 5)
135
+ unloaded_post.save
136
+ subject[:likes].should == {'david' => 4, 'emily' => 5}
137
+ end
138
+
139
+ it 'should replace post-hoc' do
140
+ unloaded_post.likes.replace('david' => 4, 'emily' => 5)
141
+ unloaded_post.likes.should == {'david' => 4, 'emily' => 5}
142
+ end
143
+ end
144
+
145
+ describe '#store' do
146
+ it 'should atomically update' do
147
+ post.likes.store('david', 4)
148
+ post.save
149
+ subject[:likes].should ==
150
+ {'alice' => 1, 'bob' => 2, 'charles' => 3, 'david' => 4}
151
+ post.likes.should == {'alice' => 1, 'bob' => 2, 'david' => 4}
152
+ end
153
+
154
+ it 'should write without reading' do
155
+ max_statements! 2
156
+ unloaded_post.likes.store('david', 4)
157
+ unloaded_post.save
158
+ subject[:likes].should ==
159
+ {'alice' => 1, 'bob' => 2, 'charles' => 3, 'david' => 4}
160
+ end
161
+
162
+ it 'should store post-hoc' do
163
+ unloaded_post.likes.store('david', 4)
164
+ unloaded_post.likes.should ==
165
+ {'alice' => 1, 'bob' => 2, 'charles' => 3, 'david' => 4}
166
+ end
167
+ end
168
+
169
+ describe '#update' do
170
+ it 'should atomically update' do
171
+ post.likes.update('david' => 4, 'emily' => 5)
172
+ post.save
173
+ subject[:likes].should ==
174
+ {'alice' => 1, 'bob' => 2, 'charles' => 3, 'david' => 4, 'emily' => 5}
175
+ post.likes.should ==
176
+ {'alice' => 1, 'bob' => 2, 'david' => 4, 'emily' => 5}
177
+ end
178
+
179
+ it 'should write without reading' do
180
+ max_statements! 2
181
+ unloaded_post.likes.update('david' => 4, 'emily' => 5)
182
+ unloaded_post.save
183
+ subject[:likes].should ==
184
+ {'alice' => 1, 'bob' => 2, 'charles' => 3, 'david' => 4, 'emily' => 5}
185
+ end
186
+
187
+ it 'should update post-hoc' do
188
+ unloaded_post.likes.update('david' => 4, 'emily' => 5)
189
+ unloaded_post.likes.should ==
190
+ {'alice' => 1, 'bob' => 2, 'charles' => 3, 'david' => 4, 'emily' => 5}
191
+ end
192
+ end
193
+
194
+ specify { expect { post.likes.default }.to raise_error(NoMethodError) }
195
+ specify { expect { post.likes.default = 1 }.to raise_error(NoMethodError) }
196
+ specify { expect { post.likes.default_proc }.to raise_error(NoMethodError) }
197
+ specify { expect { post.likes.default_proc = -> k, v { Time.now }}.
198
+ to raise_error(NoMethodError) }
199
+ specify { expect { post.likes.delete_if { |k, v| v.even? }}.
200
+ to raise_error(NoMethodError) }
201
+ specify { expect { post.likes.deep_merge!('alice' => 5) }.
202
+ to raise_error(NoMethodError) }
203
+ specify { expect { post.likes.except!('alice') }.
204
+ to raise_error(NoMethodError) }
205
+ specify { expect { post.likes.extract!('alice') }.
206
+ to raise_error(NoMethodError) }
207
+ specify { expect { post.likes.transform_keys!(&:upcase) }.
208
+ to raise_error(NoMethodError) }
209
+ specify { expect { post.likes.keep_if { |k, v| v.even? }}.
210
+ to raise_error(NoMethodError) }
211
+ specify { expect { post.likes.reject! { |k, v| v.even? }}.
212
+ to raise_error(NoMethodError) }
213
+ specify { expect { post.likes.reverse_merge!('alice' => 3) }.
214
+ to raise_error(NoMethodError) }
215
+ specify { expect { post.likes.reverse_update('alice' => 3) }.
216
+ to raise_error(NoMethodError) }
217
+ specify { expect { post.likes.select! { |k, v| v.even? }}.
218
+ to raise_error(NoMethodError) }
219
+ specify { expect { post.likes.shift }.to raise_error(NoMethodError) }
220
+ specify { expect { post.likes.stringify_keys! }.
221
+ to raise_error(NoMethodError) }
222
+ specify { expect { post.likes.symbolize_keys! }.
223
+ to raise_error(NoMethodError) }
224
+ specify { expect { post.likes.to_options! }.
225
+ to raise_error(NoMethodError) }
226
+ specify { expect { post.likes.slice!('alice') }.
227
+ to raise_error(NoMethodError) }
228
+ end
229
+ end
@@ -0,0 +1,55 @@
1
+ require_relative 'spec_helper'
2
+
3
+ describe Cequel::Model::MassAssignment do
4
+ context 'with strong parameters', :rails => '~> 4.0' do
5
+ model :Post do
6
+ key :permalink, :text
7
+ column :title, :text
8
+ end
9
+
10
+ it 'should allow assignment of vanilla hash' do
11
+ Post.new(:title => 'Cequel').title.should == 'Cequel'
12
+ end
13
+
14
+ it 'should allow assignment of permitted strong params' do
15
+ Post.new(StrongParams.new(true, :title => 'Cequel')).title.
16
+ should == 'Cequel'
17
+ end
18
+
19
+ it 'should raise exception when assigned non-permitted strong params' do
20
+ expect { Post.new(StrongParams.new(false, :title => 'Cequel')) }.
21
+ to raise_error(ActiveModel::ForbiddenAttributesError)
22
+ end
23
+
24
+ class StrongParams < DelegateClass(Hash)
25
+ def initialize(permitted, params)
26
+ super(params)
27
+ @permitted = !!permitted
28
+ end
29
+
30
+ def permitted?
31
+ @permitted
32
+ end
33
+ end
34
+ end
35
+
36
+ context 'with mass-assignment protection', :rails => '~> 3.1' do
37
+ model :Post do
38
+ key :permalink, :text
39
+ column :title, :text
40
+ column :page_views, :int
41
+
42
+ attr_accessible :title
43
+ end
44
+
45
+ let(:post) { Post.new(:title => 'Cequel', :page_views => 1000) }
46
+
47
+ it 'should allow assignment of accessible params' do
48
+ post.title.should == 'Cequel'
49
+ end
50
+
51
+ it 'should not allow assignment of inaccessible params' do
52
+ post.page_views.should be_nil
53
+ end
54
+ end
55
+ end
@@ -1,9 +1,16 @@
1
- require File.expand_path('../spec_helper', __FILE__)
1
+ require_relative 'spec_helper'
2
2
 
3
- describe Cequel::Model::Naming do
3
+ describe 'naming' do
4
+ model :Blog do
5
+ key :subdomain, :text
6
+ column :name, :text
7
+ end
4
8
 
5
- it 'should give correct model_name' do
6
- Post.model_name.should == 'Post'
9
+ it 'should implement model_name' do
10
+ Blog.model_name.should == 'Blog'
7
11
  end
8
12
 
13
+ it 'should implement model_name interpolations' do
14
+ Blog.model_name.i18n_key.should == :blog
15
+ end
9
16
  end
@@ -1,201 +1,191 @@
1
1
  require File.expand_path('../spec_helper', __FILE__)
2
2
 
3
3
  describe Cequel::Model::Persistence do
4
- describe '#find' do
5
- it 'should return hydrated instance' do
6
- connection.stub(:execute).
7
- with("SELECT * FROM posts WHERE ? = ? LIMIT 1", :id, 2).
8
- and_return result_stub(:id => 2, :title => 'Cequel')
9
-
10
- post = Post.find(2)
11
- post.id.should == 2
12
- post.title.should == 'Cequel'
13
- end
14
-
15
- it 'should not set defaults when hydrating instance' do
16
- connection.stub(:execute).
17
- with("SELECT ? FROM blogs WHERE ? = ? LIMIT 1", [:id, :name], :id, 2).
18
- and_return result_stub(:id => 1, :title => 'Big Data')
19
-
20
- blog = Blog.select(:id, :name).find(2)
21
- blog.published.should be_nil
22
- end
4
+ model :Blog do
5
+ key :subdomain, :text
6
+ column :name, :text
7
+ column :description, :text
8
+ column :owner_id, :uuid
9
+ end
23
10
 
24
- it 'should return multiple instances' do
25
- connection.stub(:execute).
26
- with("SELECT * FROM posts WHERE ? IN (?)", :id, [2, 5]).
27
- and_return result_stub(
28
- {:id => 2, :title => 'Cequel 2'},
29
- {:id => 5, :title => 'Cequel 5'}
30
- )
31
-
32
- posts = Post.find(2, 5)
33
- posts.map { |post| [post.id, post.title] }.
34
- should == [[2, 'Cequel 2'], [5, 'Cequel 5']]
35
- end
11
+ model :Post do
12
+ key :blog_subdomain, :text
13
+ key :permalink, :text
14
+ column :title, :text
15
+ column :body, :text
16
+ column :author_id, :uuid
17
+ end
36
18
 
37
- it 'should return one-element array if passed one-element array' do
38
- connection.stub(:execute).
39
- with("SELECT * FROM posts WHERE ? = ? LIMIT 1", :id, 2).
40
- and_return result_stub(:id => 2, :title => 'Cequel')
19
+ context 'simple keys' do
20
+ subject { cequel[:blogs].where(:subdomain => 'cequel').first }
41
21
 
42
- post = Post.find([2]).first
43
- post.id.should == 2
44
- post.title.should == 'Cequel'
22
+ let!(:blog) do
23
+ Blog.new do |blog|
24
+ blog.subdomain = 'cequel'
25
+ blog.name = 'Cequel'
26
+ blog.description = 'A Ruby ORM for Cassandra 1.2'
27
+ end.tap(&:save)
45
28
  end
46
29
 
47
- it 'should raise RecordNotFound if row has no data' do
48
- connection.stub(:execute).
49
- with("SELECT * FROM posts WHERE ? = ? LIMIT 1", :id, 2).
50
- and_return result_stub(:id => 2)
30
+ describe '#save' do
31
+ context 'on create' do
32
+ it 'should save row to database' do
33
+ subject[:name].should == 'Cequel'
34
+ end
51
35
 
52
- expect { Post.find(2) }.to raise_error Cequel::Model::RecordNotFound
53
- end
36
+ it 'should mark row persisted' do
37
+ blog.should be_persisted
38
+ end
39
+ end
54
40
 
55
- it 'should raise RecordNotFound if row has nil data' do
56
- connection.stub(:execute).
57
- with("SELECT ? FROM posts WHERE ? = ? LIMIT 1", [:title], :id, 2).
58
- and_return result_stub(:title => nil)
41
+ context 'on update' do
42
+ uuid :owner_id
59
43
 
60
- expect { Post.select(:title).find(2) }.to raise_error Cequel::Model::RecordNotFound
61
- end
44
+ before do
45
+ blog.name = 'Cequel 1.0'
46
+ blog.owner_id = owner_id
47
+ blog.description = nil
48
+ blog.save
49
+ end
62
50
 
63
- it 'should raise RecordNotFound if some rows in multi-row query have no data' do
64
- connection.stub(:execute).
65
- with("SELECT * FROM posts WHERE ? IN (?)", :id, [2, 5]).
66
- and_return result_stub(
67
- {:id => 2, :title => 'Cequel 2'},
68
- {:id => 5}
69
- )
51
+ it 'should change existing column value' do
52
+ subject[:name].should == 'Cequel 1.0'
53
+ end
70
54
 
71
- expect { Post.find(2, 5) }.to raise_error(Cequel::Model::RecordNotFound)
72
- end
73
- end
55
+ it 'should add new column value' do
56
+ subject[:owner_id].should == owner_id
57
+ end
74
58
 
75
- describe '#reload' do
76
- let(:post) do
77
- connection.stub(:execute).
78
- with("SELECT * FROM posts WHERE ? = ? LIMIT 1", :id, 2).
79
- and_return result_stub(:id => 2, :title => 'Cequel')
80
- Post.find(2)
59
+ it 'should remove old column values' do
60
+ subject[:description].should be_nil
61
+ end
62
+ end
81
63
  end
82
64
 
83
- it 'should reload attributes from Cassandra' do
84
- post.title = 'Donkeys'
85
- connection.should_receive(:execute).
86
- with("SELECT * FROM posts WHERE ? = ? LIMIT 1", :id, 2).
87
- and_return result_stub(:id => 2, :title => 'Cequel')
88
- post.reload
89
- post.title.should == 'Cequel'
90
- end
91
- end
65
+ describe '::create' do
66
+ uuid :owner_id
92
67
 
93
- describe '#save' do
94
- describe 'with new record' do
95
- let(:post) { Post.new(:id => 1) }
68
+ describe 'with block' do
69
+ let! :blog do
70
+ Blog.create do |blog|
71
+ blog.subdomain = 'big-data'
72
+ blog.name = 'Big Data'
73
+ end
74
+ end
96
75
 
97
- it 'should persist only columns with values' do
98
- connection.should_receive(:execute).
99
- with("INSERT INTO posts (?) VALUES (?)", ['id', 'title'], [1, 'Cequel'])
76
+ it 'should initialize with block' do
77
+ blog.name.should == 'Big Data'
78
+ end
100
79
 
101
- post.title = 'Cequel'
102
- post.save
80
+ it 'should save instance' do
81
+ Blog.find(blog.subdomain).name.should == 'Big Data'
82
+ end
103
83
  end
104
84
 
105
- it 'should mark instance as persisted' do
106
- connection.stub(:execute).
107
- with("INSERT INTO posts (?) VALUES (?)", ['id', 'title'], [1, 'Cequel'])
85
+ describe 'with attributes' do
86
+ let!(:blog) do
87
+ Blog.create(:subdomain => 'big-data', :name => 'Big Data')
88
+ end
108
89
 
109
- post.title = 'Cequel'
110
- post.save
111
- post.should be_persisted
112
- end
90
+ it 'should initialize with block' do
91
+ blog.name.should == 'Big Data'
92
+ end
113
93
 
114
- it 'should not send anything to Cassandra if no column values are set' do
115
- post.save
116
- post.should_not be_persisted
94
+ it 'should save instance' do
95
+ Blog.find(blog.subdomain).name.should == 'Big Data'
96
+ end
117
97
  end
98
+ end
118
99
 
119
- it 'should raise MissingKey if no key set' do
120
- expect { Post.new.save }.to raise_error(Cequel::Model::MissingKey)
100
+ describe '#update_attributes' do
101
+ let! :blog do
102
+ Blog.create(:subdomain => 'big-data', :name => 'Big Data')
121
103
  end
122
- end
123
104
 
124
- describe 'with persisted record' do
125
- let(:post) do
126
- connection.stub(:execute).with("SELECT * FROM posts WHERE ? = ? LIMIT 1", :id, 1).
127
- and_return result_stub(:id => 1, :blog_id => 1, :title => 'Cequel')
128
- Post.find(1)
105
+ before { blog.update_attributes(:name => 'The Big Data Blog') }
106
+
107
+ it 'should update instance in memory' do
108
+ blog.name.should == 'The Big Data Blog'
129
109
  end
130
110
 
131
- it 'should send UPDATE statement with changed columns using underlying attributes' do
132
- connection.should_receive(:execute).
133
- with "UPDATE posts SET ? = ?, ? = ? WHERE ? = ?", 'body', 'Cequel cequel', 'author_name', 'nworB taM', :id, 1
134
- post.body = 'Cequel cequel'
135
- post.author_name = 'Mat Brown'
136
- post.save
111
+ it 'should save instance' do
112
+ Blog.find(blog.subdomain).name.should == 'The Big Data Blog'
137
113
  end
114
+ end
115
+
116
+ describe '#destroy' do
117
+ before { blog.destroy }
138
118
 
139
- it 'should send DELETE statement with removed columns' do
140
- connection.should_receive(:execute).
141
- with "DELETE ? FROM posts WHERE ? = ?", ['title'], :id, 1
142
- post.title = nil
143
- post.save
119
+ it 'should delete entire row' do
120
+ subject.should be_nil
144
121
  end
145
122
 
146
- it 'should mark record as transient if all attributes removed' do
147
- connection.stub(:execute).
148
- with "DELETE ? FROM posts WHERE ? = ?", ['title', 'blog_id'], :id, 1
149
- post.title = nil
150
- post.blog_id = nil
151
- post.save
152
- post.should_not be_persisted
123
+ it 'should mark record transient' do
124
+ blog.should be_transient
153
125
  end
154
126
  end
155
127
  end
156
128
 
157
- describe '#update_attributes' do
158
- let(:post) do
159
- connection.stub(:execute).with("SELECT * FROM posts WHERE ? = ? LIMIT 1", :id, 1).
160
- and_return result_stub(:id => 1, :blog_id => 1, :title => 'Cequel')
161
- Post.find(1)
129
+ context 'compound keys' do
130
+ subject do
131
+ cequel[:posts].
132
+ where(:blog_subdomain => 'cassandra', :permalink => 'cequel').first
162
133
  end
163
134
 
164
- it 'should change attributes and save them' do
165
- connection.should_receive(:execute).
166
- with "UPDATE posts SET ? = ? WHERE ? = ?", 'body', 'Cequel cequel', :id, 1
167
- post.update_attributes(:body => 'Cequel cequel')
135
+ let!(:post) do
136
+ Post.new do |post|
137
+ post.blog_subdomain = 'cassandra'
138
+ post.permalink = 'cequel'
139
+ post.title = 'Cequel'
140
+ post.body = 'A Ruby ORM for Cassandra 1.2'
141
+ end.tap(&:save)
168
142
  end
169
- end
170
143
 
171
- describe '#destroy' do
172
- let(:post) do
173
- connection.stub(:execute).with("SELECT * FROM posts WHERE ? = ? LIMIT 1", :id, 1).
174
- and_return result_stub(:id => 1, :blog_id => 1, :title => 'Cequel')
175
- Post.find(1)
176
- end
144
+ describe '#save' do
145
+ context 'on create' do
146
+ it 'should save row to database' do
147
+ subject[:title].should == 'Cequel'
148
+ end
177
149
 
178
- it 'should delete all columns from column family' do
179
- connection.should_receive(:execute).
180
- with "DELETE FROM posts WHERE ? = ?", :id, 1
150
+ it 'should mark row persisted' do
151
+ post.should be_persisted
152
+ end
153
+ end
181
154
 
182
- post.destroy
183
- end
184
- end
155
+ context 'on update' do
156
+ uuid :author_id
157
+
158
+ before do
159
+ post.title = 'Cequel 1.0'
160
+ post.author_id = author_id
161
+ post.body = nil
162
+ post.save
163
+ end
164
+
165
+ it 'should change existing column value' do
166
+ subject[:title].should == 'Cequel 1.0'
167
+ end
185
168
 
186
- describe '::create' do
187
- it 'should persist only columns with values' do
188
- connection.should_receive(:execute).
189
- with("INSERT INTO posts (?) VALUES (?)", ['id', 'title'], [1, 'Cequel'])
169
+ it 'should add new column value' do
170
+ subject[:author_id].should == author_id
171
+ end
190
172
 
191
- Post.create(:id => 1, :title => 'Cequel')
173
+ it 'should remove old column values' do
174
+ subject[:body].should be_nil
175
+ end
176
+ end
192
177
  end
193
178
 
194
- it 'should return post instance and mark it as persisted' do
195
- connection.stub(:execute).
196
- with("INSERT INTO posts (?) VALUES (?)", ['id', 'title'], [1, 'Cequel'])
179
+ describe '#destroy' do
180
+ before { post.destroy }
181
+
182
+ it 'should delete entire row' do
183
+ subject.should be_nil
184
+ end
197
185
 
198
- Post.create(:id => 1, :title => 'Cequel').should be_persisted
186
+ it 'should mark record transient' do
187
+ post.should be_transient
188
+ end
199
189
  end
200
190
  end
201
191
  end