onyx-cache-money 0.2.5.7 → 0.2.5.8
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/cash/accessor.rb +7 -16
- data/lib/cash/finders.rb +4 -4
- data/lib/cash/query/abstract.rb +16 -45
- data/lib/cash/query/select.rb +2 -3
- data/spec/cash/finders_spec.rb +48 -89
- metadata +2 -2
data/lib/cash/accessor.rb
CHANGED
@@ -11,15 +11,11 @@ module Cash
|
|
11
11
|
def fetch(keys, options = {}, &block)
|
12
12
|
case keys
|
13
13
|
when Array
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
actual_missed_keys = missed_cache_keys.collect {|missed_cache_key| cache_and_actual_keys[missed_cache_key]}
|
20
|
-
missed_values = block.call(actual_missed_keys)
|
21
|
-
|
22
|
-
hits.merge!(missed_cache_keys.zip(Array(missed_values)).to_hash)
|
14
|
+
keys = keys.collect { |key| cache_key(key) }
|
15
|
+
hits = repository.get_multi(keys)
|
16
|
+
if (missed_keys = keys - hits.keys).any?
|
17
|
+
missed_values = block.call(missed_keys)
|
18
|
+
hits.merge!(missed_keys.zip(Array(missed_values)).to_hash)
|
23
19
|
end
|
24
20
|
hits
|
25
21
|
else
|
@@ -30,11 +26,7 @@ module Cash
|
|
30
26
|
def get(keys, options = {}, &block)
|
31
27
|
case keys
|
32
28
|
when Array
|
33
|
-
fetch(keys, options)
|
34
|
-
results = yield(missed_keys)
|
35
|
-
results.each_with_index {|result, index| add(missed_keys[index], result, options)}
|
36
|
-
results
|
37
|
-
end
|
29
|
+
fetch(keys, options, &block)
|
38
30
|
else
|
39
31
|
fetch(keys, options) do
|
40
32
|
if block_given?
|
@@ -74,8 +66,7 @@ module Cash
|
|
74
66
|
end
|
75
67
|
|
76
68
|
def cache_key(key)
|
77
|
-
|
78
|
-
ready ? key : "#{name}:#{cache_config.version}/#{key.to_s.gsub(' ', '+')}"
|
69
|
+
"#{name}:#{cache_config.version}/#{key.to_s.gsub(' ', '+')}"
|
79
70
|
end
|
80
71
|
end
|
81
72
|
|
data/lib/cash/finders.rb
CHANGED
@@ -10,7 +10,7 @@ module Cash
|
|
10
10
|
def self.extended(active_record_class)
|
11
11
|
class << active_record_class
|
12
12
|
alias_method_chain :find_every, :cache
|
13
|
-
|
13
|
+
alias_method_chain :find_from_ids, :cache
|
14
14
|
alias_method_chain :calculate, :cache
|
15
15
|
end
|
16
16
|
end
|
@@ -25,9 +25,9 @@ module Cash
|
|
25
25
|
end
|
26
26
|
|
27
27
|
# User.find(1), User.find(1, 2, 3), User.find([1, 2, 3]), User.find([])
|
28
|
-
|
29
|
-
|
30
|
-
|
28
|
+
def find_from_ids_with_cache(ids, options)
|
29
|
+
Query::PrimaryKey.perform(self, ids, options, scope(:find))
|
30
|
+
end
|
31
31
|
|
32
32
|
# User.count(:all), User.count, User.sum(...)
|
33
33
|
def calculate_with_cache(operation, column_name, options = {})
|
data/lib/cash/query/abstract.rb
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
module Cash
|
2
2
|
module Query
|
3
3
|
class Abstract
|
4
|
-
delegate :with_exclusive_scope, :get, :
|
5
|
-
:find_every_without_cache, :cache_key, :columns_hash, :quote_value, :to => :@active_record
|
4
|
+
delegate :with_exclusive_scope, :get, :table_name, :indices, :find_from_ids_without_cache, :cache_key, :columns_hash, :to => :@active_record
|
6
5
|
|
7
6
|
def self.perform(*args)
|
8
7
|
new(*args).perform
|
@@ -52,7 +51,7 @@ module Cash
|
|
52
51
|
def cacheable?(*optionss)
|
53
52
|
optionss.each { |options| return unless safe_options_for_cache?(options) }
|
54
53
|
partial_indices = optionss.collect { |options| attribute_value_pairs_for_conditions(options[:conditions]) }
|
55
|
-
return if partial_indices.
|
54
|
+
return if partial_indices.include?(nil)
|
56
55
|
attribute_value_pairs = partial_indices.sum.sort { |x, y| x[0] <=> y[0] }
|
57
56
|
if index = indexed_on?(attribute_value_pairs.collect { |pair| pair[0] })
|
58
57
|
if index.matches?(self)
|
@@ -71,20 +70,7 @@ module Cash
|
|
71
70
|
end
|
72
71
|
|
73
72
|
def cache_keys(attribute_value_pairs)
|
74
|
-
|
75
|
-
cache_keys.size == 1 ? cache_keys.first : cache_keys
|
76
|
-
end
|
77
|
-
|
78
|
-
def collect_cache_keys(pairs)
|
79
|
-
return [] if pairs.empty?
|
80
|
-
key, values = pairs.shift
|
81
|
-
Array(values).inject([]) do |memo,value|
|
82
|
-
partial_keys = collect_cache_keys(pairs.clone)
|
83
|
-
|
84
|
-
memo << "#{key}/#{value}" if partial_keys.empty?
|
85
|
-
partial_keys.each { |partial_key| memo << "#{key}/#{value}/#{partial_key}" }
|
86
|
-
memo
|
87
|
-
end
|
73
|
+
attribute_value_pairs.flatten.join('/')
|
88
74
|
end
|
89
75
|
|
90
76
|
def safe_options_for_cache?(options)
|
@@ -95,10 +81,13 @@ module Cash
|
|
95
81
|
def attribute_value_pairs_for_conditions(conditions)
|
96
82
|
case conditions
|
97
83
|
when Hash
|
84
|
+
# avoid key too long error when passing in array of ids
|
85
|
+
return nil if conditions.values.any? {|value| value.is_a?(Array)}
|
98
86
|
conditions.to_a.collect { |key, value| [key.to_s, value] }
|
99
87
|
when String
|
100
88
|
parse_indices_from_condition(conditions)
|
101
89
|
when Array
|
90
|
+
# do not cache find(:conditions => ["... :attr", {:attr => 1}]
|
102
91
|
return nil if conditions.last.is_a?(Hash)
|
103
92
|
parse_indices_from_condition(*conditions)
|
104
93
|
when NilClass
|
@@ -108,19 +97,17 @@ module Cash
|
|
108
97
|
|
109
98
|
AND = /\s+AND\s+/i
|
110
99
|
TABLE_AND_COLUMN = /(?:(?:`|")?(\w+)(?:`|")?\.)?(?:`|")?(\w+)(?:`|")?/ # Matches: `users`.id, `users`.`id`, users.id, id
|
111
|
-
VALUE = /'?(\d+|\?|(?:(?:[^']|'')
|
112
|
-
KEY_EQ_VALUE = /^\(?#{TABLE_AND_COLUMN}\s
|
113
|
-
ORDER = /^#{TABLE_AND_COLUMN}\s*(ASC|DESC)?$/i
|
100
|
+
VALUE = /'?(\d+|\?|(?:(?:[^']|'')*))'?/ # Matches: 123, ?, '123', '12''3'
|
101
|
+
KEY_EQ_VALUE = /^\(?#{TABLE_AND_COLUMN}\s+=\s+#{VALUE}\)?$/ # Matches: KEY = VALUE, (KEY = VALUE)
|
102
|
+
ORDER = /^#{TABLE_AND_COLUMN}\s*(ASC|DESC)?$/i # Matches: COLUMN ASC, COLUMN DESC, COLUMN
|
114
103
|
|
115
104
|
def parse_indices_from_condition(conditions = '', *values)
|
116
105
|
values = values.dup
|
117
106
|
conditions.split(AND).inject([]) do |indices, condition|
|
118
|
-
matched, table_name, column_name,
|
107
|
+
matched, table_name, column_name, sql_value = *(KEY_EQ_VALUE.match(condition))
|
119
108
|
if matched
|
120
|
-
|
121
|
-
|
122
|
-
end
|
123
|
-
indices << [column_name, actual_values]
|
109
|
+
value = sql_value == '?' ? values.shift : columns_hash[column_name].type_cast(sql_value)
|
110
|
+
indices << [column_name, value]
|
124
111
|
else
|
125
112
|
return nil
|
126
113
|
end
|
@@ -165,30 +152,14 @@ module Cash
|
|
165
152
|
objects
|
166
153
|
else
|
167
154
|
cache_keys = objects.collect { |id| "id/#{id}" }
|
168
|
-
objects = get(cache_keys
|
155
|
+
objects = get(cache_keys, &method(:find_from_keys))
|
169
156
|
convert_to_array(cache_keys, objects)
|
170
157
|
end
|
171
158
|
end
|
172
159
|
|
173
|
-
def find_from_keys(missing_keys
|
174
|
-
|
175
|
-
|
176
|
-
keys_values = missing_keys_pairs.inject({}) do |memo, missing_keys_pair|
|
177
|
-
while missing_keys_pair.any?
|
178
|
-
key = missing_keys_pair.shift
|
179
|
-
memo[key] ||= []
|
180
|
-
memo[key] << missing_keys_pair.shift
|
181
|
-
end
|
182
|
-
memo
|
183
|
-
end
|
184
|
-
|
185
|
-
conditions = keys_values.collect do |key,values|
|
186
|
-
converted_values = values.collect {|value| quote_value(value, columns_hash[key])}
|
187
|
-
quoted_table_and_column_name = "#{quoted_table_name}.#{connection.quote_column_name(key)}"
|
188
|
-
converted_values.size == 1 ? "#{quoted_table_and_column_name} = #{converted_values}" : "#{quoted_table_and_column_name} IN (#{converted_values.join(',')})"
|
189
|
-
end
|
190
|
-
|
191
|
-
find_every_without_cache(options.merge(:conditions => conditions.join(" AND ")))
|
160
|
+
def find_from_keys(*missing_keys)
|
161
|
+
missing_ids = Array(missing_keys).flatten.collect { |key| key.split('/')[2].to_i }
|
162
|
+
find_from_ids_without_cache(missing_ids, {})
|
192
163
|
end
|
193
164
|
end
|
194
165
|
end
|
data/lib/cash/query/select.rb
CHANGED
@@ -4,9 +4,8 @@ module Cash
|
|
4
4
|
delegate :find_every_without_cache, :to => :@active_record
|
5
5
|
|
6
6
|
protected
|
7
|
-
def miss(
|
8
|
-
|
9
|
-
misses = find_from_keys(missed_keys, miss_options)
|
7
|
+
def miss(_, miss_options)
|
8
|
+
find_every_without_cache(miss_options)
|
10
9
|
end
|
11
10
|
|
12
11
|
def uncacheable
|
data/spec/cash/finders_spec.rb
CHANGED
@@ -170,9 +170,8 @@ module Cash
|
|
170
170
|
|
171
171
|
describe '#find(1, :conditions => ...)' do
|
172
172
|
it "does not use the database" do
|
173
|
-
Story.create!
|
174
173
|
story = Story.create!
|
175
|
-
character = Character.create!(:name => name = 'barbara', :story_id => story
|
174
|
+
character = Character.create!(:name => name = 'barbara', :story_id => story)
|
176
175
|
mock(Character.connection).execute.never
|
177
176
|
Character.send :with_scope, :find => { :conditions => { :story_id => story.id } } do
|
178
177
|
Character.find(character.id, :conditions => { :name => name }).should == character
|
@@ -212,6 +211,19 @@ module Cash
|
|
212
211
|
end
|
213
212
|
end
|
214
213
|
|
214
|
+
describe "#find_all_by_id" do
|
215
|
+
it "should not create a key over 250 characters" do
|
216
|
+
150.times do
|
217
|
+
Story.create!
|
218
|
+
end
|
219
|
+
ids = Story.find(:all).map(&:id)
|
220
|
+
$memcache.flush_all
|
221
|
+
lambda do
|
222
|
+
Story.find_all_by_id(ids)
|
223
|
+
end.should_not raise_error(ArgumentError)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
215
227
|
describe '#find(:all)' do
|
216
228
|
it "uses the database, not the cache" do
|
217
229
|
character = Character.create!
|
@@ -228,6 +240,28 @@ module Cash
|
|
228
240
|
Story.find(:all, :conditions => { :title => story1.title }).should == [story1, story2]
|
229
241
|
end
|
230
242
|
end
|
243
|
+
|
244
|
+
it "should not create a key over 250 characters with hash for conditions" do
|
245
|
+
150.times do
|
246
|
+
Story.create!
|
247
|
+
end
|
248
|
+
ids = Story.find(:all).map(&:id)
|
249
|
+
$memcache.flush_all
|
250
|
+
lambda do
|
251
|
+
Story.find(:all, :conditions => {:id => ids})
|
252
|
+
end.should_not raise_error(ArgumentError)
|
253
|
+
end
|
254
|
+
|
255
|
+
it "should not create a key over 250 characters with array for conditions" do
|
256
|
+
150.times do
|
257
|
+
Story.create!
|
258
|
+
end
|
259
|
+
ids = Story.find(:all).map(&:id)
|
260
|
+
$memcache.flush_all
|
261
|
+
lambda do
|
262
|
+
Story.find(:all, :conditions => ["id IN (?)", ids])
|
263
|
+
end.should_not raise_error(ArgumentError)
|
264
|
+
end
|
231
265
|
end
|
232
266
|
|
233
267
|
describe '#find(:all, :limit => ..., :offset => ...)' do
|
@@ -262,7 +296,7 @@ module Cash
|
|
262
296
|
end
|
263
297
|
end
|
264
298
|
|
265
|
-
describe '#find_by_attr' do
|
299
|
+
describe '#find_by_attr' do
|
266
300
|
describe 'on indexed attributes' do
|
267
301
|
describe '#find_by_id(id)' do
|
268
302
|
it "does not use the database" do
|
@@ -335,102 +369,27 @@ module Cash
|
|
335
369
|
Story.fetch("title/#{@story.title}").should == [@story.id]
|
336
370
|
end
|
337
371
|
end
|
338
|
-
|
372
|
+
|
339
373
|
describe '#find(:conditions => ["... :attr", {:attr => 1}])' do
|
340
|
-
it 'retrieves
|
341
|
-
Story.
|
374
|
+
it 'never retrieves from cache' do
|
375
|
+
mock(Story).add.never
|
376
|
+
Story.find(:all, :conditions => ["id = :id", {:id => @story.id}])
|
377
|
+
end
|
378
|
+
|
379
|
+
it 'populates the cache' do
|
380
|
+
pending "remove test above when this is fixed"
|
381
|
+
Story.find(:all, :conditions => ["id = :id", {:id => @story.id}])
|
382
|
+
Story.fetch("id/#{@story.id}").should == @story
|
342
383
|
end
|
343
384
|
end
|
344
|
-
|
385
|
+
|
345
386
|
describe '#find(1)' do
|
346
387
|
it 'populates the cache' do
|
347
388
|
Story.find(@story.id)
|
348
389
|
Story.fetch("id/#{@story.id}").should == [@story]
|
349
390
|
end
|
350
391
|
end
|
351
|
-
|
352
|
-
describe '#find(1,2)' do
|
353
|
-
it 'populates the cache' do
|
354
|
-
another_story = Story.create!
|
355
|
-
$memcache.flush_all
|
356
|
-
|
357
|
-
Story.find(@story.id, another_story.id).should == [@story, another_story]
|
358
|
-
Story.fetch("id/#{@story.id}").should == @story
|
359
|
-
Story.fetch("id/#{another_story.id}").should == another_story
|
360
|
-
end
|
361
|
-
|
362
|
-
it "populates the cache and retrieves from the cache" do
|
363
|
-
story1 = Story.create!
|
364
|
-
story2 = Story.create!
|
365
|
-
story3 = Story.create!
|
366
|
-
$memcache.flush_all
|
367
|
-
|
368
|
-
Story.find(story1.id, story2.id, story3.id).should == [story1, story2, story3]
|
369
|
-
mock(Story.connection).execute.never
|
370
|
-
Story.find(story1.id, story2.id).should == [story1, story2]
|
371
|
-
end
|
372
|
-
|
373
|
-
it "uses database for missing keys" do
|
374
|
-
story1 = Story.create!
|
375
|
-
story2 = Story.create!
|
376
|
-
story3 = Story.create!
|
377
|
-
$memcache.flush_all
|
378
392
|
|
379
|
-
Story.find(story1.id, story3.id).should == [story1, story3]
|
380
|
-
Story.fetch("id/#{story2.id}").should be_nil
|
381
|
-
|
382
|
-
mock(Story).find_every_without_cache(:limit => nil, :conditions => "\"stories\".\"id\" = #{story2.id}") do
|
383
|
-
story2
|
384
|
-
end
|
385
|
-
Story.find(story1.id, story2.id)
|
386
|
-
end
|
387
|
-
|
388
|
-
it "populates and retrieves from cache when passing in a hash" do
|
389
|
-
story1 = Story.create!
|
390
|
-
story2 = Story.create!
|
391
|
-
$memcache.flush_all
|
392
|
-
|
393
|
-
Story.find(:all, :conditions => {:id => [story1.id, story2.id]})
|
394
|
-
mock(Story.connection).execute.never
|
395
|
-
Story.find(story1.id, story2.id)
|
396
|
-
end
|
397
|
-
|
398
|
-
it "populates and retrieves from cache when passing in a parametrized conditions" do
|
399
|
-
story1 = Story.create!
|
400
|
-
story2 = Story.create!
|
401
|
-
$memcache.flush_all
|
402
|
-
|
403
|
-
Story.find(:all, :conditions => ["id IN (?,?)", story1.id, story2.id])
|
404
|
-
mock(Story.connection).execute.never
|
405
|
-
Story.find(story1.id, story2.id)
|
406
|
-
end
|
407
|
-
|
408
|
-
it "populates and retrieves from cache when passing in a list of non-numeric keys" do
|
409
|
-
pending "need to fix regex to properly parse this"
|
410
|
-
story1 = Story.create! :title => 'one'
|
411
|
-
story2 = Story.create! :title => 'two'
|
412
|
-
$memcache.flush_all
|
413
|
-
|
414
|
-
Story.find(:all, :conditions => ["title IN ('one', 'two')"])
|
415
|
-
# We need to add this extra call as cache money will always go to the database
|
416
|
-
# one more time when finding by a non-id field first
|
417
|
-
Story.find_all_by_title(['one', 'two'])
|
418
|
-
mock(Story.connection).execute.never
|
419
|
-
Story.find(story1.id, story2.id)
|
420
|
-
end
|
421
|
-
|
422
|
-
it "should not create a key over 250 characters on a find_all_by_ids" do
|
423
|
-
75.times do
|
424
|
-
Story.create!
|
425
|
-
end
|
426
|
-
ids = Story.find(:all).map(&:id)
|
427
|
-
$memcache.flush_all
|
428
|
-
lambda do
|
429
|
-
Story.find(:all, :conditions => {:id => ids})
|
430
|
-
end.should_not raise_error(ArgumentError)
|
431
|
-
end
|
432
|
-
end
|
433
|
-
|
434
393
|
describe 'when there is a with_scope' do
|
435
394
|
it "uses the database, not the cache" do
|
436
395
|
Story.send :with_scope, :find => { :conditions => { :title => @story.title }} do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: onyx-cache-money
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.5.
|
4
|
+
version: 0.2.5.8
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Kallen
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-05-
|
12
|
+
date: 2009-05-28 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|