onyx-cache-money 0.2.5.8 → 0.2.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,6 +1,11 @@
1
1
  require 'activerecord'
2
2
 
3
3
  ActiveRecord::Base.establish_connection(
4
- :adapter => 'sqlite3',
5
- :dbfile => ':memory:'
4
+ # :adapter => 'sqlite3',
5
+ # :dbfile => ':memory:'
6
+
7
+ :adapter => 'mysql',
8
+ :socket => %w[/tmp/mysql.sock /var/lib/mysql/mysql.sock /var/run/mysqld/mysqld.sock /opt/local/var/run/mysql5/mysqld.sock].detect { |f| File.exists? f },
9
+ :database => 'cache_money_development',
10
+ :username => 'root'
6
11
  )
data/lib/cash/accessor.rb CHANGED
@@ -8,14 +8,20 @@ module Cash
8
8
  end
9
9
 
10
10
  module ClassMethods
11
+ NIL_CACHE_VALUE = 'NIL_CACHE_VALUE'
12
+
11
13
  def fetch(keys, options = {}, &block)
12
14
  case keys
13
15
  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)
16
+ cache_and_actual_keys = keys.inject({}) { |memo, key| memo[cache_key(key)] = key; memo }
17
+ cache_keys = keys.collect {|key| cache_key(key)}
18
+
19
+ hits = repository.get_multi(cache_keys)
20
+ if (missed_cache_keys = cache_keys - hits.keys).any?
21
+ actual_missed_keys = missed_cache_keys.collect {|missed_cache_key| cache_and_actual_keys[missed_cache_key]}
22
+ misses = block.call(actual_missed_keys)
23
+
24
+ hits.merge!(misses)
19
25
  end
20
26
  hits
21
27
  else
@@ -26,17 +32,25 @@ module Cash
26
32
  def get(keys, options = {}, &block)
27
33
  case keys
28
34
  when Array
29
- fetch(keys, options, &block)
35
+ results = fetch(keys, options) do |missed_keys|
36
+ results = yield(missed_keys)
37
+ results.each {|key, value| add(key, wrap_nil(value), options)}
38
+ results
39
+ end
40
+ results.each { |key, result| results[key] = unwrap_nil(result) }
30
41
  else
31
- fetch(keys, options) do
42
+ result = fetch(keys, options) do
32
43
  if block_given?
33
- add(keys, result = yield(keys), options)
44
+ result = yield(keys)
45
+ value = result.is_a?(Hash) ? result[cache_key(keys)] : result
46
+ add(keys, wrap_nil(value), options)
34
47
  result
35
48
  end
36
49
  end
50
+ unwrap_nil(result)
37
51
  end
38
52
  end
39
-
53
+
40
54
  def add(key, value, options = {})
41
55
  if repository.add(cache_key(key), value, options[:ttl] || 0, options[:raw]) == "NOT_STORED\r\n"
42
56
  yield if block_given?
@@ -66,8 +80,20 @@ module Cash
66
80
  end
67
81
 
68
82
  def cache_key(key)
69
- "#{name}:#{cache_config.version}/#{key.to_s.gsub(' ', '+')}"
83
+ ready = key =~ /#{name}:#{cache_config.version}/
84
+ ready ? key : "#{name}:#{cache_config.version}/#{key.to_s.gsub(' ', '+')}"
85
+ end
86
+
87
+ private
88
+
89
+ def wrap_nil(value)
90
+ value.nil? ? NIL_CACHE_VALUE : value
70
91
  end
92
+
93
+ def unwrap_nil(value)
94
+ value == NIL_CACHE_VALUE ? nil : value
95
+ end
96
+
71
97
  end
72
98
 
73
99
  module InstanceMethods
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 = {})
data/lib/cash/index.rb CHANGED
@@ -67,6 +67,11 @@ module Cash
67
67
  end
68
68
  include Attributes
69
69
 
70
+ #not ideal, need to find a better solution
71
+ def serialize_objects(objects)
72
+ primary_key? ? objects.first : objects.map(&:id)
73
+ end
74
+
70
75
  def serialize_object(object)
