cequel 0.0.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/lib/cequel.rb +16 -0
  2. data/lib/cequel/batch.rb +58 -0
  3. data/lib/cequel/cql_row_specification.rb +22 -0
  4. data/lib/cequel/data_set.rb +346 -0
  5. data/lib/cequel/errors.rb +4 -0
  6. data/lib/cequel/keyspace.rb +106 -0
  7. data/lib/cequel/model.rb +95 -0
  8. data/lib/cequel/model/associations.rb +120 -0
  9. data/lib/cequel/model/callbacks.rb +32 -0
  10. data/lib/cequel/model/class_internals.rb +48 -0
  11. data/lib/cequel/model/column.rb +20 -0
  12. data/lib/cequel/model/dictionary.rb +202 -0
  13. data/lib/cequel/model/dirty.rb +53 -0
  14. data/lib/cequel/model/dynamic.rb +31 -0
  15. data/lib/cequel/model/errors.rb +13 -0
  16. data/lib/cequel/model/inheritable.rb +48 -0
  17. data/lib/cequel/model/instance_internals.rb +23 -0
  18. data/lib/cequel/model/local_association.rb +42 -0
  19. data/lib/cequel/model/magic.rb +79 -0
  20. data/lib/cequel/model/mass_assignment_security.rb +21 -0
  21. data/lib/cequel/model/naming.rb +17 -0
  22. data/lib/cequel/model/observer.rb +42 -0
  23. data/lib/cequel/model/persistence.rb +173 -0
  24. data/lib/cequel/model/properties.rb +143 -0
  25. data/lib/cequel/model/railtie.rb +33 -0
  26. data/lib/cequel/model/remote_association.rb +40 -0
  27. data/lib/cequel/model/scope.rb +362 -0
  28. data/lib/cequel/model/scoped.rb +50 -0
  29. data/lib/cequel/model/subclass_internals.rb +45 -0
  30. data/lib/cequel/model/timestamps.rb +52 -0
  31. data/lib/cequel/model/translation.rb +17 -0
  32. data/lib/cequel/model/validations.rb +50 -0
  33. data/lib/cequel/new_relic_instrumentation.rb +22 -0
  34. data/lib/cequel/row_specification.rb +63 -0
  35. data/lib/cequel/statement.rb +23 -0
  36. data/lib/cequel/version.rb +3 -0
  37. data/spec/environment.rb +3 -0
  38. data/spec/examples/data_set_spec.rb +382 -0
  39. data/spec/examples/keyspace_spec.rb +63 -0
  40. data/spec/examples/model/associations_spec.rb +109 -0
  41. data/spec/examples/model/callbacks_spec.rb +79 -0
  42. data/spec/examples/model/dictionary_spec.rb +413 -0
  43. data/spec/examples/model/dirty_spec.rb +39 -0
  44. data/spec/examples/model/dynamic_spec.rb +41 -0
  45. data/spec/examples/model/inheritable_spec.rb +45 -0
  46. data/spec/examples/model/magic_spec.rb +199 -0
  47. data/spec/examples/model/mass_assignment_security_spec.rb +13 -0
  48. data/spec/examples/model/naming_spec.rb +9 -0
  49. data/spec/examples/model/observer_spec.rb +86 -0
  50. data/spec/examples/model/persistence_spec.rb +201 -0
  51. data/spec/examples/model/properties_spec.rb +81 -0
  52. data/spec/examples/model/scope_spec.rb +677 -0
  53. data/spec/examples/model/serialization_spec.rb +20 -0
  54. data/spec/examples/model/spec_helper.rb +12 -0
  55. data/spec/examples/model/timestamps_spec.rb +52 -0
  56. data/spec/examples/model/translation_spec.rb +23 -0
  57. data/spec/examples/model/validations_spec.rb +86 -0
  58. data/spec/examples/spec_helper.rb +9 -0
  59. data/spec/models/asset.rb +21 -0
  60. data/spec/models/asset_observer.rb +5 -0
  61. data/spec/models/blog.rb +14 -0
  62. data/spec/models/blog_posts.rb +6 -0
  63. data/spec/models/category.rb +9 -0
  64. data/spec/models/comment.rb +12 -0
  65. data/spec/models/photo.rb +5 -0
  66. data/spec/models/post.rb +88 -0
  67. data/spec/models/post_comments.rb +14 -0
  68. data/spec/models/post_observer.rb +43 -0
  69. data/spec/support/helpers.rb +26 -0
  70. data/spec/support/result_stub.rb +27 -0
  71. metadata +125 -23
