onyx-cache-money 0.2.5.7 → 0.2.5.8
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/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
|
|