71
76
  primary_key? ? object : object.id
72
77
  end
@@ -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
@@ -49,9 +50,12 @@ module Cash
49
50
 
50
51
  private
51
52
  def cacheable?(*optionss)
53
+ # Cache money used to cache queries with order option only when order was "id asc";
54
+ # For now, we just want all order queries to go to the database always.
55
+ return if optionss.find {|options| options[:order] }
52
56
  optionss.each { |options| return unless safe_options_for_cache?(options) }
53
57
  partial_indices = optionss.collect { |options| attribute_value_pairs_for_conditions(options[:conditions]) }
54
- return if partial_indices.include?(nil)
58
+ return if partial_indices.flatten.include?(nil)
55
59
  attribute_value_pairs = partial_indices.sum.sort { |x, y| x[0] <=> y[0] }
56
60
  if index = indexed_on?(attribute_value_pairs.collect { |pair| pair[0] })
57
61
  if index.matches?(self)
@@ -70,7 +74,20 @@ module Cash
70
74
  end
71
75
 
72
76
  def cache_keys(attribute_value_pairs)
73
- attribute_value_pairs.flatten.join('/')
77
+ cache_keys = collect_cache_keys(attribute_value_pairs)
78
+ cache_keys.size == 1 ? cache_keys.first : cache_keys
79
+ end
80
+
81
+ def collect_cache_keys(pairs)
82
+ return [] if pairs.empty?
83
+ key, values = pairs.shift
84
+ Array(values).inject([]) do |memo,value|
85
+ partial_keys = collect_cache_keys(pairs.clone)
86
+
87
+ memo << "#{key}/#{value}" if partial_keys.empty?
88
+ partial_keys.each { |partial_key| memo << "#{key}/#{value}/#{partial_key}" }
89
+ memo
90
+ end
74
91
  end
75
92
 
76
93
  def safe_options_for_cache?(options)
@@ -81,8 +98,6 @@ module Cash
81
98
  def attribute_value_pairs_for_conditions(conditions)
82
99
  case conditions
83
100
  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)}
86
101
  conditions.to_a.collect { |key, value| [key.to_s, value] }
87
102
  when String
88
103
  parse_indices_from_condition(conditions)
@@ -97,17 +112,20 @@ module Cash
97
112
 
98
113
  AND = /\s+AND\s+/i
