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