onyx-cache-money 0.2.5.8 → 0.2.6

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.
@@ -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