onyx-cache-money 0.2.5.6 → 0.2.5.7
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/cash/accessor.rb +16 -7
- data/lib/cash/finders.rb +4 -4
- data/lib/cash/query/abstract.rb +46 -13
- data/lib/cash/query/select.rb +3 -2
- data/spec/cash/finders_spec.rb +90 -1
- metadata +2 -2
data/lib/cash/accessor.rb
CHANGED
@@ -11,11 +11,15 @@ module Cash
|
|
11
11
|
def fetch(keys, options = {}, &block)
|
12
12
|
case keys
|
13
13
|
when Array
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
14
|
+
cache_and_actual_keys = keys.inject({}) { |memo, key| memo[cache_key(key)] = key; memo }
|
15
|
+
cache_keys = keys.collect {|key| cache_key(key)}
|
16
|
+
|
17
|
+
hits = repository.get_multi(cache_keys)
|
18
|
+
if (missed_cache_keys = cache_keys - hits.keys).any?
|
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)
|
19
23
|
end
|
20
24
|
hits
|
21
25
|
else
|
@@ -26,7 +30,11 @@ module Cash
|
|
26
30
|
def get(keys, options = {}, &block)
|
27
31
|
case keys
|
28
32
|
when Array
|
29
|
-
fetch(keys, options
|
33
|
+
fetch(keys, options) do |missed_keys|
|
34
|
+
results = yield(missed_keys)
|
35
|
+
results.each_with_index {|result, index| add(missed_keys[index], result, options)}
|
36
|
+
results
|
37
|
+
end
|
30
38
|
else
|
31
39
|
fetch(keys, options) do
|
32
40
|
if block_given?
|
@@ -66,7 +74,8 @@ module Cash
|
|
66
74
|
end
|
67
75
|
|
68
76
|
def cache_key(key)
|
69
|
-
|
77
|
+
ready = key =~ /#{name}:#{cache_config.version}/
|
78
|
+
ready ? key : "#{name}:#{cache_config.version}/#{key.to_s.gsub(' ', '+')}"
|
70
79
|
end
|
71
80
|
end
|
72
81
|
|
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
|
-
alias_method_chain :find_from_ids, :cache
|
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
|
-
def find_from_ids_with_cache(ids, options)
|
29
|
-
|
30
|
-
end
|
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,7 +1,8 @@
|
|
1
1
|
module Cash
|
2
2
|
module Query
|
3
3
|
class Abstract
|
4
|
-
delegate :with_exclusive_scope, :get, :
|
4
|
+
delegate :with_exclusive_scope, :get, :quoted_table_name, :connection, :indices,
|
5
|
+
:find_every_without_cache, :cache_key, :columns_hash, :quote_value, :to => :@active_record
|
5
6
|
|
6
7
|
def self.perform(*args)
|
7
8
|
new(*args).perform
|
@@ -51,7 +52,7 @@ module Cash
|
|
51
52
|
def cacheable?(*optionss)
|
52
53
|
optionss.each { |options| return unless safe_options_for_cache?(options) }
|
53
54
|
partial_indices = optionss.collect { |options| attribute_value_pairs_for_conditions(options[:conditions]) }
|
54
|
-
return if partial_indices.include?(nil)
|
55
|
+
return if partial_indices.flatten.include?(nil)
|
55
56
|
attribute_value_pairs = partial_indices.sum.sort { |x, y| x[0] <=> y[0] }
|
56
57
|
if index = indexed_on?(attribute_value_pairs.collect { |pair| pair[0] })
|
57
58
|
if index.matches?(self)
|
@@ -70,7 +71,20 @@ module Cash
|
|
70
71
|
end
|
71
72
|
|
72
73
|
def cache_keys(attribute_value_pairs)
|
73
|
-
attribute_value_pairs
|
74
|
+
cache_keys = collect_cache_keys(attribute_value_pairs)
|
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
|
74
88
|
end
|
75
89
|
|
76
90
|
def safe_options_for_cache?(options)
|
@@ -85,6 +99,7 @@ module Cash
|
|
85
99
|
when String
|
86
100
|
parse_indices_from_condition(conditions)
|
87
101
|
when Array
|
102
|
+
return nil if conditions.last.is_a?(Hash)
|
88
103
|
parse_indices_from_condition(*conditions)
|
89
104
|
when NilClass
|
90
105
|
[]
|
@@ -93,17 +108,19 @@ module Cash
|
|
93
108
|
|
94
109
|
AND = /\s+AND\s+/i
|
95
110
|
TABLE_AND_COLUMN = /(?:(?:`|")?(\w+)(?:`|")?\.)?(?:`|")?(\w+)(?:`|")?/ # Matches: `users`.id, `users`.`id`, users.id, id
|
96
|
-
VALUE = /'?(\d+|\?|(?:(?:[^']|'')
|
97
|
-
KEY_EQ_VALUE = /^\(?#{TABLE_AND_COLUMN}\s
|
98
|
-
ORDER = /^#{TABLE_AND_COLUMN}\s*(ASC|DESC)?$/i
|
111
|
+
VALUE = /'?(\d+|\?|(?:(?:[^']|'')*?))'?/ # Matches: 123, ?, '123', '12 ''3'
|
112
|
+
KEY_EQ_VALUE = /^\(?#{TABLE_AND_COLUMN}\s+(?:=|IN)\s+\(?(?:#{VALUE})\)?\)?$/ # Matches: KEY = VALUE, (KEY = VALUE), KEY IN (VALUE,VALUE,..)
|
113
|
+
ORDER = /^#{TABLE_AND_COLUMN}\s*(ASC|DESC)?$/i # Matches: COLUMN ASC, COLUMN DESC, COLUMN
|
99
114
|
|
100
115
|
def parse_indices_from_condition(conditions = '', *values)
|
101
116
|
values = values.dup
|
102
117
|
conditions.split(AND).inject([]) do |indices, condition|
|
103
|
-
matched, table_name, column_name,
|
118
|
+
matched, table_name, column_name, sql_values = *(KEY_EQ_VALUE.match(condition))
|
104
119
|
if matched
|
105
|
-
|
106
|
-
|
120
|
+
actual_values = sql_values.split(',').collect do |sql_value|
|
121
|
+
sql_value == '?' ? values.shift : columns_hash[column_name].type_cast(sql_value)
|
122
|
+
end
|
123
|
+
indices << [column_name, actual_values]
|
107
124
|
else
|
108
125
|
return nil
|
109
126
|
end
|
@@ -148,14 +165,30 @@ module Cash
|
|
148
165
|
objects
|
149
166
|
else
|
150
167
|
cache_keys = objects.collect { |id| "id/#{id}" }
|
151
|
-
objects = get(cache_keys
|
168
|
+
objects = get(cache_keys) {|missed_keys| find_from_keys(missed_keys)}
|
152
169
|
convert_to_array(cache_keys, objects)
|
153
170
|
end
|
154
171
|
end
|
155
172
|
|
156
|
-
def find_from_keys(
|
157
|
-
|
158
|
-
|
173
|
+
def find_from_keys(missing_keys, options = {})
|
174
|
+
missing_keys_pairs = Array(missing_keys).flatten.collect { |key| key.split('/') }
|
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 ")))
|
159
192
|
end
|
160
193
|
end
|
161
194
|
end
|
data/lib/cash/query/select.rb
CHANGED
@@ -4,8 +4,9 @@ module Cash
|
|
4
4
|
delegate :find_every_without_cache, :to => :@active_record
|
5
5
|
|
6
6
|
protected
|
7
|
-
def miss(
|
8
|
-
find_every_without_cache(miss_options)
|
7
|
+
def miss(missed_keys, miss_options)
|
8
|
+
# find_every_without_cache(miss_options)
|
9
|
+
misses = find_from_keys(missed_keys, miss_options)
|
9
10
|
end
|
10
11
|
|
11
12
|
def uncacheable
|
data/spec/cash/finders_spec.rb
CHANGED
@@ -170,8 +170,9 @@ module Cash
|
|
170
170
|
|
171
171
|
describe '#find(1, :conditions => ...)' do
|
172
172
|
it "does not use the database" do
|
173
|
+
Story.create!
|
173
174
|
story = Story.create!
|
174
|
-
character = Character.create!(:name => name = 'barbara', :story_id => story)
|
175
|
+
character = Character.create!(:name => name = 'barbara', :story_id => story.id)
|
175
176
|
mock(Character.connection).execute.never
|
176
177
|
Character.send :with_scope, :find => { :conditions => { :story_id => story.id } } do
|
177
178
|
Character.find(character.id, :conditions => { :name => name }).should == character
|
@@ -335,13 +336,101 @@ module Cash
|
|
335
336
|
end
|
336
337
|
end
|
337
338
|
|
339
|
+
describe '#find(:conditions => ["... :attr", {:attr => 1}])' do
|
340
|
+
it 'retrieves story from database - no support yet for retrieving from cache' do
|
341
|
+
Story.find(:all, :conditions => ["id = :id", {:id => @story.id}]).should == [@story]
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
338
345
|
describe '#find(1)' do
|
339
346
|
it 'populates the cache' do
|
340
347
|
Story.find(@story.id)
|
341
348
|
Story.fetch("id/#{@story.id}").should == [@story]
|
342
349
|
end
|
343
350
|
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
|
+
|
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
|
344
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
|
+
|
345
434
|
describe 'when there is a with_scope' do
|
346
435
|
it "uses the database, not the cache" do
|
347
436
|
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.7
|
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:
|
12
|
+
date: 2009-05-26 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|