arid_cache 0.2.7 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +1 -1
- data/Rakefile +11 -3
- data/VERSION +1 -1
- data/arid_cache.gemspec +4 -2
- data/lib/arid_cache/cache_proxy.rb +100 -66
- data/test/arid_cache_test.rb +83 -55
- data/test/db/schema.rb +6 -2
- data/test/models/empty_user_relation.rb +5 -0
- data/test/models/user.rb +1 -0
- data/test/test_helper.rb +1 -1
- metadata +4 -2
data/README.rdoc
CHANGED
@@ -265,7 +265,7 @@ For example, assume we have a <tt>named_scope :active</tt> on <tt>User</tt> whic
|
|
265
265
|
|
266
266
|
== Compatibility
|
267
267
|
|
268
|
-
Tested on Ruby 1.8.6
|
268
|
+
Tested on Ruby 1.8.6, 1.8.7 and 1.9.1.
|
269
269
|
|
270
270
|
For Ruby < 1.8.7 you probably want to include the following to extend the Array class with a <tt>count</tt> method. Otherwise your <tt>cached_<key>_count</tt> calls probably won't work:
|
271
271
|
|
data/Rakefile
CHANGED
@@ -8,7 +8,7 @@ begin
|
|
8
8
|
gem.summary = %Q{Automates efficient caching of your ActiveRecord collections, gives you counts for free and supports pagination.}
|
9
9
|
gem.description = <<-END.gsub(/^\s+/, '')
|
10
10
|
AridCache makes caching easy and effective. AridCache supports caching on all your model named scopes, class methods and instance methods right out of the box. AridCache prevents caching logic from cluttering your models and clarifies your logic by making explicit calls to cached result sets.
|
11
|
-
|
11
|
+
|
12
12
|
AridCache is designed for handling large, expensive ActiveRecord collections but is equally useful for caching anything else as well.
|
13
13
|
END
|
14
14
|
gem.email = "kjvarga@gmail.com"
|
@@ -30,13 +30,13 @@ end
|
|
30
30
|
# spec.libs << 'lib' << 'spec'
|
31
31
|
# spec.spec_files = FileList['spec/**/*_spec.rb']
|
32
32
|
# end
|
33
|
-
#
|
33
|
+
#
|
34
34
|
# Spec::Rake::SpecTask.new(:rcov) do |spec|
|
35
35
|
# spec.libs << 'lib' << 'spec'
|
36
36
|
# spec.pattern = 'spec/**/*_spec.rb'
|
37
37
|
# spec.rcov = true
|
38
38
|
# end
|
39
|
-
#
|
39
|
+
#
|
40
40
|
# task :spec => :check_dependencies
|
41
41
|
|
42
42
|
require 'rake/testtask'
|
@@ -58,3 +58,11 @@ Rake::RDocTask.new do |rdoc|
|
|
58
58
|
rdoc.rdoc_files.include('README*')
|
59
59
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
60
60
|
end
|
61
|
+
|
62
|
+
desc "Release a new patch version"
|
63
|
+
task :release_new_version do
|
64
|
+
Rake::Task['version:bump:patch'].invoke
|
65
|
+
Rake::Task['github:release'].invoke
|
66
|
+
Rake::Task['git:release'].invoke
|
67
|
+
Rake::Task['gemcutter:release'].invoke
|
68
|
+
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
1.0.0
|
data/arid_cache.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{arid_cache}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "1.0.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Karl Varga"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-07-05}
|
13
13
|
s.description = %q{AridCache makes caching easy and effective. AridCache supports caching on all your model named scopes, class methods and instance methods right out of the box. AridCache prevents caching logic from cluttering your models and clarifies your logic by making explicit calls to cached result sets.
|
14
14
|
AridCache is designed for handling large, expensive ActiveRecord collections but is equally useful for caching anything else as well.
|
15
15
|
}
|
@@ -44,6 +44,7 @@ AridCache is designed for handling large, expensive ActiveRecord collections but
|
|
44
44
|
"test/lib/blueprint.rb",
|
45
45
|
"test/log/.gitignore",
|
46
46
|
"test/models/company.rb",
|
47
|
+
"test/models/empty_user_relation.rb",
|
47
48
|
"test/models/user.rb",
|
48
49
|
"test/test_helper.rb"
|
49
50
|
]
|
@@ -61,6 +62,7 @@ AridCache is designed for handling large, expensive ActiveRecord collections but
|
|
61
62
|
"test/lib/active_support/cache/file_store_extras.rb",
|
62
63
|
"test/lib/blueprint.rb",
|
63
64
|
"test/models/company.rb",
|
65
|
+
"test/models/empty_user_relation.rb",
|
64
66
|
"test/models/user.rb",
|
65
67
|
"test/test_helper.rb"
|
66
68
|
]
|
@@ -1,25 +1,25 @@
|
|
1
1
|
module AridCache
|
2
2
|
class CacheProxy
|
3
3
|
attr_accessor :object, :key, :opts, :blueprint, :cached, :cache_key, :block, :records, :combined_options, :klass
|
4
|
-
|
4
|
+
|
5
5
|
# AridCache::CacheProxy::Result
|
6
6
|
#
|
7
7
|
# This struct is stored in the cache and stores information we need
|
8
8
|
# to re-query for results.
|
9
9
|
Result = Struct.new(:ids, :klass, :count) do
|
10
|
-
|
10
|
+
|
11
11
|
def has_count?
|
12
12
|
!count.nil?
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
def has_ids?
|
16
16
|
!ids.nil?
|
17
17
|
end
|
18
|
-
|
18
|
+
|
19
19
|
def klass=(value)
|
20
20
|
self['klass'] = value.is_a?(Class) ? value.name : value.class.name
|
21
21
|
end
|
22
|
-
|
22
|
+
|
23
23
|
def klass
|
24
24
|
self['klass'].constantize unless self['klass'].nil?
|
25
25
|
end
|
@@ -27,17 +27,17 @@ module AridCache
|
|
27
27
|
|
28
28
|
#
|
29
29
|
# Managing your caches
|
30
|
-
#
|
31
|
-
|
30
|
+
#
|
31
|
+
|
32
32
|
def self.clear_caches
|
33
33
|
Rails.cache.delete_matched(%r[arid-cache-.*])
|
34
|
-
end
|
35
|
-
|
34
|
+
end
|
35
|
+
|
36
36
|
def self.clear_class_caches(object)
|
37
37
|
key = (object.is_a?(Class) ? object : object.class).name.downcase + '-'
|
38
38
|
Rails.cache.delete_matched(%r[arid-cache-#{key}.*])
|
39
|
-
end
|
40
|
-
|
39
|
+
end
|
40
|
+
|
41
41
|
def self.clear_instance_caches(object)
|
42
42
|
key = (object.is_a?(Class) ? object : object.class).name.pluralize.downcase
|
43
43
|
Rails.cache.delete_matched(%r[arid-cache-#{key}.*])
|
@@ -46,11 +46,11 @@ module AridCache
|
|
46
46
|
#
|
47
47
|
# Fetching results
|
48
48
|
#
|
49
|
-
|
49
|
+
|
50
50
|
def self.fetch_count(object, key, opts={}, &block)
|
51
51
|
CacheProxy.new(object, key, opts, &block).fetch_count
|
52
52
|
end
|
53
|
-
|
53
|
+
|
54
54
|
def self.fetch(object, key, opts={}, &block)
|
55
55
|
CacheProxy.new(object, key, opts, &block).fetch
|
56
56
|
end
|
@@ -62,14 +62,14 @@ module AridCache
|
|
62
62
|
self.blueprint = AridCache.store.find(object, key)
|
63
63
|
self.block = block
|
64
64
|
self.records = nil
|
65
|
-
|
65
|
+
|
66
66
|
# The options from the blueprint merged with the options for this call
|
67
67
|
self.combined_options = self.blueprint.nil? ? self.opts : self.blueprint.opts.merge(self.opts)
|
68
|
-
|
68
|
+
|
69
69
|
self.cache_key = object.arid_cache_key(key, opts_for_cache_key)
|
70
70
|
self.cached = Rails.cache.read(cache_key, opts_for_cache)
|
71
71
|
end
|
72
|
-
|
72
|
+
|
73
73
|
def fetch_count
|
74
74
|
if refresh_cache?
|
75
75
|
execute_count
|
@@ -83,13 +83,16 @@ module AridCache
|
|
83
83
|
cached # what else can we do? return it
|
84
84
|
end
|
85
85
|
end
|
86
|
-
|
86
|
+
|
87
87
|
def fetch
|
88
88
|
if refresh_cache?
|
89
89
|
execute_find
|
90
90
|
elsif cached.is_a?(AridCache::CacheProxy::Result)
|
91
91
|
if cached.has_ids?
|
92
|
+
|
93
|
+
# Infer the class of the objects we are returning
|
92
94
|
self.klass = cached.klass || object_base_class
|
95
|
+
|
93
96
|
fetch_from_cache
|
94
97
|
else
|
95
98
|
execute_find
|
@@ -98,59 +101,90 @@ module AridCache
|
|
98
101
|
cached # some base type, return it
|
99
102
|
end
|
100
103
|
end
|
101
|
-
|
104
|
+
|
102
105
|
private
|
103
106
|
|
107
|
+
# Return a list of records from the database using the ids from
|
108
|
+
# the cached Result.
|
109
|
+
#
|
110
|
+
# The result is paginated if the :page option is preset, otherwise
|
111
|
+
# a regular list of ActiveRecord results is returned.
|
112
|
+
#
|
113
|
+
# If no :order is specified, the current ordering of the ids is
|
114
|
+
# preserved with some fancy SQL.
|
104
115
|
def fetch_from_cache
|
105
116
|
if paginate?
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
117
|
+
|
118
|
+
# Return a paginated collection
|
119
|
+
|
120
|
+
if cached.ids.empty?
|
121
|
+
|
122
|
+
# No ids, return an empty WillPaginate result
|
123
|
+
|
124
|
+
[].paginate(opts_for_paginate)
|
125
|
+
|
126
|
+
elsif combined_options.include?(:order)
|
127
|
+
|
128
|
+
# An order has been specified. We have to go to the database
|
129
|
+
# and paginate there because the contents of the requested
|
130
|
+
# page will be different.
|
131
|
+
|
132
|
+
klass.paginate(cached.ids, { :total_entries => cached.ids.size }.merge(opts_for_find.merge(opts_for_paginate)))
|
133
|
+
|
134
|
+
else
|
135
|
+
|
136
|
+
# Order is unchanged. We can paginate in memory and only select
|
137
|
+
# those ids that we actually need. This is the most efficient.
|
138
|
+
|
139
|
+
paged_ids = cached.ids.paginate(opts_for_paginate)
|
140
|
+
paged_ids.replace(klass.find_all_by_id(paged_ids, opts_for_find(paged_ids)))
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
elsif cached.ids.empty?
|
145
|
+
|
146
|
+
# We are returning a regular (non-paginated) result.
|
147
|
+
# If we don't have any ids, that's an empty result
|
148
|
+
# in any language.
|
149
|
+
|
150
|
+
[]
|
151
|
+
|
152
|
+
elsif combined_options.include?(:order)
|
153
|
+
|
154
|
+
# An order has been specified, so use it.
|
155
|
+
|
125
156
|
klass.find_all_by_id(cached.ids, opts_for_find)
|
157
|
+
|
126
158
|
else
|
159
|
+
|
160
|
+
# No order has been specified, so we have to maintain
|
161
|
+
# the current order. We do this by passing some extra
|
162
|
+
# SQL which orders by the current array ordering.
|
163
|
+
|
127
164
|
offset, limit = combined_options.delete(:offset) || 0, combined_options.delete(:limit) || cached.count
|
128
165
|
ids = cached.ids[offset, limit]
|
166
|
+
|
129
167
|
klass.find_all_by_id(ids, opts_for_find(ids))
|
130
|
-
end
|
168
|
+
end
|
131
169
|
end
|
132
|
-
|
170
|
+
|
133
171
|
def paginate?
|
134
172
|
combined_options.include?(:page)
|
135
173
|
end
|
136
|
-
|
137
|
-
def limit_or_offset?
|
138
|
-
combined_options.include?(:limit) || combined_options.include?(:offset)
|
139
|
-
end
|
140
|
-
|
174
|
+
|
141
175
|
def refresh_cache?
|
142
176
|
cached.nil? || opts[:force]
|
143
177
|
end
|
144
|
-
|
178
|
+
|
145
179
|
def get_records
|
146
180
|
block = self.block || (blueprint && blueprint.proc)
|
147
181
|
self.records = block.nil? ? object.instance_eval(key) : object.instance_eval(&block)
|
148
182
|
end
|
149
|
-
|
183
|
+
|
150
184
|
def execute_find
|
151
|
-
get_records
|
185
|
+
get_records
|
152
186
|
cached = AridCache::CacheProxy::Result.new
|
153
|
-
|
187
|
+
|
154
188
|
if !records.is_a?(Enumerable) || (!records.empty? && !records.first.is_a?(::ActiveRecord::Base))
|
155
189
|
cached = records # some base type, cache it as itself
|
156
190
|
else
|
@@ -165,7 +199,7 @@ module AridCache
|
|
165
199
|
end
|
166
200
|
end
|
167
201
|
Rails.cache.write(cache_key, cached, opts_for_cache)
|
168
|
-
|
202
|
+
|
169
203
|
self.cached = cached
|
170
204
|
return_records(records)
|
171
205
|
end
|
@@ -187,20 +221,20 @@ module AridCache
|
|
187
221
|
# If you do want a specialized, modified, or subset of the collection it's best
|
188
222
|
# to define it in a block and have a new cache for it:
|
189
223
|
#
|
190
|
-
# model.my_special_collection { the_collection(:order => 'new order', :limit => 10) }
|
224
|
+
# model.my_special_collection { the_collection(:order => 'new order', :limit => 10) }
|
191
225
|
def return_records(records)
|
192
226
|
if opts.include?(:page)
|
193
227
|
records = records.respond_to?(:to_a) ? records.to_a : records
|
194
228
|
records.respond_to?(:paginate) ? records.paginate(opts_for_paginate) : records
|
195
229
|
elsif opts.include?(:limit)
|
196
230
|
records = records.respond_to?(:to_a) ? records.to_a : records
|
197
|
-
offset = opts[:offset] || 0
|
231
|
+
offset = opts[:offset] || 0
|
198
232
|
records[offset, opts[:limit]]
|
199
233
|
else
|
200
234
|
records
|
201
|
-
end
|
235
|
+
end
|
202
236
|
end
|
203
|
-
|
237
|
+
|
204
238
|
def execute_count
|
205
239
|
get_records
|
206
240
|
cached = AridCache::CacheProxy::Result.new
|
@@ -224,14 +258,14 @@ module AridCache
|
|
224
258
|
else
|
225
259
|
cached = records # some base type, cache it as itself
|
226
260
|
end
|
227
|
-
|
261
|
+
|
228
262
|
Rails.cache.write(cache_key, cached, opts_for_cache)
|
229
263
|
self.cached = cached
|
230
264
|
cached.respond_to?(:count) ? cached.count : cached
|
231
265
|
end
|
232
|
-
|
266
|
+
|
233
267
|
OPTIONS_FOR_PAGINATE = [:page, :per_page, :total_entries, :finder]
|
234
|
-
|
268
|
+
|
235
269
|
# Filter options for paginate, if *klass* is set, we get the :per_page value from it.
|
236
270
|
def opts_for_paginate
|
237
271
|
paginate_opts = combined_options.reject { |k,v| !OPTIONS_FOR_PAGINATE.include?(k) }
|
@@ -239,11 +273,11 @@ module AridCache
|
|
239
273
|
paginate_opts[:per_page] = klass.per_page if klass && !paginate_opts.include?(:per_page)
|
240
274
|
paginate_opts
|
241
275
|
end
|
242
|
-
|
276
|
+
|
243
277
|
OPTIONS_FOR_FIND = [ :conditions, :include, :joins, :limit, :offset, :order, :select, :readonly, :group, :having, :from, :lock ]
|
244
278
|
|
245
279
|
# Preserve the original order of the results if no :order option is specified.
|
246
|
-
#
|
280
|
+
#
|
247
281
|
# @arg ids array of ids to order by unless an :order option is specified. If not
|
248
282
|
# specified, cached.ids is used.
|
249
283
|
def opts_for_find(ids=nil)
|
@@ -252,40 +286,40 @@ module AridCache
|
|
252
286
|
find_opts[:order] = preserve_order(ids) unless find_opts.include?(:order)
|
253
287
|
find_opts
|
254
288
|
end
|
255
|
-
|
289
|
+
|
256
290
|
OPTIONS_FOR_CACHE = [ :expires_in ]
|
257
|
-
|
291
|
+
|
258
292
|
def opts_for_cache
|
259
293
|
combined_options.reject { |k,v| !OPTIONS_FOR_CACHE.include?(k) }
|
260
294
|
end
|
261
295
|
|
262
296
|
OPTIONS_FOR_CACHE_KEY = [ :auto_expire ]
|
263
|
-
|
297
|
+
|
264
298
|
def opts_for_cache_key
|
265
299
|
combined_options.reject { |k,v| !OPTIONS_FOR_CACHE_KEY.include?(k) }
|
266
300
|
end
|
267
|
-
|
301
|
+
|
268
302
|
def object_base_class #:nodoc:
|
269
303
|
object.is_a?(Class) ? object : object.class
|
270
304
|
end
|
271
305
|
|
272
306
|
# Generate an ORDER BY clause that preserves the ordering of the ids in *ids*.
|
273
307
|
#
|
274
|
-
# The method we use depends on the database adapter because only MySQL
|
308
|
+
# The method we use depends on the database adapter because only MySQL
|
275
309
|
# supports the ORDER BY FIELD() function. For other databases we use
|
276
310
|
# a CASE statement.
|
277
|
-
#
|
311
|
+
#
|
278
312
|
# TODO: is it quicker to sort in memory?
|
279
313
|
def preserve_order(ids)
|
280
314
|
if ids.empty?
|
281
315
|
nil
|
282
316
|
elsif ::ActiveRecord::Base.is_mysql_adapter?
|
283
|
-
"FIELD(id,#{ids.join(',')})"
|
317
|
+
"FIELD(id,#{ids.join(',')})"
|
284
318
|
else
|
285
319
|
order = ''
|
286
320
|
ids.each_index { |i| order << "WHEN id=#{ids[i]} THEN #{i+1} " }
|
287
321
|
"CASE " + order + " END"
|
288
322
|
end
|
289
|
-
end
|
323
|
+
end
|
290
324
|
end
|
291
325
|
end
|
data/test/arid_cache_test.rb
CHANGED
@@ -10,50 +10,50 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
10
10
|
assert_instance_of AridCache::Store, AridCache.store
|
11
11
|
assert_same AridCache::CacheProxy, AridCache.cache
|
12
12
|
end
|
13
|
-
|
13
|
+
|
14
14
|
test "should respond to methods" do
|
15
15
|
assert User.respond_to?(:clear_caches)
|
16
|
-
assert @user.respond_to?(:clear_caches)
|
16
|
+
assert @user.respond_to?(:clear_caches)
|
17
17
|
assert_instance_of AridCache::Store, AridCache.store
|
18
18
|
assert_same AridCache::CacheProxy, AridCache.cache
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
test "should not clobber model methods" do
|
22
22
|
assert_respond_to @user, :name
|
23
23
|
assert_respond_to Company.first, :name
|
24
24
|
assert_nothing_raised { @user.name }
|
25
25
|
assert_nothing_raised { Company.first.name }
|
26
26
|
end
|
27
|
-
|
27
|
+
|
28
28
|
test "should not clobber method_missing" do
|
29
29
|
assert_nothing_raised { @user.is_high? }
|
30
|
-
assert @user.is_high?
|
30
|
+
assert @user.is_high?
|
31
31
|
end
|
32
|
-
|
32
|
+
|
33
33
|
test "should not clobber respond_to?" do
|
34
34
|
assert @user.respond_to?(:respond_not_overridden)
|
35
35
|
end
|
36
|
-
|
36
|
+
|
37
37
|
test "should raise an error on invalid dynamic caches" do
|
38
38
|
assert_raises ArgumentError do
|
39
39
|
@user.cached_invalid_companies
|
40
40
|
end
|
41
41
|
end
|
42
|
-
|
42
|
+
|
43
43
|
test "should create dynamic caches given valid arguments" do
|
44
44
|
assert_nothing_raised { @user.cached_companies }
|
45
45
|
end
|
46
|
-
|
46
|
+
|
47
47
|
test "counts queries correctly" do
|
48
48
|
assert_queries(1) { User.all }
|
49
49
|
end
|
50
|
-
|
50
|
+
|
51
51
|
test "returns valid results" do
|
52
52
|
@one = @user.cached_companies
|
53
53
|
assert_equal @user.companies, @one
|
54
54
|
assert_equal @user.companies.count, @one.size
|
55
55
|
end
|
56
|
-
|
56
|
+
|
57
57
|
test "paginates results" do
|
58
58
|
results = @user.cached_companies(:page => 1, :per_page => 3, :order => 'name desc')
|
59
59
|
assert_kind_of WillPaginate::Collection, results
|
@@ -61,34 +61,34 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
61
61
|
assert_equal @user.companies.count, results.total_entries
|
62
62
|
assert_equal 1, results.current_page
|
63
63
|
end
|
64
|
-
|
64
|
+
|
65
65
|
test "should work for different pages" do
|
66
66
|
results = @user.cached_companies(:page => 2, :per_page => 3)
|
67
67
|
assert_kind_of WillPaginate::Collection, results
|
68
68
|
assert results.size <= 3
|
69
|
-
assert_equal @user.companies.count, results.total_entries
|
70
|
-
assert_equal 2, results.current_page
|
71
|
-
end
|
72
|
-
|
69
|
+
assert_equal @user.companies.count, results.total_entries
|
70
|
+
assert_equal 2, results.current_page
|
71
|
+
end
|
72
|
+
|
73
73
|
test "ignores random parameters" do
|
74
74
|
result = @user.cached_companies(:invalid => :params, 'random' => 'values', :user_id => 3)
|
75
75
|
assert_equal @user.companies, result
|
76
76
|
end
|
77
|
-
|
77
|
+
|
78
78
|
test "passes on options to find" do
|
79
79
|
actual = @user.cached_companies(:order => 'users.id DESC')
|
80
80
|
expected = @user.companies
|
81
81
|
assert_equal expected, actual
|
82
82
|
assert_equal expected.first, actual.first
|
83
83
|
end
|
84
|
-
|
84
|
+
|
85
85
|
test "caches the count when it gets records" do
|
86
86
|
assert_queries(1) do
|
87
87
|
@user.cached_companies
|
88
88
|
@user.cached_companies_count
|
89
89
|
end
|
90
90
|
end
|
91
|
-
|
91
|
+
|
92
92
|
test "gets the count only if it's requested first" do
|
93
93
|
count = @user.companies.count
|
94
94
|
assert_queries(1) do
|
@@ -100,7 +100,7 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
100
100
|
assert_equal count, @user.cached_companies_count
|
101
101
|
end
|
102
102
|
end
|
103
|
-
|
103
|
+
|
104
104
|
test "applies limit and offset" do
|
105
105
|
result = @user.cached_limit_companies do
|
106
106
|
companies
|
@@ -108,22 +108,22 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
108
108
|
limit_two_result = @user.cached_limit_companies(:limit => 2)
|
109
109
|
assert_equal 2, limit_two_result.size
|
110
110
|
assert_equal result[0, 2], limit_two_result
|
111
|
-
|
111
|
+
|
112
112
|
limit_three_result = @user.cached_limit_companies(:limit => 3)
|
113
113
|
assert_equal 3, limit_three_result.size
|
114
114
|
assert_equal result[0, 3], limit_three_result
|
115
|
-
|
115
|
+
|
116
116
|
limit_two_offset_one_result = @user.cached_limit_companies(:limit => 2, :offset => 1)
|
117
117
|
assert_equal 2, limit_two_offset_one_result.size
|
118
118
|
assert_equal result[1, 2], limit_two_offset_one_result
|
119
|
-
|
119
|
+
|
120
120
|
offset_one_result = @user.cached_limit_companies(:offset => 1)
|
121
121
|
assert_equal result.size-1, offset_one_result.size
|
122
122
|
assert_equal result[1, result.size], offset_one_result
|
123
|
-
|
123
|
+
|
124
124
|
assert_equal @user.companies.all(:limit => 2, :offset => 1), @user.cached_limit_companies(:limit => 2, :offset => 1)
|
125
|
-
assert_equal @user.companies.size, @user.cached_limit_companies.size
|
126
|
-
|
125
|
+
assert_equal @user.companies.size, @user.cached_limit_companies.size
|
126
|
+
|
127
127
|
# Careful of this Rails bug: https://rails.lighthouseapp.com/projects/8994/tickets/1349-named-scope-with-group-by-bug
|
128
128
|
User.cached_successful_limit_companies do
|
129
129
|
User.successful.all
|
@@ -131,27 +131,27 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
131
131
|
assert_equal 2, User.cached_successful_limit_companies(:limit => 2).size
|
132
132
|
assert_equal 3, User.cached_successful_limit_companies(:limit => 3).size
|
133
133
|
assert_equal User.successful.all(:limit => 2, :offset => 1), User.cached_successful_limit_companies(:limit => 2, :offset => 1)
|
134
|
-
assert_equal User.successful.all.size, User.cached_successful_limit_companies.size
|
134
|
+
assert_equal User.successful.all.size, User.cached_successful_limit_companies.size
|
135
135
|
end
|
136
|
-
|
136
|
+
|
137
137
|
test "should requery the database for limits with order" do
|
138
138
|
@user.cached_companies # prime the cache
|
139
139
|
assert_equal @user.companies.find(:all, :limit => 3, :order => 'name DESC'), @user.cached_companies(:limit => 3, :order => 'name DESC')
|
140
140
|
assert_equal @user.companies.find(:all, :limit => 3, :order => 'name ASC'), @user.cached_companies(:limit => 3, :order => 'name ASC')
|
141
141
|
end
|
142
|
-
|
142
|
+
|
143
143
|
test "should activate will paginate" do
|
144
144
|
assert_nothing_raised do
|
145
145
|
User.paginate(:page => 1)
|
146
146
|
end
|
147
147
|
end
|
148
|
-
|
148
|
+
|
149
149
|
test "should requery the database for paginate with order" do
|
150
150
|
@user.cached_companies # prime the cache
|
151
151
|
assert_equal @user.companies.paginate(:page => 1, :per_page => 3, :order => 'name DESC'), @user.cached_companies(:page => 1, :per_page => 3, :order => 'name DESC')
|
152
152
|
assert_equal @user.companies.paginate(:page => 1, :per_page => 3, :order => 'name ASC'), @user.cached_companies(:page => 1, :per_page => 3, :order => 'name ASC')
|
153
153
|
end
|
154
|
-
|
154
|
+
|
155
155
|
test "pagination should not result in an extra query" do
|
156
156
|
assert_queries(1) do
|
157
157
|
@user.cached_big_companies(:page => 1)
|
@@ -160,7 +160,7 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
160
160
|
User.cached_companies(:page => 1)
|
161
161
|
end
|
162
162
|
end
|
163
|
-
|
163
|
+
|
164
164
|
test "should support a 'force' option" do
|
165
165
|
# ActiveRecord caches the result of the proc, so we need to
|
166
166
|
# use different instances of the user to test the force option.
|
@@ -177,7 +177,7 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
177
177
|
assert_equal size, uncached_user.cached_companies_count(:force => true)
|
178
178
|
end
|
179
179
|
end
|
180
|
-
|
180
|
+
|
181
181
|
test "should handle various different model instances" do
|
182
182
|
one = User.first
|
183
183
|
two = User.first :offset => 1
|
@@ -185,13 +185,13 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
185
185
|
assert_equal one.companies, one.cached_companies
|
186
186
|
assert_equal two.companies, two.cached_companies
|
187
187
|
end
|
188
|
-
|
188
|
+
|
189
189
|
test "should handle arrays of non-active record instances" do
|
190
190
|
assert_equal @user.pet_names, @user.cached_pet_names
|
191
191
|
assert_equal @user.pet_names, @user.cached_pet_names
|
192
192
|
assert_equal @user.pet_names.count, @user.cached_pet_names_count
|
193
193
|
end
|
194
|
-
|
194
|
+
|
195
195
|
test "should empty the Rails cache" do
|
196
196
|
@user.cached_companies
|
197
197
|
User.cached_companies
|
@@ -199,9 +199,9 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
199
199
|
assert Rails.cache.exist?(User.arid_cache_key('companies'))
|
200
200
|
User.clear_caches
|
201
201
|
assert !Rails.cache.exist?(@user.arid_cache_key('companies'))
|
202
|
-
assert !Rails.cache.exist?(User.arid_cache_key('companies'))
|
202
|
+
assert !Rails.cache.exist?(User.arid_cache_key('companies'))
|
203
203
|
end
|
204
|
-
|
204
|
+
|
205
205
|
test "should support expiring caches" do
|
206
206
|
# The first query should put the count in the cache. The second query
|
207
207
|
# should read the count from the cache. The third query should
|
@@ -213,16 +213,16 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
213
213
|
User.cached_companies_count(:expires_in => 1.second)
|
214
214
|
end
|
215
215
|
end
|
216
|
-
|
216
|
+
|
217
217
|
test "should support an auto-expire option" do
|
218
218
|
assert_match %r[users/#{@user.id}-companies], @user.arid_cache_key('companies')
|
219
219
|
assert_equal @user.arid_cache_key('companies'), @user.arid_cache_key('companies', :auto_expire => false)
|
220
220
|
assert_match %r[users/#{@user.id}-\d{14}-companies], @user.arid_cache_key('companies', :auto_expire => true)
|
221
|
-
|
221
|
+
|
222
222
|
# It doesn't apply to class caches, but shouldn't affect it either
|
223
223
|
assert_equal User.arid_cache_key('companies'), User.arid_cache_key('companies', :auto_expire => true)
|
224
224
|
end
|
225
|
-
|
225
|
+
|
226
226
|
test "should turn off auto-expire by default" do
|
227
227
|
assert_queries(2) do
|
228
228
|
@user.cached_companies_count
|
@@ -230,7 +230,7 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
230
230
|
@user.cached_companies_count
|
231
231
|
end
|
232
232
|
end
|
233
|
-
|
233
|
+
|
234
234
|
test "should reload auto-expired caches" do
|
235
235
|
assert_queries(2) do
|
236
236
|
@user.cached_companies_count(:auto_expire => true)
|
@@ -240,29 +240,29 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
240
240
|
@user.cached_companies_count(:auto_expire => true)
|
241
241
|
end
|
242
242
|
end
|
243
|
-
|
243
|
+
|
244
244
|
test "should support configuring instance caches" do
|
245
245
|
User.instance_caches { best_companies { companies } }
|
246
246
|
assert_equal @user.companies, @user.cached_best_companies
|
247
247
|
end
|
248
|
-
|
248
|
+
|
249
249
|
test "instance caches should work on all instances of the class" do
|
250
250
|
User.instance_caches { best_companies { companies } }
|
251
251
|
assert_equal @user.cached_best_companies, User.first.cached_best_companies
|
252
252
|
end
|
253
|
-
|
253
|
+
|
254
254
|
test "should support configuring class caches" do
|
255
255
|
User.class_caches { successful_users { successful } }
|
256
256
|
assert_equal User.successful, User.cached_successful_users
|
257
257
|
end
|
258
|
-
|
258
|
+
|
259
259
|
test "should create valid store keys" do
|
260
260
|
assert_equal 'user-key', AridCache.store.send(:class_store_key, User, 'key')
|
261
261
|
assert_equal 'users-key', AridCache.store.send(:instance_store_key, User, 'key')
|
262
262
|
assert_equal AridCache.store.send(:instance_store_key, User, 'key'), AridCache.store.send(:object_store_key, @user, 'key')
|
263
263
|
assert_equal AridCache.store.send(:class_store_key, User, 'key'), AridCache.store.send(:object_store_key, User, 'key')
|
264
264
|
end
|
265
|
-
|
265
|
+
|
266
266
|
test "configuring caches should not perform any queries" do
|
267
267
|
User.instance_caches do
|
268
268
|
best_companies { companies }
|
@@ -271,7 +271,7 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
271
271
|
best_companies(:order => 'name DESC') { companies.find(:all, :order => 'name DESC') }
|
272
272
|
end
|
273
273
|
end
|
274
|
-
|
274
|
+
|
275
275
|
test "should support options in the cache configuration" do
|
276
276
|
User.instance_caches(:auto_expire => true) do
|
277
277
|
expires_in = 1.second
|
@@ -289,9 +289,9 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
289
289
|
# Call it twice to ensure that on the second call the order is applied when retrieving the
|
290
290
|
# records by id
|
291
291
|
assert_equal User.successful.find(:all, :order => 'name DESC'), User.cached_most_successful
|
292
|
-
assert_equal User.successful.find(:all, :order => 'name DESC'), User.cached_most_successful
|
292
|
+
assert_equal User.successful.find(:all, :order => 'name DESC'), User.cached_most_successful
|
293
293
|
end
|
294
|
-
|
294
|
+
|
295
295
|
test "should not raise an error if all cached ids cannot be found" do
|
296
296
|
@user.cached_companies
|
297
297
|
key = @user.arid_cache_key('companies')
|
@@ -301,7 +301,7 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
301
301
|
assert_nothing_raised { @user.cached_companies }
|
302
302
|
assert_equal @user.cached_companies, @user.companies
|
303
303
|
end
|
304
|
-
|
304
|
+
|
305
305
|
test "should not raise an error if all cached ids cannot be found while paginating" do
|
306
306
|
@user.cached_companies
|
307
307
|
key = @user.arid_cache_key('companies')
|
@@ -311,7 +311,7 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
311
311
|
assert_nothing_raised { @user.cached_companies(:page => 1, :order => 'name DESC') }
|
312
312
|
assert_equal @user.cached_companies(:page => 1, :order => 'name DESC'), @user.companies.paginate(:page => 1, :order => 'name DESC')
|
313
313
|
end
|
314
|
-
|
314
|
+
|
315
315
|
test "should handle empty collections" do
|
316
316
|
@user.cached_empty_collection { [] }
|
317
317
|
assert_nothing_raised { @user.cached_empty_collection }
|
@@ -337,12 +337,40 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
337
337
|
end
|
338
338
|
assert_equal @user.companies.all(:order => 'name DESC'), AridCache.cache.fetch(@user, 'my-fancy-key', :order => 'name DESC') { companies }
|
339
339
|
end
|
340
|
-
|
340
|
+
|
341
|
+
test "empty user relation should be empty" do
|
342
|
+
assert_equal [], @user.empty_user_relations
|
343
|
+
end
|
344
|
+
|
345
|
+
test "should not query the database for ids when the list is empty" do
|
346
|
+
assert_queries(1) do
|
347
|
+
@user.cached_empty_user_relations
|
348
|
+
result = @user.cached_empty_user_relations
|
349
|
+
assert_kind_of Array, result
|
350
|
+
end
|
351
|
+
end
|
352
|
+
|
353
|
+
test "should not query the database for ids when the list is empty and we are paginating" do
|
354
|
+
assert_queries(1) do
|
355
|
+
@user.cached_empty_user_relations(:page => 1)
|
356
|
+
result = @user.cached_empty_user_relations(:page => 1)
|
357
|
+
assert_kind_of WillPaginate::Collection, result
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
test "should not query the database for ids when the list is empty and we are applying limits etc" do
|
362
|
+
assert_queries(1) do
|
363
|
+
@user.cached_empty_user_relations(:limit => 10)
|
364
|
+
result = @user.cached_empty_user_relations(:limit => 10)
|
365
|
+
assert_kind_of Array, result
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
341
369
|
#
|
342
370
|
# Tests requiring manual verification by looking at the SQL logs.
|
343
371
|
# TODO move these to a separate class.
|
344
372
|
#
|
345
|
-
|
373
|
+
|
346
374
|
test "should preserve original ordering" do
|
347
375
|
# TODO. This one is hard to test because SQL usually keeps the same ordering
|
348
376
|
# and it's difficult to make it do otherwise. Best to just inspect the queries
|
@@ -350,13 +378,13 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
350
378
|
@user.cached_companies
|
351
379
|
@user.cached_companies
|
352
380
|
end
|
353
|
-
|
381
|
+
|
354
382
|
test "should paginate collections in memory" do
|
355
383
|
# TODO. Tough to test because we can't just count queries
|
356
384
|
# have to look at the SQL in the logs for this one.
|
357
385
|
@user.cached_companies(:page => 2, :per_page => 3)
|
358
386
|
@user.cached_companies(:page => 1, :per_page => 3)
|
359
|
-
end
|
387
|
+
end
|
360
388
|
|
361
389
|
protected
|
362
390
|
|
data/test/db/schema.rb
CHANGED
@@ -4,12 +4,16 @@ ActiveRecord::Schema.define do
|
|
4
4
|
t.column "email", :text
|
5
5
|
t.timestamps
|
6
6
|
end
|
7
|
-
|
7
|
+
|
8
8
|
create_table "companies", :force => true do |t|
|
9
9
|
t.column "name", :text
|
10
10
|
t.column "owner_id", :integer
|
11
11
|
t.column "country_id", :integer
|
12
|
-
t.column "employees", :integer
|
12
|
+
t.column "employees", :integer
|
13
13
|
t.timestamps
|
14
14
|
end
|
15
|
+
|
16
|
+
create_table "empty_user_relations", :force => true do |t|
|
17
|
+
t.column "user_id", :integer
|
18
|
+
end
|
15
19
|
end
|
data/test/models/user.rb
CHANGED
@@ -2,6 +2,7 @@ require 'arid_cache'
|
|
2
2
|
|
3
3
|
class User < ActiveRecord::Base
|
4
4
|
has_many :companies, :foreign_key => :owner_id
|
5
|
+
has_many :empty_user_relations # This must always return an empty list
|
5
6
|
named_scope :companies, :joins => :companies
|
6
7
|
named_scope :successful, :joins => :companies, :conditions => 'companies.employees > 50', :group => 'users.id'
|
7
8
|
|
data/test/test_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: arid_cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Karl Varga
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-
|
12
|
+
date: 2010-07-05 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -90,6 +90,7 @@ files:
|
|
90
90
|
- test/lib/blueprint.rb
|
91
91
|
- test/log/.gitignore
|
92
92
|
- test/models/company.rb
|
93
|
+
- test/models/empty_user_relation.rb
|
93
94
|
- test/models/user.rb
|
94
95
|
- test/test_helper.rb
|
95
96
|
has_rdoc: true
|
@@ -129,5 +130,6 @@ test_files:
|
|
129
130
|
- test/lib/active_support/cache/file_store_extras.rb
|
130
131
|
- test/lib/blueprint.rb
|
131
132
|
- test/models/company.rb
|
133
|
+
- test/models/empty_user_relation.rb
|
132
134
|
- test/models/user.rb
|
133
135
|
- test/test_helper.rb
|