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 CHANGED
@@ -11,11 +11,15 @@ module Cash
11
11
  def fetch(keys, options = {}, &block)
12
12
  case keys
13
13
  when Array
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)
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, &block)
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
- "#{name}:#{cache_config.version}/#{key.to_s.gsub(' ', '+')}"
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
- Query::PrimaryKey.perform(self, ids, options, scope(:find))
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 = {})
@@ -1,7 +1,8 @@
1
1
  module Cash
2
2
  module Query
3
3
  class Abstract
4
- delegate :with_exclusive_scope, :get, :table_name, :indices, :find_from_ids_without_cache, :cache_key, :columns_hash, :to => :@active_record
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.flatten.join('/')
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+|\?|(?:(?:[^']|'')*))'?/ # Matches: 123, ?, '123', '12''3'
97
- KEY_EQ_VALUE = /^\(?#{TABLE_AND_COLUMN}\s+=\s+#{VALUE}\)?$/ # Matches: KEY = VALUE, (KEY = VALUE)
98
- ORDER = /^#{TABLE_AND_COLUMN}\s*(ASC|DESC)?$/i # Matches: COLUMN ASC, COLUMN DESC, COLUMN
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, sql_value = *(KEY_EQ_VALUE.match(condition))
118
+ matched, table_name, column_name, sql_values = *(KEY_EQ_VALUE.match(condition))
104
119
  if matched
105
- value = sql_value == '?' ? values.shift : columns_hash[column_name].type_cast(sql_value)
106
- indices << [column_name, value]
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, &method(:find_from_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(*missing_keys)
157
- missing_ids = Array(missing_keys).flatten.collect { |key| key.split('/')[2].to_i }
158
- find_from_ids_without_cache(missing_ids, {})
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
@@ -4,8 +4,9 @@ module Cash
4
4
  delegate :find_every_without_cache, :to => :@active_record
5
5
 
6
6
  protected
7
- def miss(_, miss_options)
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
@@ -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.6
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: 2008-11-24 00:00:00 -08:00
12
+ date: 2009-05-26 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15