cequel 0.0.0 → 0.4.0

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