@@ -0,0 +1,39 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Cequel::Model::Dirty do
4
+ let(:post) { Post.new(:id => 1) }
5
+
6
+ it 'should not have changed attributes by default' do
7
+ post.changed_attributes.should be_empty
8
+ end
9
+
10
+ it 'should have changed attributes if attributes change' do
11
+ post.title = 'Cequel'
12
+ post.changed_attributes.should == {:title => nil}.with_indifferent_access
13
+ end
14
+
15
+ it 'should not have changed attributes if attribute set to the same thing' do
16
+ post.title = nil
17
+ post.changed_attributes.should be_empty
18
+ end
19
+
20
+ it 'should support *_changed? method' do
21
+ post.title = 'Cequel'
22
+ post.title_changed?.should be_true
23
+ end
24
+
25
+ it 'should not have changed attributes after save' do
26
+ connection.stub(:execute)
27
+ post.title = 'Cequel'
28
+ post.save
29
+ post.changed_attributes.should be_empty
30
+ end
31
+
32
+ it 'should have previous changes after save' do
33
+ connection.stub(:execute)
34
+ post.title = 'Cequel'
35
+ post.save
36
+ post.previous_changes.
37
+ should == { :title => [nil, 'Cequel'] }.with_indifferent_access
38
+ end
39
+ end
@@ -0,0 +1,41 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Cequel::Model::Dynamic do
4
+
5
+ let(:category) { Category.new(:id => 1, :name => 'Big Data') }
6
+
7
+ it 'should allow getting and setting of dynamic attributes' do
8
+ category[:tag] = 'bigdata'
9
+ category[:tag].should == 'bigdata'
10
+ end
11
+
12
+ it 'should insert dynamic values' do
13
+ category[:tag] = 'bigdata'
14
+ connection.should_receive(:execute).
15
+ with "INSERT INTO categories (?) VALUES (?)", ['id', 'name', 'tag'], [1, 'Big Data', 'bigdata']
16
+ category.save
17
+ end
18
+
19
+ it 'should update dynamic values' do
20
+ category[:tag] = 'bigdata'
21
+ connection.stub(:execute).
22
+ with "INSERT INTO categories (?) VALUES (?)", ['id', 'name', 'tag'], [1, 'Big Data', 'bigdata']
23
+ category.save
24
+ category[:tag] = 'big-data'
25
+ category[:color] = 'blue'
26
+ connection.should_receive(:execute).
27
+ with "UPDATE categories SET ? = ?, ? = ? WHERE ? = ?", 'tag', 'big-data', 'color', 'blue', :id, 1
28
+ category.save
29
+ end
30
+
31
+ it 'should delete dynamic values' do
32
+ category[:tag] = 'bigdata'
33
+ connection.stub(:execute).
34
+ with "INSERT INTO categories (?) VALUES (?)", ['id', 'name', 'tag'], [1, 'Big Data', 'bigdata']
35
+ category.save
36
+ category[:tag] = nil
37
+ connection.should_receive(:execute).
38
+ with "DELETE ? FROM categories WHERE ? = ?", ['tag'], :id, 1
39
+ category.save
40
+ end
41
+ end
@@ -0,0 +1,45 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Cequel::Model::Inheritable do
4
+ it 'should inherit key from superclass' do
5
+ Photo.key_alias.should == :id
6
+ end
7
+
8
+ it 'should inherit columns from superclass' do
9
+ Photo.column_names.should == [:id, :class_name, :label, :checksum, :url]
10
+ end
11
+
12
+ it 'should return ::base_class for base class' do
13
+ Asset.base_class.should == Asset
14
+ end
15
+
16
+ it 'should return ::base_class for subclass' do
17
+ Photo.base_class.should == Asset
18
+ end
19
+
20
+ it 'should not allow overriding a class without a type column' do
21
+ base = Class.new do
22
+ include Cequel::Model
23
+ key :id, :integer
24
+ end
25
+ expect { sub = Class.new(base) }.to raise_error(ArgumentError)
26
+ end
27
+
28
+ it 'should set type on initialization' do
29
+ Photo.new.class_name.should == 'Photo'
30
+ end
31
+
32
+ it 'should query correct column family when querying subclass' do
33
+ connection.stub(:execute).
34
+ with("SELECT * FROM assets WHERE ? = ? LIMIT 1", :id, 1).
35
+ and_return result_stub(:id => 1, :label => 'Cequel')
36
+ Photo.find(1)
37
+ end
38
+
39
+ it 'should hydrate correct model class when querying base class' do
40
+ connection.stub(:execute).
41
+ with('SELECT * FROM assets WHERE ? = ? LIMIT 1', :id, 1).
42
+ and_return result_stub(:id => 1, :class_name => 'Photo', :label => 'Cequel')
43
+ Asset.find(1).should be_a(Photo)
44
+ end
45
+ end
@@ -0,0 +1,199 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Cequel::Model::Magic do
4
+ describe '::find_by_*' do
5
+ it 'should magically look up one record by given value' do
6
+ connection.stub(:execute).
7
+ with("SELECT * FROM posts WHERE ? = ? LIMIT 1", :title, 'Cequel').
8
+ and_return result_stub(:id => 1, :title => 'Cequel')
9
+
10
+ Post.find_by_title('Cequel').id.should == 1
11
+ end
12
+
13
+ it 'should magically look up one record by multiple values' do
14
+ connection.stub(:execute).
15
+ with("SELECT * FROM posts WHERE ? = ? AND ? = ? LIMIT 1", :title, 'Cequel', :published, true).
16
+ and_return result_stub(:id => 1, :title => 'Cequel', :published => true)
17
+
18
+ Post.find_by_title_and_published('Cequel', true).id.should == 1
19
+ end
20
+
21
+ it 'should magically work on scopes' do
22
+ connection.stub(:execute).
23
+ with("SELECT ? FROM posts WHERE ? = ? AND ? = ? LIMIT 1", [:id], :title, 'Cequel', :published, true).
24
+ and_return result_stub(:id => 1)
25
+
26
+ Post.select(:id).find_by_title_and_published('Cequel', true).id.should == 1
27
+ end
28
+
29
+ it 'should raise error if specified columns different from arg count' do
30
+ expect { Post.find_by_title_and_published('Cequel') }.
31
+ to raise_error(ArgumentError)
32
+ end
33
+ end
34
+
35
+ describe '::find_all_by_*' do
36
+ context 'with existing record specified as args' do
37
+ it 'should magically look up one record by multiple values' do
38
+ connection.stub(:execute).
39
+ with("SELECT * FROM posts WHERE ? = ? AND ? = ?", :title, 'Cequel', :published, true).
40
+ and_return result_stub(
41
+ {:id => 1, :title => 'Cequel', :published => true},
42
+ {:id => 2, :title => 'Cequel2', :published => true }
43
+ )
44
+
45
+ Post.find_all_by_title_and_published('Cequel', true).map(&:id).
46
+ should == [1, 2]
47
+ end
48
+
49
+ it 'should magically work on scopes' do
50
+ connection.stub(:execute).
51
+ with("SELECT ? FROM posts WHERE ? = ? AND ? = ?", [:id], :title, 'Cequel', :published, true).
52
+ and_return result_stub(
53
+ {:id => 1},
54
+ {:id => 2}
55
+ )
56
+
57
+ Post.select(:id).find_all_by_title_and_published('Cequel', true).map(&:id).
58
+ should == [1, 2]
59
+ end
60
+ end
61
+ end
62
+
63
+ describe '::find_or_create_by_*' do
64
+ it 'should return existing record from args' do
65
+ connection.stub(:execute).
66
+ with("SELECT * FROM posts WHERE ? = ? AND ? = ? LIMIT 1", :title, 'Cequel', :published, true).
67
+ and_return result_stub(:id => 1, :title => 'Cequel', :published => true)
68
+
69
+ Post.find_or_create_by_title_and_published('Cequel', true).id.should == 1
70
+ end
71
+
72
+ it 'should create new record from args' do
73
+ now = Time.now
74
+ Time.stub!(:now).and_return now
75
+ connection.stub(:execute).
76
+ with("SELECT * FROM blogs WHERE ? = ? LIMIT 1", :id, 1).
77
+ and_return result_stub
78
+ connection.should_receive(:execute).
79
+ with(
80
+ "INSERT INTO blogs (?) VALUES (?)",
81
+ ['id', 'published', 'updated_at', 'created_at'],
82
+ [1, true, now, now]
83
+ )
84
+
85
+ Blog.find_or_create_by_id(1).id.should == 1
86
+ end
87
+
88
+ it 'should look up record from attributes' do
89
+ connection.stub(:execute).
90
+ with("SELECT * FROM posts WHERE ? = ? AND ? = ? LIMIT 1", :title, 'Cequel', :published, true).
91
+ and_return result_stub(:id => 1, :title => 'Cequel', :published => true)
92
+
93
+ Post.find_or_create_by_title_and_published(
94
+ :id => 2, :title => 'Cequel', :published => true
95
+ ).id.should == 1
96
+ end
97
+
98
+ it 'should create record from all attributes specified, including non-lookup ones' do
99
+ connection.stub(:execute).
100
+ with("SELECT * FROM posts WHERE ? = ? AND ? = ? LIMIT 1", :title, 'Cequel', :published, true).
101
+ and_return result_stub
102
+ connection.should_receive(:execute).
103
+ with(
104
+ "INSERT INTO posts (?) VALUES (?)",
105
+ ['id', 'title', 'published'],
106
+ [2, 'Cequel', true])
107
+
108
+ Post.find_or_create_by_title_and_published(
109
+ :id => 2, :title => 'Cequel', :published => true
110
+ ).id.should == 2
111
+ end
112
+
113
+ it 'should yield instance on create if block given' do
114
+ connection.stub(:execute).
115
+ with("SELECT * FROM posts WHERE ? = ? AND ? = ? LIMIT 1", :title, 'Cequel', :published, true).
116
+ and_return result_stub
117
+ connection.should_receive(:execute).
118
+ with(
119
+ "INSERT INTO posts (?) VALUES (?)",
120
+ ['id', 'title', 'published'],
121
+ [2, 'Cequel', true]
122
+ )
123
+
124
+ Post.find_or_create_by_title_and_published('Cequel', true) do |post|
125
+ post.id = 2
126
+ end.id.should == 2
127
+ end
128
+
129
+ it 'should work on scopes' do
130
+ connection.stub(:execute).
131
+ with("SELECT ? FROM posts WHERE ? = ? AND ? = ? LIMIT 1", [:id], :title, 'Cequel', :published, true).
132
+ and_return result_stub(:id => 1)
133
+
134
+ Post.select(:id).find_or_create_by_title_and_published('Cequel', true).id.
135
+ should == 1
136
+ end
137
+ end
138
+
139
+ describe '::find_or_initialize_by_*' do
140
+ it 'should return existing record from args' do
141
+ connection.stub(:execute).
142
+ with("SELECT * FROM posts WHERE ? = ? AND ? = ? LIMIT 1", :title, 'Cequel', :published, true).
143
+ and_return result_stub(:id => 1, :title => 'Cequel', :published => true)
144
+
145
+ Post.find_or_initialize_by_title_and_published('Cequel', true).id.should == 1
146
+ end
147
+
148
+ it 'should initialize new record from args' do
149
+ now = Time.now
150
+ Time.stub!(:now).and_return now
151
+ timestamp = (now.to_f * 1000).to_i
152
+ connection.stub(:execute).
153
+ with("SELECT * FROM blogs WHERE ? = ? LIMIT 1", :id, 1).
154
+ and_return result_stub
155
+
156
+ Blog.find_or_initialize_by_id(1).id.should == 1
157
+ end
158
+
159
+ it 'should look up record from attributes' do
160
+ connection.stub(:execute).
161
+ with("SELECT * FROM posts WHERE ? = ? AND ? = ? LIMIT 1", :title, 'Cequel', :published, true).
162
+ and_return result_stub(:id => 1, :title => 'Cequel', :published => true)
163
+
164
+ Post.find_or_initialize_by_title_and_published(
165
+ :id => 2, :title => 'Cequel', :published => true
166
+ ).id.should == 1
167
+ end
168
+
169
+ it 'should create record from all attributes specified, including non-lookup ones' do
170
+ connection.stub(:execute).
171
+ with("SELECT * FROM posts WHERE ? = ? AND ? = ? LIMIT 1", :title, 'Cequel', :published, true).
172
+ and_return result_stub
173
+
174
+ Post.find_or_initialize_by_title_and_published(
175
+ :id => 2, :title => 'Cequel', :published => true
176
+ ).id.should == 2
177
+ end
178
+
179
+ it 'should yield instance on initialize if block given' do
180
+ connection.stub(:execute).
181
+ with("SELECT * FROM posts WHERE ? = ? AND ? = ? LIMIT 1", :title, 'Cequel', :published, true).
182
+ and_return result_stub
183
+
184
+ Post.find_or_initialize_by_title_and_published('Cequel', true) do |post|
185
+ post.id = 2
186
+ end.id.should == 2
187
+ end
188
+
189
+ it 'should work on scopes' do
190
+ connection.stub(:execute).
191
+ with("SELECT ? FROM posts WHERE ? = ? AND ? = ? LIMIT 1", [:id], :title, 'Cequel', :published, true).
192
+ and_return result_stub(:id => 1)
193
+
194
+ Post.select(:id).find_or_initialize_by_title_and_published('Cequel', true).id.
195
+ should == 1
196
+ end
197
+
198
+ end
199
+ end
@@ -0,0 +1,13 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Cequel::Model::MassAssignmentSecurity do
4
+ let(:post) { Post.new(:id => 1, :title => 'Cequel', :blog_id => 3) }
5
+
6
+ it 'should allow setting of unprotected attributes' do
7
+ post.title.should == 'Cequel'
8
+ end
9
+
10
+ it 'should not allow setting of protected attributes' do
11
+ post.blog_id.should be_nil
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Cequel::Model::Naming do
4
+
5
+ it 'should give correct model_name' do
6
+ Post.model_name.should == 'Post'
7
+ end
8
+
9
+ end
@@ -0,0 +1,86 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Cequel::Model::Observer do
4
+ before do
5
+ Cequel::Model.observers = [:post_observer, :asset_observer]
6
+ Cequel::Model.instantiate_observers
7
+ end
8
+
9
+ let(:post) do
10
+ connection.stub(:execute).
11
+ with("SELECT * FROM posts WHERE ? = ? LIMIT 1", :id, 1).
12
+ and_return result_stub('id' => 1, 'title' => 'Hey')
13
+ Post.find(1)
14
+ end
15
+
16
+ shared_examples_for 'observing callbacks' do |*callbacks|
17
+
18
+ all_callbacks = [
19
+ :before_create, :after_create, :before_update, :after_update,
20
+ :before_save, :after_save, :before_destroy, :after_destroy,
21
+ :before_validation, :after_validation
22
+ ]
23
+
24
+ callbacks.each do |callback|
25
+ it "should observe #{callback}" do
26
+ post.should have_been_observed(callback)
27
+ end
28
+ end
29
+
30
+ (all_callbacks - callbacks).each do |callback|
31
+ it "should not observe #{callback}" do
32
+ post.should_not have_been_observed(callback)
33
+ end
34
+ end
35
+ end
36
+
37
+ context 'on create' do
38
+ let(:post) do
39
+ connection.stub(:execute).
40
+ with "INSERT INTO posts (?) VALUES (?)", ['id', 'title'], [1, 'Hey']
41
+ Post.new(:id => 1, :title => 'Hey')
42
+ end
43
+
44
+ before { post.save }
45
+
46
+ it_should_behave_like 'observing callbacks',
47
+ :before_create, :after_create, :before_save, :after_save, :before_validation, :after_validation
48
+ end
49
+
50
+ context 'on update' do
51
+
52
+ before { post.save }
53
+
54
+ it_should_behave_like 'observing callbacks',
55
+ :before_update, :after_update, :before_save, :after_save, :before_validation, :after_validation
56
+ end
57
+
58
+ context 'on destroy 'do
59
+
60
+ before do
61
+ connection.stub(:execute).
62
+ with "DELETE FROM posts WHERE ? = ?", :id, 1
63
+
64
+ post.destroy
65
+ end
66
+
67
+ it_should_behave_like 'observing callbacks', :before_destroy, :after_destroy
68
+ end
69
+
70
+ context 'on validation' do
71
+ before do
72
+ post.valid?
73
+ end
74
+
75
+ it_should_behave_like 'observing callbacks', :before_validation, :after_validation
76
+ end
77
+
78
+ context 'with inheritence' do
79
+ it 'should observe subclass' do
80
+ connection.stub(:execute).
81
+ with("INSERT INTO assets (?) VALUES (?)", ['id', 'label', 'class_name'], [1, 'Cequel', 'Photo'])
82
+ photo = Photo.create!(:id => 1, :label => 'Cequel')
83
+ photo.should have_observed(:before_save)
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,201 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
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
23
+
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
36
+
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')
41
+
42
+ post = Post.find([2]).first
43
+ post.id.should == 2
44
+ post.title.should == 'Cequel'
45
+ end
46
+
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)
51
+
52
+ expect { Post.find(2) }.to raise_error Cequel::Model::RecordNotFound
53
+ end
54
+
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)
59
+
60
+ expect { Post.select(:title).find(2) }.to raise_error Cequel::Model::RecordNotFound
61
+ end
62
+
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
+ )
70
+
71
+ expect { Post.find(2, 5) }.to raise_error(Cequel::Model::RecordNotFound)
72
+ end
73
+ end
74
+
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)
81
+ end
82
+
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
92
+
93
+ describe '#save' do
94
+ describe 'with new record' do
95
+ let(:post) { Post.new(:id => 1) }
96
+
97
+ it 'should persist only columns with values' do
98
+ connection.should_receive(:execute).
99
+ with("INSERT INTO posts (?) VALUES (?)", ['id', 'title'], [1, 'Cequel'])
100
+
101
+ post.title = 'Cequel'
102
+ post.save
103
+ end
104
+
105
+ it 'should mark instance as persisted' do
106
+ connection.stub(:execute).
107
+ with("INSERT INTO posts (?) VALUES (?)", ['id', 'title'], [1, 'Cequel'])
108
+
109
+ post.title = 'Cequel'
110
+ post.save
111
+ post.should be_persisted
112
+ end
113
+
114
+ it 'should not send anything to Cassandra if no column values are set' do
115
+ post.save
116
+ post.should_not be_persisted
117
+ end
118
+
119
+ it 'should raise MissingKey if no key set' do
120
+ expect { Post.new.save }.to raise_error(Cequel::Model::MissingKey)
121
+ end
122
+ end
123
+
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)
129
+ end
130
+
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
137
+ end
138
+
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
144
+ end
145
+
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
153
+ end
154
+ end
155
+ end
156
+
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)
162
+ end
163
+
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')
168
+ end
169
+ end
170
+
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
177
+
178
+ it 'should delete all columns from column family' do
179
+ connection.should_receive(:execute).
180
+ with "DELETE FROM posts WHERE ? = ?", :id, 1
181
+
182
+ post.destroy
183
+ end
184
+ end
185
+
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'])
190
+
191
+ Post.create(:id => 1, :title => 'Cequel')
192
+ end
193
+
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'])
197
+
198
+ Post.create(:id => 1, :title => 'Cequel').should be_persisted
199
+ end
200
+ end
201
+ end