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.
- data/lib/cequel.rb +16 -0
- data/lib/cequel/batch.rb +58 -0
- data/lib/cequel/cql_row_specification.rb +22 -0
- data/lib/cequel/data_set.rb +346 -0
- data/lib/cequel/errors.rb +4 -0
- data/lib/cequel/keyspace.rb +106 -0
- data/lib/cequel/model.rb +95 -0
- data/lib/cequel/model/associations.rb +120 -0
- data/lib/cequel/model/callbacks.rb +32 -0
- data/lib/cequel/model/class_internals.rb +48 -0
- data/lib/cequel/model/column.rb +20 -0
- data/lib/cequel/model/dictionary.rb +202 -0
- data/lib/cequel/model/dirty.rb +53 -0
- data/lib/cequel/model/dynamic.rb +31 -0
- data/lib/cequel/model/errors.rb +13 -0
- data/lib/cequel/model/inheritable.rb +48 -0
- data/lib/cequel/model/instance_internals.rb +23 -0
- data/lib/cequel/model/local_association.rb +42 -0
- data/lib/cequel/model/magic.rb +79 -0
- data/lib/cequel/model/mass_assignment_security.rb +21 -0
- data/lib/cequel/model/naming.rb +17 -0
- data/lib/cequel/model/observer.rb +42 -0
- data/lib/cequel/model/persistence.rb +173 -0
- data/lib/cequel/model/properties.rb +143 -0
- data/lib/cequel/model/railtie.rb +33 -0
- data/lib/cequel/model/remote_association.rb +40 -0
- data/lib/cequel/model/scope.rb +362 -0
- data/lib/cequel/model/scoped.rb +50 -0
- data/lib/cequel/model/subclass_internals.rb +45 -0
- data/lib/cequel/model/timestamps.rb +52 -0
- data/lib/cequel/model/translation.rb +17 -0
- data/lib/cequel/model/validations.rb +50 -0
- data/lib/cequel/new_relic_instrumentation.rb +22 -0
- data/lib/cequel/row_specification.rb +63 -0
- data/lib/cequel/statement.rb +23 -0
- data/lib/cequel/version.rb +3 -0
- data/spec/environment.rb +3 -0
- data/spec/examples/data_set_spec.rb +382 -0
- data/spec/examples/keyspace_spec.rb +63 -0
- data/spec/examples/model/associations_spec.rb +109 -0
- data/spec/examples/model/callbacks_spec.rb +79 -0
- data/spec/examples/model/dictionary_spec.rb +413 -0
- data/spec/examples/model/dirty_spec.rb +39 -0
- data/spec/examples/model/dynamic_spec.rb +41 -0
- data/spec/examples/model/inheritable_spec.rb +45 -0
- data/spec/examples/model/magic_spec.rb +199 -0
- data/spec/examples/model/mass_assignment_security_spec.rb +13 -0
- data/spec/examples/model/naming_spec.rb +9 -0
- data/spec/examples/model/observer_spec.rb +86 -0
- data/spec/examples/model/persistence_spec.rb +201 -0
- data/spec/examples/model/properties_spec.rb +81 -0
- data/spec/examples/model/scope_spec.rb +677 -0
- data/spec/examples/model/serialization_spec.rb +20 -0
- data/spec/examples/model/spec_helper.rb +12 -0
- data/spec/examples/model/timestamps_spec.rb +52 -0
- data/spec/examples/model/translation_spec.rb +23 -0
- data/spec/examples/model/validations_spec.rb +86 -0
- data/spec/examples/spec_helper.rb +9 -0
- data/spec/models/asset.rb +21 -0
- data/spec/models/asset_observer.rb +5 -0
- data/spec/models/blog.rb +14 -0
- data/spec/models/blog_posts.rb +6 -0
- data/spec/models/category.rb +9 -0
- data/spec/models/comment.rb +12 -0
- data/spec/models/photo.rb +5 -0
- data/spec/models/post.rb +88 -0
- data/spec/models/post_comments.rb +14 -0
- data/spec/models/post_observer.rb +43 -0
- data/spec/support/helpers.rb +26 -0
- data/spec/support/result_stub.rb +27 -0
- 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,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
|