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.
- data/config/environment.rb +7 -2
- data/lib/cash/accessor.rb +36 -10
- data/lib/cash/finders.rb +4 -4
- data/lib/cash/index.rb +5 -0
- data/lib/cash/query/abstract.rb +93 -19
- data/lib/cash/query/select.rb +3 -2
- data/spec/cash/accessor_spec.rb +16 -5
- data/spec/cash/active_record_spec.rb +8 -0
- data/spec/cash/finders_spec.rb +205 -2
- data/spec/cash/order_spec.rb +5 -0
- metadata +2 -2
data/config/environment.rb
CHANGED
@@ -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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
@@ -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
|
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+|\?|(?:(?:[^']|'')
|
101
|
-
KEY_EQ_VALUE = /^\(?#{TABLE_AND_COLUMN}\s
|
102
|
-
ORDER = /^#{TABLE_AND_COLUMN}\s*(ASC|DESC)?$/i
|
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,
|
122
|
+
matched, table_name, column_name, sql_values = *(KEY_EQ_VALUE.match(condition))
|
108
123
|
if matched
|
109
|
-
|
110
|
-
|
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
|
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,
|
135
|
-
|
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
|
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(
|
161
|
-
|
162
|
-
|
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
|
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/accessor_spec.rb
CHANGED
@@ -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"])
|
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
|
32
|
-
it 'yields
|
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.
|
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
|
data/spec/cash/finders_spec.rb
CHANGED
@@ -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 ==
|
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
|
data/spec/cash/order_spec.rb
CHANGED
@@ -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.
|
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-
|
12
|
+
date: 2009-06-18 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|