cequel 0.4.2 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -10,7 +10,7 @@ module Cequel
10
10
  config_path = Rails.root.join('config/cequel.yml').to_s
11
11
 
12
12
  if File.exist?(config_path)
13
- yaml = YAML.load_file(config_path)[Rails.env]
13
+ yaml = YAML::load(ERB.new(IO.read(config_path)).result)[Rails.env]
14
14
  Cequel::Model.configure(yaml.symbolize_keys) if yaml
15
15
  end
16
16
 
@@ -0,0 +1,169 @@
1
+ module Cequel
2
+
3
+ module Model
4
+
5
+ class ReadableDictionary
6
+
7
+ class <<self
8
+
9
+ attr_writer :column_family, :default_batch_size
10
+
11
+ def key_alias
12
+ @key_alias ||= :KEY
13
+ end
14
+
15
+ def key_type
16
+ @key_type ||= :text
17
+ end
18
+
19
+ def comparator
20
+ @comparator ||= :text
21
+ end
22
+
23
+ def validation
24
+ :counter
25
+ end
26
+
27
+ def key(key_alias, type)
28
+ @key_alias, @key_type = key_alias, type
29
+
30
+ module_eval(<<-RUBY)
31
+ def #{key_alias.downcase}
32
+ @key
33
+ end
34
+ RUBY
35
+ end
36
+
37
+ def columns(comparator)
38
+ @comparator = comparator
39
+ end
40
+
41
+ def column_family
42
+ return @column_family if @column_family
43
+ self.column_family_name = name.underscore.to_sym
44
+ @column_family
45
+ end
46
+
47
+ def column_family_name=(column_family_name)
48
+ self.column_family = Cequel::Model.keyspace[column_family_name]
49
+ end
50
+
51
+ def default_batch_size
52
+ @default_batch_size || 1000
53
+ end
54
+
55
+ def [](key)
56
+ new(key)
57
+ end
58
+ private :new
59
+
60
+ def load(*keys)
61
+ options = keys.extract_options!
62
+ keys.flatten!
63
+ batch_size = options[:columns] || options[:batch_size] ||
64
+ default_batch_size
65
+ column_family.select(:first => batch_size).
66
+ where(key_alias.to_s => keys).
67
+ map { |row| new(row.delete(key_alias.to_s), row) }
68
+ end
69
+
70
+ end
71
+
72
+ include Enumerable
73
+
74
+ def initialize(key, row = nil)
75
+ @key = key
76
+ setup(row)
77
+ end
78
+
79
+ def [](column)
80
+ if @loaded
81
+ @row[column]
82
+ else
83
+ scope.select(column).first[column]
84
+ end
85
+ end
86
+
87
+ def keys
88
+ @loaded ? @row.keys : each_pair.map { |key, value| key }
89
+ end
90
+
91
+ def values
92
+ @loaded ? @row.values : each_pair.map { |key, value| value }
93
+ end
94
+
95
+ def slice(*columns)
96
+ if @loaded
97
+ @row.slice(*columns)
98
+ else
99
+ deserialize_row(load_raw_slice(columns))
100
+ end
101
+ end
102
+
103
+ def key?(column)
104
+ @row.key?(column) || load_raw_slice([column])[column].present?
105
+ end
106
+
107
+ def each_pair(options = {}, &block)
108
+ return Enumerator.new(self, :each_pair, options) unless block
109
+ return @row.each_pair(&block) if @loaded
110
+ batch_size = options[:batch_size] || self.class.default_batch_size
111
+ each_slice(batch_size) do |batch_results|
112
+ batch_results.each_pair(&block)
113
+ end
114
+ end
115
+
116
+ def each(&block)
117
+ each_pair(&block)
118
+ end
119
+
120
+ def each_slice(batch_size)
121
+ batch_scope = scope.select(:first => batch_size)
122
+ key_alias = self.class.key_alias
123
+ last_key = nil
124
+ begin
125
+ batch_results = batch_scope.first
126
+ batch_results.delete(key_alias)
127
+ result_length = batch_results.length
128
+ batch_results.delete(last_key) unless last_key.nil?
129
+ yield deserialize_row(batch_results)
130
+ last_key = batch_results.keys.last
131
+ batch_scope = batch_scope.select(:from => last_key)
132
+ end while result_length == batch_size
133
+ end
134
+
135
+ def load
136
+ @row = {}
137
+ each_pair { |column, value| @row[column] = value }
138
+ @loaded = true
139
+ self
140
+ end
141
+
142
+ def loaded?
143
+ !!@loaded
144
+ end
145
+
146
+ private
147
+
148
+ def setup(init_row = nil)
149
+ @row = deserialize_row(init_row || {})
150
+ @loaded = !!init_row
151
+ end
152
+
153
+ def scope
154
+ self.class.column_family.where(self.class.key_alias => @key)
155
+ end
156
+
157
+ def load_raw_slice(columns)
158
+ row = scope.select(*columns).first.except(self.class.key_alias)
159
+ end
160
+
161
+ def deserialize_row(row)
162
+ row
163
+ end
164
+
165
+ end
166
+
167
+ end
168
+
169
+ end
@@ -16,7 +16,7 @@ module Cequel
16
16
  end
