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,63 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
require 'stringio'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
describe Cequel::Keyspace do
|
6
|
+
describe '::batch' do
|
7
|
+
it 'should send enclosed write statements in bulk' do
|
8
|
+
connection.should_receive(:execute).with(<<CQL, [:id, :title], [1, 'Hey'], :body, 'Body')
|
9
|
+
BEGIN BATCH
|
10
|
+
INSERT INTO posts (?) VALUES (?)
|
11
|
+
UPDATE posts SET ? = ?
|
12
|
+
DELETE FROM posts
|
13
|
+
APPLY BATCH
|
14
|
+
CQL
|
15
|
+
cequel.batch do
|
16
|
+
cequel[:posts].insert(:id => 1, :title => 'Hey')
|
17
|
+
cequel[:posts].update(:body => 'Body')
|
18
|
+
cequel[:posts].delete
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should auto-apply if option given' do
|
23
|
+
connection.should_receive(:execute).with(<<CQL, [:id, :title], [1, 'Hey'], :body, 'Body')
|
24
|
+
BEGIN BATCH
|
25
|
+
INSERT INTO posts (?) VALUES (?)
|
26
|
+
UPDATE posts SET ? = ?
|
27
|
+
APPLY BATCH
|
28
|
+
CQL
|
29
|
+
connection.should_receive(:execute).with(<<CQL)
|
30
|
+
BEGIN BATCH
|
31
|
+
DELETE FROM posts
|
32
|
+
APPLY BATCH
|
33
|
+
CQL
|
34
|
+
|
35
|
+
|
36
|
+
cequel.batch(:auto_apply => 2) do
|
37
|
+
cequel[:posts].insert(:id => 1, :title => 'Hey')
|
38
|
+
cequel[:posts].update(:body => 'Body')
|
39
|
+
cequel[:posts].delete
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should do nothing if no statements executed in batch' do
|
44
|
+
expect { cequel.batch {} }.to_not raise_error
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '::logger=' do
|
49
|
+
let(:io) { StringIO.new }
|
50
|
+
let(:logger) { Logger.new(io) }
|
51
|
+
|
52
|
+
before do
|
53
|
+
logger.level = Logger::DEBUG
|
54
|
+
cequel.logger = logger
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'should log queries with bind variables resolved' do
|
58
|
+
connection.stub(:execute).with("SELECT ? FROM posts", [:id, :title]).and_return result_stub
|
59
|
+
cequel[:posts].select(:id, :title).to_a
|
60
|
+
io.string.should =~ /CQL \(\d+ms\) SELECT 'id','title' FROM posts/
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Cequel::Model::Associations do
|
4
|
+
|
5
|
+
describe '::belongs_to' do
|
6
|
+
let(:post) do
|
7
|
+
Post.new(:id => 1).tap { |post| post.blog_id = 2 }
|
8
|
+
end
|
9
|
+
|
10
|
+
before do
|
11
|
+
connection.stub(:execute).
|
12
|
+
with('SELECT * FROM blogs WHERE ? = ? LIMIT 1', :id, 2).
|
13
|
+
and_return result_stub(:id => 2, :name => 'Big Data Blog')
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'should query column family and return associated model' do
|
17
|
+
post.blog.name.should == 'Big Data Blog'
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'should be nil if foreign key column is nil' do
|
21
|
+
post.blog_id = nil
|
22
|
+
post.blog.should be_nil
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'should memoize instance' do
|
26
|
+
post.blog
|
27
|
+
connection.should_not_receive :execute
|
28
|
+
post.blog
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should unmemoize instance if foreign key is changed' do
|
32
|
+
post.blog
|
33
|
+
post.blog_id = 3
|
34
|
+
connection.stub(:execute).
|
35
|
+
with('SELECT * FROM blogs WHERE ? = ? LIMIT 1', :id, 3).
|
36
|
+
and_return result_stub(:id => 2, :name => 'Another Blog')
|
37
|
+
|
38
|
+
post.blog.name.should == 'Another Blog'
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should provide setter for association' do
|
42
|
+
post.blog = Blog.new(:id => 3, :name => 'This blog')
|
43
|
+
post.blog_id.should == 3
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'should set foreign key to nil if association set to nil' do
|
47
|
+
post.blog = Blog.new(:id => 3, :name => 'This blog')
|
48
|
+
post.blog = nil
|
49
|
+
post.blog_id.should be_nil
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should save transient associated object' do
|
53
|
+
now = Time.now
|
54
|
+
Time.stub(:now).and_return now
|
55
|
+
post.blog = Blog.new(:id => 3, :name => 'This blog')
|
56
|
+
connection.should_receive(:execute).
|
57
|
+
with "INSERT INTO blogs (?) VALUES (?)",
|
58
|
+
['id', 'published', 'name', 'updated_at', 'created_at'],
|
59
|
+
[3, true, 'This blog', now, now]
|
60
|
+
connection.stub(:execute).
|
61
|
+
with "INSERT INTO posts (?) VALUES (?)", ['id', 'blog_id'], [post.id, 3]
|
62
|
+
post.save
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '::has_many' do
|
68
|
+
let(:blog) do
|
69
|
+
Blog.new(:id => 2)
|
70
|
+
end
|
71
|
+
|
72
|
+
before do
|
73
|
+
connection.stub(:execute).
|
74
|
+
with('SELECT * FROM posts WHERE ? = ?', :blog_id, 2).
|
75
|
+
and_return result_stub(
|
76
|
+
{:id => 1, :title => 'Cequel'},
|
77
|
+
{:id => 2, :title => 'Cequel revisited'}
|
78
|
+
)
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should provide scope for associated instances' do
|
82
|
+
blog.posts.map { |post| post.title }.should ==
|
83
|
+
['Cequel', 'Cequel revisited']
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should destroy associated instances if :dependent => :destroy' do
|
87
|
+
connection.stub(:execute).with 'DELETE FROM blogs WHERE ? = ?', :id, 2
|
88
|
+
connection.should_receive(:execute).with 'DELETE FROM posts WHERE ? = ?', :id, 1
|
89
|
+
connection.should_receive(:execute).with 'DELETE FROM posts WHERE ? = ?', :id, 2
|
90
|
+
blog.destroy
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe '::has_one' do
|
95
|
+
let(:post) { Post.new(:id => 1) }
|
96
|
+
|
97
|
+
before do
|
98
|
+
connection.stub(:execute).
|
99
|
+
with("SELECT * FROM assets WHERE ? = ? AND ? = ? LIMIT 1", :class_name, 'Photo', :post_id, 1).
|
100
|
+
and_return result_stub(
|
101
|
+
{:id => 1, :type => 'Photo', :url => 'http://outofti.me/glamour.jpg'},
|
102
|
+
)
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should look up association by foreign key' do
|
106
|
+
post.thumbnail.url.should == 'http://outofti.me/glamour.jpg'
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Cequel::Model::Callbacks do
|
4
|
+
let(:post) do
|
5
|
+
connection.stub(:execute).
|
6
|
+
with("SELECT * FROM posts WHERE ? = ? LIMIT 1", :id, 1).
|
7
|
+
and_return result_stub(:id => 1, :title => 'Cequel')
|
8
|
+
Post.find(1)
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'on create' do
|
12
|
+
let(:post) { Post.new(:id => 1) }
|
13
|
+
|
14
|
+
before do
|
15
|
+
post.save
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should invoke save callback' do
|
19
|
+
post.should have_callback(:save)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should invoke create callback' do
|
23
|
+
post.should have_callback(:create)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'should not invoke update callback' do
|
27
|
+
post.should_not have_callback(:update)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'should not invoke destroy callback' do
|
31
|
+
post.should_not have_callback(:destroy)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'on update' do
|
36
|
+
before do
|
37
|
+
post.save
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should invoke save callback' do
|
41
|
+
post.should have_callback(:save)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'should not invoke create callback' do
|
45
|
+
post.should_not have_callback(:create)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should invoke update callback' do
|
49
|
+
post.should have_callback(:update)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should not invoke destroy callback' do
|
53
|
+
post.should_not have_callback(:destroy)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'on destroy' do
|
58
|
+
before do
|
59
|
+
connection.stub(:execute).with("DELETE FROM posts WHERE ? = ?", :id, 1)
|
60
|
+
post.destroy
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should not invoke save callback' do
|
64
|
+
post.should_not have_callback(:save)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'should not invoke create callback' do
|
68
|
+
post.should_not have_callback(:create)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should not invoke update callback' do
|
72
|
+
post.should_not have_callback(:update)
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'should invoke destroy callback' do
|
76
|
+
post.should have_callback(:destroy)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,413 @@
|
|
1
|
+
require File.expand_path('../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe Cequel::Model::Dictionary do
|
4
|
+
let(:uuid1) { uuid }
|
5
|
+
let(:uuid2) { uuid }
|
6
|
+
let(:uuid3) { uuid }
|
7
|
+
let(:dictionary) { BlogPosts[1] }
|
8
|
+
|
9
|
+
describe '#save' do
|
10
|
+
before do
|
11
|
+
connection.stub(:execute).
|
12
|
+
with('SELECT FIRST 2 * FROM blog_posts WHERE ? = ? LIMIT 1', :blog_id, 1).
|
13
|
+
and_return result_stub('blog_id' => 1, uuid1 => 1, uuid2 => 2)
|
14
|
+
connection.stub(:execute).
|
15
|
+
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid2, '', :blog_id, 1).
|
16
|
+
and_return result_stub('blog_id' => 1)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should write to row' do
|
20
|
+
dictionary[uuid1] = 1
|
21
|
+
dictionary[uuid2] = 2
|
22
|
+
connection.should_receive(:execute).
|
23
|
+
with('UPDATE blog_posts SET ? = ?, ? = ? WHERE ? = ?', uuid1, 1, uuid2, 2, :blog_id, 1)
|
24
|
+
dictionary.save
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'should update changed columns' do
|
28
|
+
dictionary.load
|
29
|
+
dictionary[uuid1] = 2
|
30
|
+
connection.should_receive(:execute).
|
31
|
+
with('UPDATE blog_posts SET ? = ? WHERE ? = ?', uuid1, 2, :blog_id, 1)
|
32
|
+
dictionary.save
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should write new columns' do
|
36
|
+
dictionary.load
|
37
|
+
dictionary[uuid3] = 3
|
38
|
+
connection.should_receive(:execute).
|
39
|
+
with('UPDATE blog_posts SET ? = ? WHERE ? = ?', uuid3, 3, :blog_id, 1)
|
40
|
+
dictionary.save
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should delete removed columns' do
|
44
|
+
dictionary.load
|
45
|
+
dictionary[uuid1] = nil
|
46
|
+
connection.should_receive(:execute).
|
47
|
+
with('DELETE ? FROM blog_posts WHERE ? = ?', [uuid1], :blog_id, 1)
|
48
|
+
dictionary.save
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'should not update a row if it should be deleted' do
|
52
|
+
dictionary.load
|
53
|
+
dictionary[uuid1] = 1
|
54
|
+
dictionary[uuid1] = nil
|
55
|
+
connection.should_receive(:execute).once.
|
56
|
+
with('DELETE ? FROM blog_posts WHERE ? = ?', [uuid1], :blog_id, 1)
|
57
|
+
dictionary.save
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'should not delete a row if it was subsequently updated' do
|
61
|
+
dictionary.load
|
62
|
+
dictionary[uuid1] = nil
|
63
|
+
dictionary[uuid1] = 1
|
64
|
+
connection.should_receive(:execute).once.
|
65
|
+
with('UPDATE blog_posts SET ? = ? WHERE ? = ?', uuid1, 1, :blog_id, 1)
|
66
|
+
dictionary.save
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'should not save columns that have been saved previously' do
|
70
|
+
dictionary.load
|
71
|
+
dictionary[uuid3] = 3
|
72
|
+
connection.should_receive(:execute).once.
|
73
|
+
with('UPDATE blog_posts SET ? = ? WHERE ? = ?', uuid3, 3, :blog_id, 1)
|
74
|
+
2.times { dictionary.save }
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'should not delete columns that have been deleted previously' do
|
78
|
+
dictionary.load
|
79
|
+
dictionary[uuid1] = nil
|
80
|
+
connection.should_receive(:execute).once.
|
81
|
+
with('DELETE ? FROM blog_posts WHERE ? = ?', [uuid1], :blog_id, 1)
|
82
|
+
2.times { dictionary.save }
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
describe '#destroy' do
|
88
|
+
before do
|
89
|
+
connection.stub(:execute).
|
90
|
+
with('SELECT FIRST 2 * FROM blog_posts WHERE ? = ? LIMIT 1', :blog_id, 1).
|
91
|
+
and_return result_stub('blog_id' => 1, uuid1 => 1, uuid2 => 2)
|
92
|
+
connection.stub(:execute).
|
93
|
+
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid2, '', :blog_id, 1).
|
94
|
+
and_return result_stub({'blog_id' => 1})
|
95
|
+
|
96
|
+
dictionary.load
|
97
|
+
connection.should_receive(:execute).
|
98
|
+
with('DELETE FROM blog_posts WHERE ? = ?', :blog_id, 1)
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'should delete row from cassandra' do
|
102
|
+
dictionary.destroy
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'should remove all properties from memory' do
|
106
|
+
dictionary.destroy
|
107
|
+
connection.stub(:execute).
|
108
|
+
with('SELECT ? FROM blog_posts WHERE ? = ? LIMIT 1', [uuid1], :blog_id, 1).
|
109
|
+
and_return result_stub({})
|
110
|
+
dictionary[uuid1].should be_nil
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'should not save previously updated properties when saved' do
|
114
|
+
dictionary[uuid1] = 5
|
115
|
+
dictionary.destroy
|
116
|
+
connection.should_not_receive(:execute)
|
117
|
+
dictionary.save
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'should not delete previously deleted properties when saved' do
|
121
|
+
dictionary[uuid1] = nil
|
122
|
+
dictionary.destroy
|
123
|
+
connection.should_not_receive(:execute)
|
124
|
+
dictionary.save
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
context 'without row in memory' do
|
129
|
+
|
130
|
+
describe '#each_pair' do
|
131
|
+
|
132
|
+
it 'should load columns in batches and yield them' do
|
133
|
+
connection.should_receive(:execute).
|
134
|
+
with('SELECT FIRST 2 * FROM blog_posts WHERE ? = ? LIMIT 1', :blog_id, 1).
|
135
|
+
and_return result_stub('blog_id' => 1, uuid1 => 1, uuid2 => 2)
|
136
|
+
connection.should_receive(:execute).
|
137
|
+
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid2, '', :blog_id, 1).
|
138
|
+
and_return result_stub('blog_id' => 1, uuid2 => 2, uuid3 => 3)
|
139
|
+
connection.should_receive(:execute).
|
140
|
+
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid3, '', :blog_id, 1).
|
141
|
+
and_return result_stub({'blog_id' => 1})
|
142
|
+
hash = {}
|
143
|
+
dictionary.each_pair do |key, value|
|
144
|
+
hash[key] = value
|
145
|
+
end
|
146
|
+
hash.should == {uuid1 => 1, uuid2 => 2, uuid3 => 3}
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
describe '#[]' do
|
152
|
+
|
153
|
+
it 'should load column from cassandra' do
|
154
|
+
connection.stub(:execute).
|
155
|
+
with('SELECT ? FROM blog_posts WHERE ? = ? LIMIT 1', [uuid1], :blog_id, 1).
|
156
|
+
and_return result_stub(uuid1 => 1)
|
157
|
+
dictionary[uuid1].should == 1
|
158
|
+
end
|
159
|
+
|
160
|
+
end
|
161
|
+
|
162
|
+
describe '#slice' do
|
163
|
+
it 'should load columns from data store' do
|
164
|
+
connection.stub(:execute).
|
165
|
+
with('SELECT ? FROM blog_posts WHERE ? = ? LIMIT 1', [uuid1,uuid2], :blog_id, 1).
|
166
|
+
and_return result_stub(uuid1 => 1, uuid2 => 2)
|
167
|
+
dictionary.slice(uuid1, uuid2).should == {uuid1 => 1, uuid2 => 2}
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
describe '#keys' do
|
172
|
+
it 'should load keys from data store' do
|
173
|
+
connection.should_receive(:execute).
|
174
|
+
with('SELECT FIRST 2 * FROM blog_posts WHERE ? = ? LIMIT 1', :blog_id, 1).
|
175
|
+
and_return result_stub('blog_id' => 1, uuid1 => 1, uuid2 => 2)
|
176
|
+
connection.should_receive(:execute).
|
177
|
+
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid2, '', :blog_id, 1).
|
178
|
+
and_return result_stub('blog_id' => 1, uuid2 => 2, uuid3 => 3)
|
179
|
+
connection.should_receive(:execute).
|
180
|
+
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid3, '', :blog_id, 1).
|
181
|
+
and_return result_stub({'blog_id' => 1})
|
182
|
+
dictionary.keys.should == [uuid1, uuid2, uuid3]
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
describe '#values' do
|
187
|
+
it 'should load values from data store' do
|
188
|
+
connection.should_receive(:execute).
|
189
|
+
with('SELECT FIRST 2 * FROM blog_posts WHERE ? = ? LIMIT 1', :blog_id, 1).
|
190
|
+
and_return result_stub('blog_id' => 1, uuid1 => 1, uuid2 => 2)
|
191
|
+
connection.should_receive(:execute).
|
192
|
+
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid2, '', :blog_id, 1).
|
193
|
+
and_return result_stub('blog_id' => 1, uuid2 => 2, uuid3 => 3)
|
194
|
+
connection.should_receive(:execute).
|
195
|
+
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid3, '', :blog_id, 1).
|
196
|
+
and_return result_stub({'blog_id' => 1})
|
197
|
+
dictionary.values.should == [1, 2, 3]
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
|
203
|
+
context 'with data loaded in memory' do
|
204
|
+
before do
|
205
|
+
connection.stub(:execute).
|
206
|
+
with('SELECT FIRST 2 * FROM blog_posts WHERE ? = ? LIMIT 1', :blog_id, 1).
|
207
|
+
and_return result_stub('blog_id' => 1, uuid1 => 1, uuid2 => 2)
|
208
|
+
connection.stub(:execute).
|
209
|
+
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid2, '', :blog_id, 1).
|
210
|
+
and_return result_stub('blog_id' => 1, uuid2 => 2, uuid3 => 3)
|
211
|
+
connection.stub(:execute).
|
212
|
+
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid3, '', :blog_id, 1).
|
213
|
+
and_return result_stub({'blog_id' => 1})
|
214
|
+
dictionary.load
|
215
|
+
connection.should_not_receive(:execute)
|
216
|
+
end
|
217
|
+
|
218
|
+
describe '#each_pair' do
|
219
|
+
it 'should yield data from memory' do
|
220
|
+
hash = {}
|
221
|
+
dictionary.each_pair do |key, value|
|
222
|
+
hash[key] = value
|
223
|
+
end
|
224
|
+
hash.should == {uuid1 => 1, uuid2 => 2, uuid3 => 3}
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
describe '#[]' do
|
229
|
+
it 'should return value from memory' do
|
230
|
+
dictionary[uuid1].should == 1
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
describe '#slice' do
|
235
|
+
it 'should return slice of data in memory' do
|
236
|
+
dictionary.slice(uuid1, uuid2).should == {uuid1 => 1, uuid2 => 2}
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
describe '#keys' do
|
241
|
+
it 'should return keys from memory' do
|
242
|
+
dictionary.keys.should == [uuid1, uuid2, uuid3]
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
describe '#values' do
|
247
|
+
it 'should return values from memory' do
|
248
|
+
dictionary.values.should == [1, 2, 3]
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
context 'with data modified but not loaded in memory' do
|
254
|
+
let(:uuid4) { uuid }
|
255
|
+
|
256
|
+
before do
|
257
|
+
dictionary[uuid1] = -1
|
258
|
+
dictionary[uuid3] = nil
|
259
|
+
dictionary[uuid4] = 4
|
260
|
+
end
|
261
|
+
|
262
|
+
describe '#each_pair' do
|
263
|
+
it 'should override persisted data with unsaved changes' do
|
264
|
+
connection.stub(:execute).
|
265
|
+
with('SELECT FIRST 2 * FROM blog_posts WHERE ? = ? LIMIT 1', :blog_id, 1).
|
266
|
+
and_return result_stub('blog_id' => 1, uuid1 => 1, uuid2 => 2)
|
267
|
+
connection.stub(:execute).
|
268
|
+
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid2, '', :blog_id, 1).
|
269
|
+
and_return result_stub('blog_id' => 1, uuid2 => 2, uuid3 => 3)
|
270
|
+
connection.stub(:execute).
|
271
|
+
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid3, '', :blog_id, 1).
|
272
|
+
and_return result_stub({'blog_id' => 1})
|
273
|
+
hash = {}
|
274
|
+
dictionary.each_pair do |key, value|
|
275
|
+
hash[key] = value
|
276
|
+
end
|
277
|
+
hash.should == {uuid1 => -1, uuid2 => 2, uuid4 => 4}
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
describe '#[]' do
|
282
|
+
it 'should return unsaved changed value' do
|
283
|
+
dictionary[uuid1].should == -1
|
284
|
+
end
|
285
|
+
|
286
|
+
it 'should return nil if value removed' do
|
287
|
+
dictionary[uuid3].should be_nil
|
288
|
+
end
|
289
|
+
|
290
|
+
it 'should return unsaved new value' do
|
291
|
+
dictionary[uuid4].should == 4
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
describe '#slice' do
|
296
|
+
it 'should override loaded slice with unsaved data in memory' do
|
297
|
+
connection.stub(:execute).
|
298
|
+
with('SELECT ? FROM blog_posts WHERE ? = ? LIMIT 1', [uuid1,uuid2,uuid3,uuid4], :blog_id, 1).
|
299
|
+
and_return result_stub(uuid1 => 1, uuid2 => 2, uuid3 => 3)
|
300
|
+
dictionary.slice(uuid1, uuid2, uuid3, uuid4).should ==
|
301
|
+
{uuid1 => -1, uuid2 => 2, uuid4 => 4}
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
describe '#keys' do
|
306
|
+
it 'should override keys that have been added or removed' do
|
307
|
+
connection.should_receive(:execute).
|
308
|
+
with('SELECT FIRST 2 * FROM blog_posts WHERE ? = ? LIMIT 1', :blog_id, 1).
|
309
|
+
and_return result_stub('blog_id' => 1, uuid1 => 1, uuid2 => 2)
|
310
|
+
connection.should_receive(:execute).
|
311
|
+
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid2, '', :blog_id, 1).
|
312
|
+
and_return result_stub('blog_id' => 1, uuid2 => 2, uuid3 => 3)
|
313
|
+
connection.should_receive(:execute).
|
314
|
+
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid3, '', :blog_id, 1).
|
315
|
+
and_return result_stub({'blog_id' => 1})
|
316
|
+
dictionary.keys.should == [uuid1, uuid2, uuid4]
|
317
|
+
end
|
318
|
+
end
|
319
|
+
|
320
|
+
describe '#values' do
|
321
|
+
it 'should override values that have been added or removed' do
|
322
|
+
connection.should_receive(:execute).
|
323
|
+
with('SELECT FIRST 2 * FROM blog_posts WHERE ? = ? LIMIT 1', :blog_id, 1).
|
324
|
+
and_return result_stub('blog_id' => 1, uuid1 => 1, uuid2 => 2)
|
325
|
+
connection.should_receive(:execute).
|
326
|
+
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid2, '', :blog_id, 1).
|
327
|
+
and_return result_stub('blog_id' => 1, uuid2 => 2, uuid3 => 3)
|
328
|
+
connection.should_receive(:execute).
|
329
|
+
with('SELECT FIRST 2 ?..? FROM blog_posts WHERE ? = ? LIMIT 1', uuid3, '', :blog_id, 1).
|
330
|
+
and_return result_stub({'blog_id' => 1})
|
331
|
+
dictionary.values.should == [-1, 2, 4]
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
context 'with serializer/deserializer defined' do
|
337
|
+
let(:dictionary) { PostComments[1] }
|
338
|
+
let(:comment) { {'user' => 'Mat Brown', 'comment' => 'I like big data.'} }
|
339
|
+
|
340
|
+
describe '#save' do
|
341
|
+
it 'should serialize data' do
|
342
|
+
dictionary[4] = comment
|
343
|
+
connection.should_receive(:execute).
|
344
|
+
with(
|
345
|
+
'UPDATE post_comments SET ? = ? WHERE ? = ?',
|
346
|
+
4, comment.to_json, :post_id, 1
|
347
|
+
)
|
348
|
+
dictionary.save
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
describe '#[]' do
|
353
|
+
it 'should return deserialized data' do
|
354
|
+
connection.stub(:execute).with(
|
355
|
+
'SELECT ? FROM post_comments WHERE ? = ? LIMIT 1',
|
356
|
+
[4], :post_id, 1
|
357
|
+
).and_return result_stub(4 => comment.to_json)
|
358
|
+
dictionary[4].should == comment
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
describe '#slice' do
|
363
|
+
it 'should return deserialized values' do
|
364
|
+
connection.stub(:execute).with(
|
365
|
+
'SELECT ? FROM post_comments WHERE ? = ? LIMIT 1',
|
366
|
+
[4, 5], :post_id, 1
|
367
|
+
).and_return result_stub(4 => comment.to_json)
|
368
|
+
dictionary.slice(4, 5).should == {4 => comment}
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
describe '#load' do
|
373
|
+
it 'should retain deserialized values in memory' do
|
374
|
+
connection.stub(:execute).with(
|
375
|
+
'SELECT FIRST 1000 * FROM post_comments WHERE ? = ? LIMIT 1',
|
376
|
+
:post_id, 1
|
377
|
+
).and_return result_stub(4 => comment.to_json)
|
378
|
+
dictionary.load
|
379
|
+
connection.should_not_receive(:execute)
|
380
|
+
dictionary[4].should == comment
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
describe '#each_pair' do
|
385
|
+
it 'should yield deserialized values' do
|
386
|
+
connection.stub(:execute).
|
387
|
+
with(
|
388
|
+
'SELECT FIRST 1000 * FROM post_comments WHERE ? = ? LIMIT 1',
|
389
|
+
:post_id, 1
|
390
|
+
).and_return result_stub('post_id' => 1, 4 => comment.to_json)
|
391
|
+
dictionary.each_pair.map { |column, comment| comment }.first.
|
392
|
+
should == comment
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
describe '#values' do
|
397
|
+
it 'should return deserialized values' do
|
398
|
+
connection.stub(:execute).
|
399
|
+
with(
|
400
|
+
'SELECT FIRST 1000 * FROM post_comments WHERE ? = ? LIMIT 1',
|
401
|
+
:post_id, 1
|
402
|
+
).and_return result_stub('post_id' => 1, 4 => comment.to_json)
|
403
|
+
dictionary.values.should == [comment]
|
404
|
+
end
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
private
|
409
|
+
|
410
|
+
def uuid
|
411
|
+
SimpleUUID::UUID.new.to_guid
|
412
|
+
end
|
413
|
+
end
|