99
114
  TABLE_AND_COLUMN = /(?:(?:`|")?(\w+)(?:`|")?\.)?(?:`|")?(\w+)(?:`|")?/ # Matches: `users`.id, `users`.`id`, users.id, id
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
115
+ VALUE = /'?(\d+|\?|(?:(?:[^']|'')*?))'?/ # Matches: 123, ?, '123', '12 ''3'
116
+ KEY_EQ_VALUE = /^\(?#{TABLE_AND_COLUMN}\s+(?:=|IN)\s+\(?(?:#{VALUE})\)?\)?$/ # Matches: KEY = VALUE, (KEY = VALUE), KEY IN (VALUE,VALUE,..)
117
+ ORDER = /^#{TABLE_AND_COLUMN}\s*(ASC|DESC)?$/i # Matches: COLUMN ASC, COLUMN DESC, COLUMN
103
118
 
104
119
  def parse_indices_from_condition(conditions = '', *values)
105
120
  values = values.dup
106
121
  conditions.split(AND).inject([]) do |indices, condition|
107
- matched, table_name, column_name, sql_value = *(KEY_EQ_VALUE.match(condition))
122
+ matched, table_name, column_name, sql_values = *(KEY_EQ_VALUE.match(condition))
108
123
  if matched
109
- value = sql_value == '?' ? values.shift : columns_hash[column_name].type_cast(sql_value)
110
- indices << [column_name, value]
124
+ actual_values = sql_values.split(',').collect do |sql_value|
125
+ sql_value == '?' ? values.shift : columns_hash[column_name].type_cast(sql_value)
126
+ end
127
+ actual_values.flatten!
128
+ indices << [column_name, actual_values]
111
129
  else
112
130
  return nil
113
131
  end
@@ -120,8 +138,7 @@ module Cash
120
138
  alias_method :index_for, :indexed_on?
121
139
 
122
140
  def format_results(cache_keys, objects)
123
- return objects if objects.blank?
124
-
141
+ return [] if objects.blank?
125
142
  objects = convert_to_array(cache_keys, objects)
126
143
  objects = apply_limits_and_offsets(objects, @options1)
127
144
  deserialize_objects(objects)
@@ -131,8 +148,10 @@ module Cash
131
148
  missed_keys == cache_keys ? misses : objects
132
149
  end
133
150
 
134
- def serialize_objects(index, objects)
135
- Array(objects).collect { |missed| index.serialize_object(missed) }
151
+ def serialize_objects(index, objects_hash)
152
+ objects_hash.each do |key, objects|
153
+ objects_hash[key] = index.serialize_objects(objects)
154
+ end
136
155
  end
137
156
 
138
157
  def convert_to_array(cache_keys, object)
@@ -152,15 +171,70 @@ module Cash
152
171
  objects
153
172
  else
154
173
  cache_keys = objects.collect { |id| "id/#{id}" }
155
- objects = get(cache_keys, &method(:find_from_keys))
174
+ objects = get(cache_keys) {|missed_keys| find_from_keys(missed_keys)}
156
175
  convert_to_array(cache_keys, objects)
157
176
  end
158
177
  end
159
178
 
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, {})
179
+ def find_from_keys(missing_keys, options = {})
180
+ #[[id/1/title/foo], [id/1/title/foo]]
181
+ missing_keys_values_pairs = Array(missing_keys).flatten.collect { |key| key.split('/') }
182
+ #[[id,1,title,foo], [id,2,title,foo]]
183
+
184
+ conditions = conditions_from_missing_keys_values_pairs(missing_keys_values_pairs)
185
+ results = find_every_without_cache options.merge(:conditions => conditions)
186
+ # [<object1>, <object2>, <object3>]
187
+
188
+ collect_results_into_hash(missing_keys_values_pairs, Array(results))
189
+ #{ id/1/title/foo => [<object1>], id/2/title/foo => [<object2>,<object3>] }
190
+ end
191
+
192
+ def collect_results_into_hash(missing_keys_values_pairs, results)
193
+ missing_keys_values_pairs.inject({}) do |memo, missing_keys_values_pair|
194
+ match = results.select do |result|
195
+ found_match = false
196
+ missing_keys_values_pair.each_slice(2) do |key, value|
197
+ found_match = upcase_if_possible(result.send(key)) == convert_value(key, value)
198
+ break unless found_match
199
+ end
200
+ found_match
201
+ end
202
+ memo[cache_key(missing_keys_values_pair.join('/'))] = match
203
+ memo
204
+ end
205
+ end
206
+
207
+ def conditions_from_missing_keys_values_pairs(missing_keys_values_pairs)
208
+ keys_values = missing_keys_values_pairs.inject({}) do |memo, missing_keys_values_pair|
209
+ missing_keys_values_pair.each_slice(2) do |key, value|
210
+ memo[key] ||= []
211
+ memo[key] << value
212
+ end
213
+ memo
214
+ end
215
+ # { :id => [1,2], :title => [foo] }
216
+
217
+ conditions = keys_values.collect do |key,values|
218
+ quoted_values = values.collect {|value| quote_value(value, columns_hash[key])}
219
+ quoted_table_and_column_name = "#{quoted_table_name}.#{connection.quote_column_name(key)}"
220
+ if quoted_values.size == 1
221
+ "#{quoted_table_and_column_name} = #{quoted_values}"
222
+ else
223
+ "#{quoted_table_and_column_name} IN (#{quoted_values.join(',')})"
224
+ end
225
+ end.join(' AND ')
226
+ end
227
+
228
+ protected
229
+
230
+ def upcase_if_possible(value)
231
+ value.respond_to?(:upcase) ? value.upcase : value
232
+ end
233
+
234
+ def convert_value(key, value)
235
+ columns_hash[key].type == :integer ? value.to_i : upcase_if_possible(value)
163
236
  end
237
+
164
238
  end
165
239
  end
166
240
  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
@@ -21,17 +21,28 @@ module Cash
21
21
  describe '#fetch([...])', :shared => true do
22
22
  describe 'when there is a total cache miss' do
23
23
  it 'yields the keys to the block' do
24
- Story.fetch(["yabba", "dabba"]) { |*missing_ids| ["doo", "doo"] }.should == {
25
- "Story:1/yabba" => "doo",
24
+ Story.fetch(["yabba", "dabba"]) do |*missing_ids|
25
+ {"Story:1/yabba" => "doo", "Story:1/dabba" => "doo"}
26
+ end.should == {
27
+ "Story:1/yabba" => "doo", "Story:1/dabba" => "doo"}
28
+ end
29
+ end
30
+
31
+ describe 'when there is a partial cache miss' do
32
+ it 'yields just the missing keys and ids to the block' do
33
+ Story.set("yabba", "dabba")
34
+ Story.fetch(["yabba", "dabba"]) { |*missing_ids| {"Story:1/dabba" => "doo"} }.should == {
35
+ "Story:1/yabba" => "dabba",
26
36
  "Story:1/dabba" => "doo"
27
37
  }
28
38
  end
29
39
  end
30
40
 
31
- describe 'when there is a partial cache miss' do
32
- it 'yields just the missing ids to the block' do
41
+ describe 'when there is a total cache hit' do
42
+ it 'yields empty hash to the block' do
33
43
  Story.set("yabba", "dabba")
34
- Story.fetch(["yabba", "dabba"]) { |*missing_ids| "doo" }.should == {
44
+ Story.set("dabba", "doo")
45
+ Story.fetch(["yabba", "dabba"]) { |*missing_ids| {} }.should == {
35
46
  "Story:1/yabba" => "dabba",
36
47
  "Story:1/dabba" => "doo"
37
48
  }
@@ -41,6 +41,14 @@ module Cash
41
41
  it 'raises an error' do
42
42
  lambda { Story.find(1) }.should raise_error(ActiveRecord::RecordNotFound)
43
43
  end
44
+
45
+ it 'raises an error the second time as well' do
46
+ begin
47
+ Story.find(1)
48
+ rescue ActiveRecord::RecordNotFound
49
+ lambda { Story.find(1) }.should raise_error(ActiveRecord::RecordNotFound)
50
+ end
51
+ end
44
52
  end
45
53
 
46
54
  describe 'when given multiple nonexistent ids' do
@@ -5,6 +5,52 @@ module Cash
5
5
  describe 'Cache Usage' do
6
6
  describe 'when the cache is populated' do
7
7
  describe '#find' do
8
+
9
+ describe 'storing nil values in cache' do
10
+ it 'populates the cache with special token when id does not exist in the database' do
11
+ Story.find_by_id(999_999)
12
+ Story.fetch("id/999999").should == 'NIL_CACHE_VALUE'
13
+ end
14
+
15
+ it 'caches the non-existent record' do
16
+ Story.find_by_id(999_999)
17
+ mock(Story.connection).execute.never
18
+ Story.find_by_id(999_999)
19
+ end
20
+
21
+ it 'correctly retrieves nil when special token is stored in the cache' do
22
+ Story.find_by_id(999_999)
23
+ Story.find_by_id(999_999).should be_nil
24
+ end
25
+
26
+ it 'populates the cache with multiple special tokens when none of the ids exist' do
27
+ Story.find(:all, :conditions => {:id => [999_997, 999_998]} )
28
+ Story.fetch("id/999997").should == 'NIL_CACHE_VALUE'
29
+ Story.fetch("id/999998").should == 'NIL_CACHE_VALUE'
30
+ end
31
+
32
+ it 'caches the non-existent records when finding by multiple ids' do
33
+ Story.find(:all, :conditions => {:id => [999_997, 999_998]} )
34
+ mock(Story.connection).execute.never
35
+ Story.find(:all, :conditions => {:id => [999_997, 999_998]} )
36
+ end
37
+
38
+ it 'correctly retrieves empty array when special tokens are stored in the cache' do
39
+ Story.find(:all, :conditions => {:id => [999_997, 999_998]} )
40
+ Story.find(:all, :conditions => {:id => [999_997, 999_998]} ).should == []
41
+ end
42
+
43
+ it "populates the cache correctly when passing in a nonexistent key" do
44
+ story1 = Story.create!
45
+ story2 = Story.create!
46
+ $memcache.flush_all
47
+ Story.find(:all, :conditions => ["id IN (?)", [story1.id, 999, story2.id]]).should == [story1, story2]
48
+ Story.fetch("id/#{story1.id}").should == story1
49
+ Story.fetch("id/#{story2.id}").should == story2
50
+ Story.fetch("id/999").should == 'NIL_CACHE_VALUE'
51
+ end
52
+ end
53
+
8
54
  describe '#find(1)' do
9
55
  it 'does not use the database' do
10
56
  story = Story.create!
@@ -170,8 +216,9 @@ module Cash
170
216
 
171
217
  describe '#find(1, :conditions => ...)' do
172
218
  it "does not use the database" do
219
+ Story.create!
173
220
  story = Story.create!
174
- character = Character.create!(:name => name = 'barbara', :story_id => story)
221
+ character = Character.create!(:name => name = 'barbara', :story_id => story.id)
175
222
  mock(Character.connection).execute.never
176
223
  Character.send :with_scope, :find => { :conditions => { :story_id => story.id } } do
177
224
  Character.find(character.id, :conditions => { :name => name }).should == character
@@ -241,6 +288,17 @@ module Cash
241
288
  end
242
289
  end
243
290
 
291
+ it "populate cache with compound key" do
292
+ story = Story.create!
293
+ harry = story.characters.create! :name => "harry"
294
+ larry = story.characters.create! :name => "larry"
295
+ $memcache.flush_all
296
+
297
+ Character.find(:all, :conditions => {:story_id => story.id, :name => [larry.name, harry.name]})
298
+ Character.fetch("name/#{harry.name}/story_id/#{story.id}").should == [harry.id]
299
+ Character.fetch("name/#{larry.name}/story_id/#{story.id}").should == [larry.id]
300
+ end
301
+
244
302
  it "should not create a key over 250 characters with hash for conditions" do
245
303
  150.times do
246
304
  Story.create!
@@ -354,6 +412,11 @@ module Cash
354
412
  Story.find(:first, :conditions => { :title => @story.title })
355
413
  Story.fetch("title/#{@story.title}").should == [@story.id]
356
414
  end
415
+
416
+ it 'populates the cache when database is case insensitive - fails under sqlite (case sensitive)' do
417
+ Story.find(:first, :conditions => { :title => @story.title.upcase })
418
+ Story.fetch("title/#{@story.title.upcase}").should == [@story.id]
419
+ end
357
420
  end
358
421
 
359
422
  describe '#find_by_attr' do
@@ -361,6 +424,11 @@ module Cash
361
424
  Story.find_by_title(@story.title)
362
425
  Story.fetch("title/#{@story.title}").should == [@story.id]
363
426
  end
427
+
428
+ it 'populates the cache with empty array for non-existent value' do
429
+ Story.find_by_title("blah")
430
+ Story.fetch("title/blah").should == []
431
+ end
364
432
  end
365
433
 
366
434
  describe '#find(:all, :conditions => ...)' do
@@ -383,13 +451,124 @@ module Cash
383
451
  end
384
452
  end
385
453
 
454
+ describe '#find(:conditions => ["... :attr", {:attr => 1}])' do
455
+ it 'retrieves story from database - no support yet for retrieving from cache' do
456
+ Story.find(:all, :conditions => ["id = :id", {:id => @story.id}]).should == [@story]
457
+ end
458
+ end
459
+
386
460
  describe '#find(1)' do
387
461
  it 'populates the cache' do
388
462
  Story.find(@story.id)
389
- Story.fetch("id/#{@story.id}").should == [@story]
463
+ Story.fetch("id/#{@story.id}").should == @story
390
464
  end
391
465
  end
466
+
467
+ describe '#find(1,2)' do
468
+
469
+ it 'populates the cache' do
470
+ another_story = Story.create!
471
+ $memcache.flush_all
472
+
473
+ Story.find(@story.id, another_story.id).should == [@story, another_story]
474
+ Story.fetch("id/#{@story.id}").should == @story
475
+ Story.fetch("id/#{another_story.id}").should == another_story
476
+ end
477
+
478
+ it "populates the cache and retrieves from the cache" do
479
+ story1 = Story.create!
480
+ story2 = Story.create!
481
+ story3 = Story.create!
482
+ $memcache.flush_all
483
+
484
+ Story.find(story1.id, story2.id, story3.id).should == [story1, story2, story3]
485
+ mock(Story.connection).execute.never
486
+ Story.find(story1.id, story2.id).should == [story1, story2]
487
+ end
488
+
489
+ it "uses database for single missing key" do
490
+ story1 = Story.create!
491
+ story2 = Story.create!
492
+ story3 = Story.create!
493
+ $memcache.flush_all
494
+
495
+ Story.find(story1.id, story3.id).should == [story1, story3]
496
+ Story.fetch("id/#{story2.id}").should be_nil
497
+
498
+ quoted_table_name = Story.quoted_table_name
499
+ quoted_column_name = Story.connection.quote_column_name("id")
500
+ mock(Story).find_every_without_cache(:limit => nil,
501
+ :conditions => "#{quoted_table_name}.#{quoted_column_name} = #{story2.id}") do
502
+ story2
503
+ end
504
+ Story.find(story1.id, story2.id)
505
+ end
506
+
507
+ it "uses database for all missing keys" do
508
+ story1 = Story.create!
509
+ story2 = Story.create!
510
+ story3 = Story.create!
511
+ $memcache.flush_all
512
+
513
+ Story.find(story2.id).should == story2
514
+ Story.fetch("id/#{story1.id}").should be_nil
515
+ Story.fetch("id/#{story3.id}").should be_nil
516
+
517
+ quoted_table_name = Story.quoted_table_name
518
+ quoted_column_name = Story.connection.quote_column_name("id")
519
+ mock(Story).find_every_without_cache(:limit => nil,
520
+ :conditions => "#{quoted_table_name}.#{quoted_column_name} IN (#{story1.id},#{story3.id})") do
521
+ [story1, story3]
522
+ end
523
+ Story.find(story1.id, story2.id, story3.id)
524
+ end
525
+
526
+ it "populates and retrieves from cache when passing in a hash" do
527
+ story1 = Story.create!
528
+ story2 = Story.create!
529
+ $memcache.flush_all
530
+
531
+ Story.find(:all, :conditions => {:id => [story1.id, story2.id]})
532
+ mock(Story.connection).execute.never
533
+ Story.find(story1.id, story2.id)
534
+ end
535
+
536
+ it "populates and retrieves from cache when passing in a parametrized conditions" do
537
+ story1 = Story.create!
538
+ story2 = Story.create!
539
+ $memcache.flush_all
392
540
 
541
+ Story.find(:all, :conditions => ["id IN (?,?)", story1.id, story2.id])
542
+ mock(Story.connection).execute.never
543
+ Story.find(story1.id, story2.id)
544
+ end
545
+
546
+ it "populates and retrieves from cache when passing in a list of non-numeric keys" do
547
+ pending "need to fix regex to properly parse this"
548
+ story1 = Story.create! :title => 'one'
549
+ story2 = Story.create! :title => 'two'
550
+ $memcache.flush_all
551
+
552
+ Story.find(:all, :conditions => ["title IN ('one', 'two')"])
553
+ # We need to add this extra call as cache money will always go to the database
554
+ # one more time when finding by a non-id field first
555
+ Story.find_all_by_title(['one', 'two'])
556
+ mock(Story.connection).execute.never
557
+ Story.find(story1.id, story2.id)
558
+ end
559
+
560
+ it "should not create a key over 250 characters on a find_all_by_ids" do
561
+ 75.times do
562
+ Story.create!
563
+ end
564
+ ids = Story.find(:all).map(&:id)
565
+ $memcache.flush_all
566
+ lambda do
567
+ Story.find(:all, :conditions => {:id => ids})
568
+ end.should_not raise_error(ArgumentError)
569
+ end
570
+ end
571
+
393
572
  describe 'when there is a with_scope' do
394
573
  it "uses the database, not the cache" do
395
574
  Story.send :with_scope, :find => { :conditions => { :title => @story.title }} do
@@ -397,6 +576,30 @@ module Cash
397
576
  end
398
577
  end
399
578
  end
579
+
580
+ it 'populates the cache correctly when arguments are unordered' do
581
+ another_story = Story.create!
582
+ $memcache.flush_all
583
+
584
+ Story.find(another_story.id, @story.id)
585
+ Story.fetch("id/#{@story.id}").should == @story
586
+ Story.fetch("id/#{another_story.id}").should == another_story
587
+ end
588
+
589
+ describe 'ordering' do
590
+ it 'returns data in correct order' do
591
+ story1 = Story.create!
592
+ story2 = Story.create!
593
+ story3 = Story.create!
594
+ story4 = Story.create!
595
+ $memcache.flush_all
596
+
597
+ Story.find(story1.id, story3.id)
598
+ unordered_fairy_tales = [story4.id, story3.id, story2.id, story1.id]
599
+ ordered_fairy_tales = Story.find(:all, :conditions => ["id IN (?)", unordered_fairy_tales], :order => 'id ASC')
600
+ ordered_fairy_tales.should == [story1, story2, story3, story4]
601
+ end
602
+ end
400
603
  end
401
604
  end
402
605
  end
@@ -1,6 +1,7 @@
1
1
  require File.join(File.dirname(__FILE__), '..', 'spec_helper')
2
2
 
3
3
  module Cash
4
+ # for now, all order queries will go to database and bypass cache"
4
5
  describe 'Ordering' do
5
6
  before :suite do
6
7
  FairyTale = Class.new(Story)
@@ -79,6 +80,7 @@ module Cash
79
80
  describe "#find(..., :order => 'id ASC')" do
80
81
  describe 'when the cache is populated' do
81
82
  it 'does not use the database' do
83
+ pending "for now, all order queries will go to database and bypass cache"
82
84
  mock(FairyTale.connection).execute.never
83
85
  FairyTale.find(:all, :conditions => { :title => @title }, :order => 'id ASC').should == @fairy_tales
84
86
  FairyTale.find(:all, :conditions => { :title => @title }, :order => 'id').should == @fairy_tales
@@ -91,6 +93,7 @@ module Cash
91
93
 
92
94
  describe 'when the cache is not populated' do
93
95
  it 'populates the cache' do
96
+ pending "for now, all order queries will go to database and bypass cache"
94
97
  $memcache.flush_all
95
98
  FairyTale.find(:all, :conditions => { :title => @title }, :order => 'id ASC').should == @fairy_tales
96
99
  FairyTale.get("title/#{@title}").should == @fairy_tales.collect(&:id)
@@ -124,6 +127,7 @@ module Cash
124
127
  describe "#find(..., :order => 'id DESC')" do
125
128
  describe 'when the cache is populated' do
126
129
  it 'does not use the database' do
130
+ pending "for now, all order queries will go to database and bypass cache"
127
131
  mock(FairyTale.connection).execute.never
128
132
  FairyTale.find(:all, :conditions => { :title => @title }, :order => 'id DESC').should == @fairy_tales.reverse
129
133
  FairyTale.find(:all, :conditions => { :title => @title }, :order => 'id DESC').should == @fairy_tales.reverse
@@ -136,6 +140,7 @@ module Cash
136
140
 
137
141
  describe 'when the cache is not populated' do
138
142
  it 'populates the cache' do
143
+ pending "for now, all order queries will go to database and bypass cache"
139
144
  $memcache.flush_all
140
145
  FairyTale.find(:all, :conditions => { :title => @title }, :order => 'id DESC')
141
146
  FairyTale.get("title/#{@title}").should == @fairy_tales.collect(&:id).reverse
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.8
4
+ version: 0.2.6
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-28 00:00:00 -07:00
12
+ date: 2009-06-18 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies: []
15
15