17
17
 
18
18
  def all
19
- @_cequel.current_scope || @_cequel.default_scope || empty_scope
19
+ current_scope || @_cequel.default_scope || empty_scope
20
20
  end
21
21
 
22
22
  def select(*rows)
@@ -25,12 +25,12 @@ module Cequel
25
25
 
26
26
  def with_scope(scope)
27
27
  @_cequel.synchronize do
28
- old_scope = @_cequel.current_scope
28
+ old_scope = current_scope
29
29
  begin
30
- @_cequel.current_scope = scope
30
+ self.current_scope = scope
31
31
  yield
32
32
  ensure
33
- @_cequel.current_scope = old_scope
33
+ self.current_scope = old_scope
34
34
  end
35
35
  end
36
36
  end
@@ -41,6 +41,18 @@ module Cequel
41
41
  Scope.new(self, [column_family])
42
42
  end
43
43
 
44
+ def current_scope
45
+ ::Thread.current[current_scope_key]
46
+ end
47
+
48
+ def current_scope=(scope)
49
+ ::Thread.current[current_scope_key] = scope
50
+ end
51
+
52
+ def current_scope_key
53
+ :"cequel-current_scope-#{object_id}"
54
+ end
55
+
44
56
  end
45
57
 
46
58
  end
@@ -1,3 +1,3 @@
1
1
  module Cequel
2
- VERSION = '0.4.2'
2
+ VERSION = '0.5.0'
3
3
  end
@@ -93,6 +93,32 @@ describe Cequel::DataSet do
93
93
  end
94
94
  end
95
95
 
96
+ describe '#increment' do
97
+ it 'should increment counter columns' do
98
+ connection.should_receive(:execute).with(
99
+ 'UPDATE comment_counts SET ? = ? + ?, ? = ? + ? WHERE ? = ?',
100
+ 'somepost', 'somepost', 1,
101
+ 'anotherpost', 'anotherpost', 2,
102
+ 'blog_id', 'myblog'
103
+ )
104
+ cequel[:comment_counts].where('blog_id' => 'myblog').
105
+ increment('somepost' => 1, 'anotherpost' => 2)
106
+ end
107
+ end
108
+
109
+ describe '#decrement' do
110
+ it 'should decrement counter columns' do
111
+ connection.should_receive(:execute).with(
112
+ 'UPDATE comment_counts SET ? = ? - ?, ? = ? - ? WHERE ? = ?',
113
+ 'somepost', 'somepost', 1,
114
+ 'anotherpost', 'anotherpost', 2,
115
+ 'blog_id', 'myblog'
116
+ )
117
+ cequel[:comment_counts].where('blog_id' => 'myblog').
118
+ decrement('somepost' => 1, 'anotherpost' => 2)
119
+ end
120
+ end
121
+
96
122
  describe '#delete' do
97
123
  it 'should send basic delete statement' do
98
124
  connection.should_receive(:execute).
@@ -48,7 +48,7 @@ CQL
48
48
  describe '::logger=' do
49
49
  let(:io) { StringIO.new }
50
50
  let(:logger) { Logger.new(io) }
51
-
51
+
52
52
  before do
53
53
  logger.level = Logger::DEBUG
54
54
  cequel.logger = logger
