arid_cache 1.0.4 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/README.rdoc +86 -20
- data/VERSION +1 -1
- data/arid_cache.gemspec +4 -4
- data/lib/arid_cache/cache_proxy.rb +66 -30
- data/lib/arid_cache/helpers.rb +8 -1
- data/spec/arid_cache/cache_proxy_result_spec.rb +53 -0
- data/spec/arid_cache/cache_proxy_spec.rb +81 -5
- data/spec/spec_helper.rb +1 -1
- data/test/arid_cache_test.rb +5 -5
- metadata +6 -6
- data/Gemfile.lock +0 -59
- data/tasks/arid_cache_tasks.rake +0 -4
data/.gitignore
CHANGED
data/README.rdoc
CHANGED
@@ -6,9 +6,29 @@ AridCache supports caching large, expensive ActiveRecord collections by caching
|
|
6
6
|
|
7
7
|
AridCache simplifies caching by supporting auto-expiring cache keys - as well as common options like <tt>:expires_in</tt> - and provides methods to help you manage your caches at the global, model class, model instance and per-cache level.
|
8
8
|
|
9
|
+
== Changes
|
10
|
+
|
11
|
+
v1.0.5: Support <tt>:raw</tt> and <tt>:clear</tt> options.
|
12
|
+
|
9
13
|
== Install
|
10
14
|
|
11
|
-
|
15
|
+
<b>Rails 3:</b>
|
16
|
+
|
17
|
+
Add the gem to your `Gemfile`
|
18
|
+
|
19
|
+
gem 'arid_cache'
|
20
|
+
|
21
|
+
Then
|
22
|
+
|
23
|
+
bundle install
|
24
|
+
|
25
|
+
For some reason AridCache is not being included into ActiveRecord, so add the following to an initializer to get around that until I fix it:
|
26
|
+
|
27
|
+
AridCache.init_rails
|
28
|
+
|
29
|
+
<b>Rails 2:</b>
|
30
|
+
|
31
|
+
Add the gem to your <tt>config/environment.rb</tt> file:
|
12
32
|
|
13
33
|
config.gem 'arid_cache'
|
14
34
|
|
@@ -16,8 +36,6 @@ Then
|
|
16
36
|
|
17
37
|
rake gems:install
|
18
38
|
|
19
|
-
(Requires having GemCutter in your gem sources.)
|
20
|
-
|
21
39
|
== Introduction
|
22
40
|
|
23
41
|
The name AridCache comes from <b>A</b>ctive<b>R</b>ecord *ID* Cache. It's also very DRY...get it? :)
|
@@ -148,7 +166,7 @@ Or via the cache configuration:
|
|
148
166
|
top_tracks(:auto_expire => true)
|
149
167
|
end
|
150
168
|
|
151
|
-
If you need to examine values in the cache yourself you can build the AridCache key by calling <tt>arid_cache_key('method')</tt> on your object. Using the examples above we would call,
|
169
|
+
If you need to examine values in the cache yourself you can build the AridCache key by calling <tt>arid_cache_key('method')</tt> on your object, whether it is a class or instance. Using the examples above we would call,
|
152
170
|
|
153
171
|
Album.arid_cache_key('featured_albums') => arid-cache-album-featured_albums
|
154
172
|
album.arid_cache_key('top_tracks') => arid-cache-albums/2-top_tracks
|
@@ -174,7 +192,12 @@ Alternatively you can pass a <b><tt>:force => true</tt></b> option in your <tt>c
|
|
174
192
|
Album.cached_featured_albums(:force => true) => returns featured albums
|
175
193
|
album.cached_top_tracks(:force => true) => returns top tracks
|
176
194
|
|
177
|
-
|
195
|
+
If you just want to clear a cache without forcing a refresh pass <b><tt>:clear => true</tt></b>. The cached value will be deleted with no unnecessary queries or cache reads being performed. It is safe to pass this option even if there is nothing in the cache yet. The method returns the result of calling <tt>delete</tt> on your cache object. For example:
|
196
|
+
|
197
|
+
Album.cached_featured_albums(:clear => true) => returns false
|
198
|
+
Rails.cache.read(Album.arid_cache_key(:featured_albums)) => returns nil
|
199
|
+
|
200
|
+
You can pass an <b><tt>:expires_in</tt></b> option to your caches to manage your cache expiry (if your cache store supports this option, which most do).
|
178
201
|
|
179
202
|
Album.cached_featured_albums(:expires_in => 1.day)
|
180
203
|
album.cached_top_tracks(:expires_in => 1.day)
|
@@ -186,7 +209,7 @@ Or via the cache configuration,
|
|
186
209
|
featured_albums
|
187
210
|
end
|
188
211
|
|
189
|
-
If you would like to be able to pass more options to your cache store (like <tt>:
|
212
|
+
If you would like to be able to pass more options to your cache store (like <tt>:unless_exists</tt>, etc), just add them to the <tt>AridCache::CacheProxy::OPTIONS_FOR_CACHE</tt> class constant, for example
|
190
213
|
|
191
214
|
AridCache::CacheProxy::OPTIONS_FOR_CACHE.push(:raw, :unless_exist)
|
192
215
|
|
@@ -224,6 +247,9 @@ An advantage of using AridCache is that since we already have the size of the co
|
|
224
247
|
|
225
248
|
To paginate just pass a <tt>:page</tt> option in your call to <tt>cached_</tt>. If you don't pass a value for <tt>:per_page</tt> AridCache gets the value from <tt>Model.per_page</tt>, which is what <tt>WillPaginate</tt> uses.
|
226
249
|
|
250
|
+
The supported pagination options are:
|
251
|
+
:page, :per_page, :total_entries, :finder
|
252
|
+
|
227
253
|
Some examples of pagination:
|
228
254
|
|
229
255
|
User.cached_active(:page => 1, :per_page => 30)
|
@@ -263,14 +289,59 @@ You apply <tt>:limit</tt> and <tt>:offset</tt> options in a similar manner to th
|
|
263
289
|
|
264
290
|
=== Other Options to <tt>find</tt>
|
265
291
|
|
292
|
+
The supported options to <tt>find</tt> are:
|
293
|
+
:conditions, :include, :joins, :limit, :offset, :order,
|
294
|
+
:select, :readonly, :group, :having, :from, :lock
|
295
|
+
|
266
296
|
You can pass options like <tt>:include</tt> (or any other valid <tt>find</tt> options) to augment the results of your cached query. Just because all of the options are supported, does not mean it's a good idea to use them, though. Take a look at your logs to see how AridCache is interacting with the cache and the database if you don't get the results you expect.
|
267
297
|
|
268
|
-
For example,
|
298
|
+
For example, we could call:
|
299
|
+
|
300
|
+
User.cached_active(:page => 2, :per_page => 10, :include => :preferences)
|
301
|
+
|
302
|
+
To return page two of the active users, with the <tt>preferences</tt> association eager-loaded for all the users.
|
303
|
+
|
304
|
+
=== Accessing the cached IDs directly
|
305
|
+
|
306
|
+
Sometimes you may want to access the cached list of record IDs without instantiating all the records. This can be useful, for example, to determine if a particular track belongs to a user's favorite tracks. If we have cached the list of favorite tracks, we just need to determine whether the track's ID appears in the cached list of IDs.
|
307
|
+
|
308
|
+
The cached result is a <tt>AridCache::CacheProxy::Result</tt> and can be accessed by passing the <b><tt>:raw => true</tt></b> option in your cached call. The <tt>AridCache::CacheProxy::Result</tt> is a type of <tt>Struct</tt> with methods to return the <tt>ids</tt>, <tt>count</tt> and <tt>klass</tt> of the cached records.
|
309
|
+
|
310
|
+
Note that passing the <tt>:raw</tt> option to your cache store is not supported, because the AridCache option shares the same name. If you really want to get the marshalled result from your cache you will have to use <tt>cache.read</tt>, manually passing in the AridCache key and <tt>:raw</tt> option.
|
311
|
+
|
312
|
+
Usage example:
|
269
313
|
|
270
|
-
User.
|
271
|
-
|
314
|
+
user = User.first
|
315
|
+
user.cached_favorite_tracks => returns [#<Track:1>, #<Track:2>]
|
316
|
+
user.cached_favorite_tracks(:raw => true) => returns
|
317
|
+
{
|
318
|
+
:klass => "Track", # stored as a string
|
319
|
+
:count => 1,
|
320
|
+
:ids => [1, 2]
|
321
|
+
}
|
322
|
+
user.cached_favorite_tracks(:raw => true).ids => returns [1, 2]
|
272
323
|
|
273
|
-
|
324
|
+
The cache will be primed if it is empty, so you can be sure that it will always return a <tt>AridCache::CacheProxy::Result</tt>.
|
325
|
+
|
326
|
+
In some circumstances - like when you are querying on a named scope - if you have only requested a count, only the count is computed, which means the ids array is <tt>nil</tt>. When you call your cached method passing in <tt>:raw => true</tt> AridCache detects that the ids array has not yet been set, so in this case it will perform a query to seed the ids array before returning the result. This can be seen in the following example:
|
327
|
+
|
328
|
+
class User
|
329
|
+
named_scope :guests, :conditions => { :account_type => ['guest'] }
|
330
|
+
end
|
331
|
+
|
332
|
+
User.cached_guests_count => returns 4
|
333
|
+
Rails.cache.read(User.arid_cache_key(:guests)) => returns
|
334
|
+
{
|
335
|
+
:klass => "User",
|
336
|
+
:count => 4,
|
337
|
+
:ids => nil # notice the ids array is nil in the cache
|
338
|
+
}
|
339
|
+
User.cached_guests(:raw => true) => returns
|
340
|
+
{
|
341
|
+
:klass => "User",
|
342
|
+
:count => 4,
|
343
|
+
:ids => [2, 235, 236, 237] # the ids array is seeded before returning
|
344
|
+
}
|
274
345
|
|
275
346
|
== Efficiency
|
276
347
|
|
@@ -281,13 +352,16 @@ For example, assume we have a <tt>named_scope :active</tt> on <tt>User</tt> whic
|
|
281
352
|
|
282
353
|
== Compatibility
|
283
354
|
|
284
|
-
Tested on Ruby 1.8.6, 1.8.7 and 1.9.1.
|
355
|
+
Tested on Ruby 1.8.6, 1.8.7, REE 1.8.7 and 1.9.1.
|
356
|
+
Tested in Rails 2.3.* and Rails 3
|
285
357
|
|
286
358
|
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:
|
287
359
|
|
288
360
|
Array.class_eval { alias count size }
|
289
361
|
|
290
|
-
|
362
|
+
For Rails 3 for some reason AridCache is not being included into ActiveRecord, so add the following to an initializer to get around that:
|
363
|
+
|
364
|
+
AridCache.init_rails
|
291
365
|
|
292
366
|
== Resources & Metrics
|
293
367
|
|
@@ -300,14 +374,6 @@ The version of Rails shouldn't matter much, but it's working on 2.3.4.
|
|
300
374
|
1. <b>Caches that contains duplicate records will only return unique records on subsequent calls</b>. This is because of the way <tt>find</tt> works when selecting multiple ids. For example, if your query returns <tt>[#<User id: 1>, #<User id: 1>, #<User id: 1>]</tt>, the IDs are cached as <tt>[1,1,1]</tt>. On the next call to the cache we load the IDs using <tt>User.find_all_by_id([1,1,1])</tt> which returns <tt>[#<User id: 1>]</tt>, not <tt>[#<User id: 1>, #<User id: 1>, #<User id: 1>]</tt> as you might have expected.
|
301
375
|
2. <b>You can't cache polymorphic arrays</b> e.g. [#<User id: 1>, #<Pet id: 5>] because it expects all ActiveRecords to be of the same class. We could accept a <tt>:polymorphic => true</tt> option but I don't think this is a great idea because instantiating all the records would result in a lot of queries to the individual tables.
|
302
376
|
|
303
|
-
== Wish List / Coming Soon
|
304
|
-
|
305
|
-
* Some kind of memoize option.
|
306
|
-
* Priming caches. Warm up the cache for all models based on the information in the instance and class cache configurations. Would need to know when you want to prime counts as well.
|
307
|
-
* The ability to <tt>include AridCache</tt> on any class or module.
|
308
|
-
* An option to bypass ID caching when you want to store records in the cache.
|
309
|
-
* Option to turn off the <tt>respond_to?</tt> requirement for caches without blocks in cases where methods are implemented using method_missing and respond_to? hasn't been properly updated.
|
310
|
-
|
311
377
|
== Contributors
|
312
378
|
|
313
379
|
Contributions are welcome! Please,
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0
|
1
|
+
1.1.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 = "1.0
|
8
|
+
s.version = "1.1.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-12-17}
|
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
|
}
|
@@ -21,7 +21,6 @@ AridCache is designed for handling large, expensive ActiveRecord collections but
|
|
21
21
|
s.files = [
|
22
22
|
".gitignore",
|
23
23
|
"Gemfile",
|
24
|
-
"Gemfile.lock",
|
25
24
|
"LICENSE",
|
26
25
|
"README.rdoc",
|
27
26
|
"Rakefile",
|
@@ -35,13 +34,13 @@ AridCache is designed for handling large, expensive ActiveRecord collections but
|
|
35
34
|
"lib/arid_cache/store.rb",
|
36
35
|
"rails/init.rb",
|
37
36
|
"spec/arid_cache/arid_cache_spec.rb",
|
37
|
+
"spec/arid_cache/cache_proxy_result_spec.rb",
|
38
38
|
"spec/arid_cache/cache_proxy_spec.rb",
|
39
39
|
"spec/spec.opts",
|
40
40
|
"spec/spec_helper.rb",
|
41
41
|
"spec/support/ar_query.rb",
|
42
42
|
"spec/support/custom_methods.rb",
|
43
43
|
"spec/support/matchers.rb",
|
44
|
-
"tasks/arid_cache_tasks.rake",
|
45
44
|
"test/arid_cache_test.rb",
|
46
45
|
"test/console",
|
47
46
|
"test/lib/add_query_counting_to_active_record.rb",
|
@@ -62,6 +61,7 @@ AridCache is designed for handling large, expensive ActiveRecord collections but
|
|
62
61
|
s.summary = %q{Automates efficient caching of your ActiveRecord collections, gives you counts for free and supports pagination.}
|
63
62
|
s.test_files = [
|
64
63
|
"spec/arid_cache/arid_cache_spec.rb",
|
64
|
+
"spec/arid_cache/cache_proxy_result_spec.rb",
|
65
65
|
"spec/arid_cache/cache_proxy_spec.rb",
|
66
66
|
"spec/spec_helper.rb",
|
67
67
|
"spec/support/ar_query.rb",
|
@@ -43,38 +43,25 @@ module AridCache
|
|
43
43
|
Rails.cache.delete_matched(%r[arid-cache-#{key}.*])
|
44
44
|
end
|
45
45
|
|
46
|
-
|
47
|
-
# Fetching results
|
48
|
-
#
|
49
|
-
|
50
|
-
def self.fetch_count(object, key, opts={}, &block)
|
51
|
-
CacheProxy.new(object, key, opts, &block).fetch_count
|
52
|
-
end
|
53
|
-
|
54
|
-
def self.fetch(object, key, opts={}, &block)
|
55
|
-
CacheProxy.new(object, key, opts, &block).fetch
|
56
|
-
end
|
57
|
-
|
58
|
-
def initialize(object, key, opts, &block)
|
46
|
+
def initialize(object, key, opts={}, &block)
|
59
47
|
self.object = object
|
60
48
|
self.key = key
|
61
|
-
self.opts = opts.symbolize_keys
|
49
|
+
self.opts = opts.symbolize_keys
|
62
50
|
self.blueprint = AridCache.store.find(object, key)
|
63
51
|
self.block = block
|
64
52
|
self.records = nil
|
65
53
|
|
66
54
|
# The options from the blueprint merged with the options for this call
|
67
55
|
self.combined_options = self.blueprint.nil? ? self.opts : self.blueprint.opts.merge(self.opts)
|
68
|
-
|
69
56
|
self.cache_key = object.arid_cache_key(key, opts_for_cache_key)
|
70
|
-
self.cached = Rails.cache.read(cache_key, opts_for_cache)
|
71
|
-
self.klass = if self.cached && self.cached.is_a?(AridCache::CacheProxy::Result) # infer class of the results to return
|
72
|
-
self.cached.klass
|
73
|
-
else
|
74
|
-
object_base_class
|
75
|
-
end
|
76
57
|
end
|
77
58
|
|
59
|
+
#
|
60
|
+
# Fetching results
|
61
|
+
#
|
62
|
+
|
63
|
+
# Return a count of ids in the cache, or return whatever is in the cache if it is
|
64
|
+
# not a CacheProxy::Result
|
78
65
|
def fetch_count
|
79
66
|
if refresh_cache?
|
80
67
|
execute_count
|
@@ -89,20 +76,52 @@ module AridCache
|
|
89
76
|
end
|
90
77
|
end
|
91
78
|
|
79
|
+
# Return a list of records using the options provided. If the item in the cache
|
80
|
+
# is not a CacheProxy::Result it is returned as-is. If there is nothing in the cache
|
81
|
+
# the block defining the cache is exectued. If the :raw option is true, returns the
|
82
|
+
# CacheProxy::Result unmodified, ignoring other options, except where those options
|
83
|
+
# are used to initialize the cache.
|
92
84
|
def fetch
|
93
|
-
|
94
|
-
|
85
|
+
@raw_result = opts_for_cache_proxy[:raw] == true
|
86
|
+
|
87
|
+
result = if refresh_cache?
|
88
|
+
execute_find(@raw_result)
|
95
89
|
elsif cached.is_a?(AridCache::CacheProxy::Result)
|
96
|
-
if cached.has_ids?
|
97
|
-
|
98
|
-
|
99
|
-
|
90
|
+
if cached.has_ids? && @raw_result
|
91
|
+
self.cached # return it unmodified
|
92
|
+
elsif cached.has_ids?
|
93
|
+
fetch_from_cache # return a list of active records after applying options
|
94
|
+
else # true if we have only calculated the count thus far
|
95
|
+
execute_find(@raw_result)
|
100
96
|
end
|
101
97
|
else
|
102
|
-
cached
|
98
|
+
cached # some base type, return it unmodified
|
103
99
|
end
|
104
100
|
end
|
105
101
|
|
102
|
+
# Clear the cached result for this cache only
|
103
|
+
def clear_cached
|
104
|
+
Rails.cache.delete(self.cache_key, opts_for_cache)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Return the cached result for this object's key
|
108
|
+
def cached
|
109
|
+
@cached ||= Rails.cache.read(self.cache_key, opts_for_cache)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Return the class of the cached results i.e. if the cached result is a
|
113
|
+
# list of Album records, then klass returns Album. If there is nothing
|
114
|
+
# in the cache, then the class is inferred to be the class of the object
|
115
|
+
# that the cached method is being called on.
|
116
|
+
def klass
|
117
|
+
@klass ||= if self.cached && self.cached.is_a?(AridCache::CacheProxy::Result)
|
118
|
+
self.cached.klass
|
119
|
+
else
|
120
|
+
object_base_class
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
|
106
125
|
private
|
107
126
|
|
108
127
|
# Return a list of records from the database using the ids from
|
@@ -175,7 +194,14 @@ module AridCache
|
|
175
194
|
self.records = block.nil? ? object.instance_eval(key) : object.instance_eval(&block)
|
176
195
|
end
|
177
196
|
|
178
|
-
|
197
|
+
# Seed the cache by executing the stored block (or by calling a method on the object).
|
198
|
+
# Then apply any options like pagination or ordering before returning the result, which
|
199
|
+
# is either some base type, or usually, a list of active records.
|
200
|
+
#
|
201
|
+
# Options:
|
202
|
+
# raw - if true, return the CacheProxy::Result after seeding the cache, ignoring
|
203
|
+
# other options. Default is false.
|
204
|
+
def execute_find(raw = false)
|
179
205
|
get_records
|
180
206
|
cached = AridCache::CacheProxy::Result.new
|
181
207
|
|
@@ -193,8 +219,11 @@ module AridCache
|
|
193
219
|
end
|
194
220
|
end
|
195
221
|
Rails.cache.write(cache_key, cached, opts_for_cache)
|
196
|
-
|
197
222
|
self.cached = cached
|
223
|
+
|
224
|
+
# Return the raw result?
|
225
|
+
return self.cached if raw
|
226
|
+
|
198
227
|
# An order has been specified. We have to go to the database
|
199
228
|
# to order because we can't be sure that the current order is the same as the cache.
|
200
229
|
if cached.is_a?(AridCache::CacheProxy::Result) && combined_options.include?(:order)
|
@@ -300,6 +329,13 @@ module AridCache
|
|
300
329
|
combined_options.reject { |k,v| !OPTIONS_FOR_CACHE_KEY.include?(k) }
|
301
330
|
end
|
302
331
|
|
332
|
+
OPTIONS_FOR_CACHE_PROXY = [:raw, :clear]
|
333
|
+
|
334
|
+
# Returns options that affect the cache proxy result
|
335
|
+
def opts_for_cache_proxy
|
336
|
+
combined_options.reject { |k,v| !OPTIONS_FOR_CACHE_PROXY.include?(k) }
|
337
|
+
end
|
338
|
+
|
303
339
|
def object_base_class #:nodoc:
|
304
340
|
object.is_a?(Class) ? object : object.class
|
305
341
|
end
|
data/lib/arid_cache/helpers.rb
CHANGED
@@ -63,12 +63,19 @@ module AridCache
|
|
63
63
|
|
64
64
|
private
|
65
65
|
|
66
|
+
# Dynamically define a method on the object's class to return cached results
|
66
67
|
def method_for_cached(object, key, fetch_method=:fetch, method_name=nil)
|
67
68
|
method_name = ("cached_" + (method_name || key)).gsub(/[^\w\!\?]/, '_')
|
68
69
|
method_body = <<-END
|
69
70
|
def #{method_name}(*args, &block)
|
70
71
|
opts = args.empty? ? {} : args.first
|
71
|
-
|
72
|
+
|
73
|
+
proxy = AridCache::CacheProxy.new(self, #{key.inspect}, opts, &block)
|
74
|
+
if opts[:clear] == true
|
75
|
+
proxy.clear_cached
|
76
|
+
else
|
77
|
+
proxy.#{fetch_method.to_s}
|
78
|
+
end
|
72
79
|
end
|
73
80
|
END
|
74
81
|
# Get the correct object
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AridCache::CacheProxy::Result do
|
4
|
+
before :each do
|
5
|
+
class X; end
|
6
|
+
@result = AridCache::CacheProxy::Result.new
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should set the klass from a class" do
|
10
|
+
@result.klass = X
|
11
|
+
@result.klass.should be(X)
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should set the klass from an object" do
|
15
|
+
@result.klass = X.new
|
16
|
+
@result.klass.should be(X)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should store the klass as a string" do
|
20
|
+
@result.klass = X
|
21
|
+
@result[:klass].should == X.name
|
22
|
+
end
|
23
|
+
|
24
|
+
it "should not have ids if it's nil" do
|
25
|
+
@result.ids = nil
|
26
|
+
@result.has_ids?.should be_false
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should have ids" do
|
30
|
+
@result.ids = [1,2,3]
|
31
|
+
@result.has_ids?.should be_true
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should have ids even if the array is empty" do
|
35
|
+
@result.ids = []
|
36
|
+
@result.has_ids?.should be_true
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should not have a count if it's nil" do
|
40
|
+
@result.count = nil
|
41
|
+
@result.has_count?.should be_false
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should have a count" do
|
45
|
+
@result.count = 3
|
46
|
+
@result.has_count?.should be_true
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should have a count even if it is zero" do
|
50
|
+
@result.count = 0
|
51
|
+
@result.has_count?.should be_true
|
52
|
+
end
|
53
|
+
end
|
@@ -1,19 +1,95 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe AridCache::CacheProxy do
|
4
|
-
describe '
|
4
|
+
describe 'preserve_order' do
|
5
5
|
before :each do
|
6
6
|
@proxy = AridCache::CacheProxy.new(Company, 'dummy-key', {})
|
7
7
|
end
|
8
|
-
|
9
|
-
it "should be prefixed by the table name" do
|
8
|
+
|
9
|
+
it "id column should be prefixed by the table name" do
|
10
10
|
::ActiveRecord::Base.stubs(:is_mysql_adapter?).returns(true)
|
11
11
|
@proxy.send(:preserve_order, [1,2,3]).should =~ %r[#{Company.table_name}]
|
12
12
|
end
|
13
|
-
|
14
|
-
it "should be prefixed by the table name" do
|
13
|
+
|
14
|
+
it "id column should be prefixed by the table name" do
|
15
15
|
::ActiveRecord::Base.stubs(:is_mysql_adapter?).returns(false)
|
16
16
|
@proxy.send(:preserve_order, [1,2,3]).should =~ %r[#{Company.table_name}]
|
17
17
|
end
|
18
18
|
end
|
19
|
+
|
20
|
+
describe "with raw => true" do
|
21
|
+
before :each do
|
22
|
+
@user = User.make
|
23
|
+
@user.companies << Company.make
|
24
|
+
@user.companies << Company.make
|
25
|
+
@user.clear_instance_caches
|
26
|
+
end
|
27
|
+
|
28
|
+
it "should return a CacheProxy::Result" do
|
29
|
+
@user.cached_companies(:raw => true).should be_a(AridCache::CacheProxy::Result)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "result should have the same ids as the normal result" do
|
33
|
+
@user.cached_companies(:raw => true).ids.should == @user.cached_companies.collect(&:id)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should ignore :raw => false" do
|
37
|
+
@user.cached_companies(:raw => false).should == @user.cached_companies
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should only query once to seed the cache, ignoring all other options" do
|
41
|
+
lambda { @user.cached_companies(:raw => true, :limit => 0, :order => 'nonexistent_column desc') }.should query(1)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should ignore all other options if the cache has already been seeded" do
|
45
|
+
lambda {
|
46
|
+
companies = @user.cached_companies
|
47
|
+
@user.cached_companies(:raw => true, :limit => 0, :order => 'nonexistent_column').ids.should == companies.collect(&:id)
|
48
|
+
}.should query(1)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "should not use the raw option when reading from the cache" do
|
52
|
+
Rails.cache.expects(:read).with(@user.arid_cache_key(:companies), {})
|
53
|
+
@user.cached_companies(:raw => true)
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should work for calls to a method that ends with _count" do
|
57
|
+
@user.cached_bogus_count do
|
58
|
+
10
|
59
|
+
end
|
60
|
+
@user.cached_bogus_count(:raw => true).should == 10
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should work for calls to a method that ends with _count" do
|
64
|
+
@user.cached_companies_count(:raw => true).should == @user.cached_companies_count
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "with clear => true" do
|
69
|
+
before :each do
|
70
|
+
@user = User.make
|
71
|
+
@user.companies << Company.make
|
72
|
+
@user.companies << Company.make
|
73
|
+
@user.clear_instance_caches rescue Rails.cache.clear
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should not fail if there is no cached value" do
|
77
|
+
lambda { @user.cached_companies(:clear => true) }.should_not raise_exception
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should clear the cached entry" do
|
81
|
+
key = @user.arid_cache_key(:companies)
|
82
|
+
@user.cached_companies
|
83
|
+
Rails.cache.read(key).should_not be_nil
|
84
|
+
@user.cached_companies(:clear => true)
|
85
|
+
Rails.cache.read(key).should be_nil
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should not read from the cache or database" do
|
89
|
+
Rails.cache.expects(:read).never
|
90
|
+
lambda {
|
91
|
+
@user.cached_companies(:clear => true)
|
92
|
+
}.should query(0)
|
93
|
+
end
|
94
|
+
end
|
19
95
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
root_path = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
2
|
-
$LOAD_PATH.unshift(File.join(root_path, '/test/lib')) #
|
2
|
+
$LOAD_PATH.unshift(File.join(root_path, '/test/lib')) # add test/lib to the load path
|
3
3
|
|
4
4
|
require 'bundler/setup'
|
5
5
|
Bundler.require
|
data/test/arid_cache_test.rb
CHANGED
@@ -331,11 +331,11 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
331
331
|
|
332
332
|
test "should support calling store methods directly with a block" do
|
333
333
|
assert_nothing_raised do
|
334
|
-
AridCache.cache.
|
334
|
+
AridCache.cache.new(@user, 'my-fancy-key') do
|
335
335
|
companies
|
336
|
-
end
|
336
|
+
end.fetch
|
337
337
|
end
|
338
|
-
assert_equal @user.companies.all(:order => 'name DESC'), AridCache.cache.
|
338
|
+
assert_equal @user.companies.all(:order => 'name DESC'), AridCache.cache.new(@user, 'my-fancy-key', :order => 'name DESC') { companies }.fetch
|
339
339
|
end
|
340
340
|
|
341
341
|
test "empty user relation should be empty" do
|
@@ -371,14 +371,14 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
371
371
|
::ActiveRecord::Base.connection.instance_eval { def adapter_name; "Mysql2"; end }
|
372
372
|
assert_equal "Mysql2", ::ActiveRecord::Base.connection.adapter_name
|
373
373
|
assert_equal true, ::ActiveRecord::Base.is_mysql_adapter?
|
374
|
-
|
374
|
+
|
375
375
|
# Restore it otherwise the other tests break
|
376
376
|
::ActiveRecord::Base.connection.instance_eval { def adapter_name; "SQLite3"; end }
|
377
377
|
assert_equal "SQLite3", ::ActiveRecord::Base.connection.adapter_name
|
378
378
|
::ActiveRecord::Base.is_mysql_adapter = nil
|
379
379
|
assert_equal false, ::ActiveRecord::Base.is_mysql_adapter?
|
380
380
|
end
|
381
|
-
|
381
|
+
|
382
382
|
#
|
383
383
|
# Tests requiring manual verification by looking at the SQL logs.
|
384
384
|
# TODO move these to a separate class.
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: arid_cache
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 19
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 1
|
8
|
+
- 1
|
8
9
|
- 0
|
9
|
-
|
10
|
-
version: 1.0.4
|
10
|
+
version: 1.1.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Karl Varga
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-12-17 00:00:00 -08:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
@@ -89,7 +89,6 @@ extra_rdoc_files:
|
|
89
89
|
files:
|
90
90
|
- .gitignore
|
91
91
|
- Gemfile
|
92
|
-
- Gemfile.lock
|
93
92
|
- LICENSE
|
94
93
|
- README.rdoc
|
95
94
|
- Rakefile
|
@@ -103,13 +102,13 @@ files:
|
|
103
102
|
- lib/arid_cache/store.rb
|
104
103
|
- rails/init.rb
|
105
104
|
- spec/arid_cache/arid_cache_spec.rb
|
105
|
+
- spec/arid_cache/cache_proxy_result_spec.rb
|
106
106
|
- spec/arid_cache/cache_proxy_spec.rb
|
107
107
|
- spec/spec.opts
|
108
108
|
- spec/spec_helper.rb
|
109
109
|
- spec/support/ar_query.rb
|
110
110
|
- spec/support/custom_methods.rb
|
111
111
|
- spec/support/matchers.rb
|
112
|
-
- tasks/arid_cache_tasks.rake
|
113
112
|
- test/arid_cache_test.rb
|
114
113
|
- test/console
|
115
114
|
- test/lib/add_query_counting_to_active_record.rb
|
@@ -158,6 +157,7 @@ specification_version: 3
|
|
158
157
|
summary: Automates efficient caching of your ActiveRecord collections, gives you counts for free and supports pagination.
|
159
158
|
test_files:
|
160
159
|
- spec/arid_cache/arid_cache_spec.rb
|
160
|
+
- spec/arid_cache/cache_proxy_result_spec.rb
|
161
161
|
- spec/arid_cache/cache_proxy_spec.rb
|
162
162
|
- spec/spec_helper.rb
|
163
163
|
- spec/support/ar_query.rb
|
data/Gemfile.lock
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
arid_cache (1.0.3)
|
5
|
-
will_paginate
|
6
|
-
|
7
|
-
GEM
|
8
|
-
remote: http://rubygems.org/
|
9
|
-
specs:
|
10
|
-
activerecord (2.3.8)
|
11
|
-
activesupport (= 2.3.8)
|
12
|
-
activesupport (2.3.8)
|
13
|
-
columnize (0.3.1)
|
14
|
-
faker (0.3.1)
|
15
|
-
gemcutter (0.6.1)
|
16
|
-
git (1.2.5)
|
17
|
-
hoe (2.6.2)
|
18
|
-
rake (>= 0.8.7)
|
19
|
-
rubyforge (>= 2.0.4)
|
20
|
-
jeweler (1.4.0)
|
21
|
-
gemcutter (>= 0.1.0)
|
22
|
-
git (>= 1.2.5)
|
23
|
-
rubyforge (>= 2.0.0)
|
24
|
-
json_pure (1.4.6)
|
25
|
-
linecache (0.43)
|
26
|
-
machinist (1.0.6)
|
27
|
-
mocha (0.9.8)
|
28
|
-
rake
|
29
|
-
rake (0.8.7)
|
30
|
-
rspec (1.3.0)
|
31
|
-
ruby-debug (0.10.3)
|
32
|
-
columnize (>= 0.1)
|
33
|
-
ruby-debug-base (~> 0.10.3.0)
|
34
|
-
ruby-debug-base (0.10.3)
|
35
|
-
linecache (>= 0.3)
|
36
|
-
rubyforge (2.0.4)
|
37
|
-
json_pure (>= 1.1.7)
|
38
|
-
sqlite3-ruby (1.3.1)
|
39
|
-
test-unit (1.2.3)
|
40
|
-
hoe (>= 1.5.1)
|
41
|
-
will_paginate (2.3.15)
|
42
|
-
|
43
|
-
PLATFORMS
|
44
|
-
ruby
|
45
|
-
|
46
|
-
DEPENDENCIES
|
47
|
-
activerecord (= 2.3.8)
|
48
|
-
activesupport (= 2.3.8)
|
49
|
-
arid_cache!
|
50
|
-
faker (= 0.3.1)
|
51
|
-
jeweler (= 1.4.0)
|
52
|
-
machinist (= 1.0.6)
|
53
|
-
mocha (= 0.9.8)
|
54
|
-
rspec (= 1.3.0)
|
55
|
-
ruby-debug (= 0.10.3)
|
56
|
-
ruby-debug-base (= 0.10.3)
|
57
|
-
sqlite3-ruby (= 1.3.1)
|
58
|
-
test-unit (= 1.2.3)
|
59
|
-
will_paginate (= 2.3.15)
|
data/tasks/arid_cache_tasks.rake
DELETED