onyx-cache-money 0.2.5.6 → 0.2.5.7
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 +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
|
|