@@ -0,0 +1,94 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Cequel::Model::Counter do
4
+ let(:counter) { CommentCounts[1] }
5
+ let(:dictionary) { counter }
6
+ let(:uuid1) { CassandraCQL::UUID.new }
7
+ let(:uuid2) { CassandraCQL::UUID.new }
8
+
9
+ it_behaves_like 'readable dictionary'
10
+
11
+ describe '#increment' do
12
+ it 'should increment single column by default 1' do
13
+ connection.should_receive(:execute).with(
14
+ 'UPDATE comment_counts SET ? = ? + ? WHERE ? = ?',
15
+ uuid1, uuid1, 1, :blog_id, 1
16
+ )
17
+ dictionary.increment(uuid1)
18
+ end
19
+
20
+ it 'should increment single column by given value' do
21
+ connection.should_receive(:execute).with(
22
+ 'UPDATE comment_counts SET ? = ? + ? WHERE ? = ?',
23
+ uuid1, uuid1, 4, :blog_id, 1
24
+ )
25
+ dictionary.increment(uuid1, 4)
26
+ end
27
+
28
+ it 'should increment multiple columns by default value' do
29
+ connection.should_receive(:execute).with(
30
+ 'UPDATE comment_counts SET ? = ? + ?, ? = ? + ? WHERE ? = ?',
31
+ uuid1, uuid1, 1, uuid2, uuid2, 1, :blog_id, 1
32
+ )
33
+ dictionary.increment([uuid1, uuid2])
34
+ end
35
+
36
+ it 'should increment multiple columns by given value' do
37
+ connection.should_receive(:execute).with(
38
+ 'UPDATE comment_counts SET ? = ? + ?, ? = ? + ? WHERE ? = ?',
39
+ uuid1, uuid1, 4, uuid2, uuid2, 4, :blog_id, 1
40
+ )
41
+ dictionary.increment([uuid1, uuid2], 4)
42
+ end
43
+
44
+ it 'should increment multiple columns by given deltas' do
45
+ connection.should_receive(:execute).with(
46
+ 'UPDATE comment_counts SET ? = ? + ?, ? = ? + ? WHERE ? = ?',
47
+ uuid1, uuid1, 4, uuid2, uuid2, 2, :blog_id, 1
48
+ )
49
+ dictionary.increment(uuid1 => 4, uuid2 => 2)
50
+ end
51
+ end
52
+
53
+ describe '#decrement' do
54
+ it 'should increment single column by default 1' do
55
+ connection.should_receive(:execute).with(
56
+ 'UPDATE comment_counts SET ? = ? - ? WHERE ? = ?',
57
+ uuid1, uuid1, 1, :blog_id, 1
58
+ )
59
+ dictionary.decrement(uuid1)
60
+ end
61
+
62
+ it 'should increment single column by given value' do
63
+ connection.should_receive(:execute).with(
64
+ 'UPDATE comment_counts SET ? = ? - ? WHERE ? = ?',
65
+ uuid1, uuid1, 4, :blog_id, 1
66
+ )
67
+ dictionary.decrement(uuid1, 4)
68
+ end
69
+
70
+ it 'should increment multiple columns by default value' do
71
+ connection.should_receive(:execute).with(
72
+ 'UPDATE comment_counts SET ? = ? - ?, ? = ? - ? WHERE ? = ?',
73
+ uuid1, uuid1, 1, uuid2, uuid2, 1, :blog_id, 1
74
+ )
75
+ dictionary.decrement([uuid1, uuid2])
76
+ end
77
+
78
+ it 'should increment multiple columns by given value' do
79
+ connection.should_receive(:execute).with(
80
+ 'UPDATE comment_counts SET ? = ? - ?, ? = ? - ? WHERE ? = ?',
81
+ uuid1, uuid1, 4, uuid2, uuid2, 4, :blog_id, 1
82
+ )
83
+ dictionary.decrement([uuid1, uuid2], 4)
84
+ end
85
+
86
+ it 'should increment multiple columns by given deltas' do
87
+ connection.should_receive(:execute).with(
88
+ 'UPDATE comment_counts SET ? = ? - ?, ? = ? - ? WHERE ? = ?',
89
+ uuid1, uuid1, 4, uuid2, uuid2, 2, :blog_id, 1
90
+ )
91
+ dictionary.decrement(uuid1 => 4, uuid2 => 2)
92
+ end
93
+ end
94
+ end
@@ -6,6 +6,8 @@ describe Cequel::Model::Dictionary do
6
6
  let(:uuid3) { uuid }
7
7
  let(:dictionary) { BlogPosts[1] }
8
8
 
9
+ it_behaves_like 'readable dictionary'
10
+
9
11
  describe '#save' do
10
12
  before do
11
13
  connection.stub(:execute).
@@ -125,131 +127,6 @@ describe Cequel::Model::Dictionary do
125
127
  end
126
128
  end
127
129
 
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
130
  context 'with data modified but not loaded in memory' do
254
131
  let(:uuid4) { uuid }
255
132