cequel 0.4.2 → 0.5.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.
@@ -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