arid_cache 0.1.1 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/LICENSE +1 -1
- data/README.rdoc +232 -73
- data/Rakefile +24 -15
- data/VERSION +1 -1
- data/arid_cache.gemspec +14 -9
- data/lib/arid_cache/active_record.rb +47 -19
- data/lib/arid_cache/cache_proxy.rb +85 -32
- data/lib/arid_cache/helpers.rb +4 -5
- data/lib/arid_cache/store.rb +50 -11
- data/lib/arid_cache.rb +3 -3
- data/test/arid_cache_test.rb +172 -77
- data/test/db/prepare.rb +4 -5
- data/test/db/schema.rb +2 -0
- data/test/lib/active_support/cache/file_store_extras.rb +17 -0
- data/test/lib/blueprint.rb +29 -0
- data/test/models/company.rb +2 -1
- data/test/models/user.rb +9 -1
- data/test/test_helper.rb +13 -4
- metadata +21 -9
- data/test/fixtures/companies.yml +0 -6
- data/test/fixtures/users.yml +0 -5
@@ -1,6 +1,6 @@
|
|
1
1
|
module AridCache
|
2
2
|
class CacheProxy
|
3
|
-
attr_accessor :object, :key, :opts, :blueprint, :cached, :cache_key, :block, :records
|
3
|
+
attr_accessor :object, :key, :opts, :blueprint, :cached, :cache_key, :block, :records, :combined_options
|
4
4
|
|
5
5
|
# AridCache::CacheProxy::Result
|
6
6
|
#
|
@@ -17,7 +17,7 @@ module AridCache
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def klass=(value)
|
20
|
-
self['klass'] = value.is_a?(Class) ? value.name : value
|
20
|
+
self['klass'] = value.is_a?(Class) ? value.name : value.class.name
|
21
21
|
end
|
22
22
|
|
23
23
|
def klass
|
@@ -25,7 +25,11 @@ module AridCache
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
|
28
|
+
#
|
29
|
+
# Managing your caches
|
30
|
+
#
|
31
|
+
|
32
|
+
def self.clear_caches
|
29
33
|
Rails.cache.delete_matched(%r[arid-cache-.*])
|
30
34
|
end
|
31
35
|
|
@@ -37,12 +41,12 @@ module AridCache
|
|
37
41
|
def self.clear_instance_caches(object)
|
38
42
|
key = (object.is_a?(Class) ? object : object.class).name.pluralize.downcase
|
39
43
|
Rails.cache.delete_matched(%r[arid-cache-#{key}.*])
|
40
|
-
end
|
41
|
-
|
42
|
-
def self.has?(object, key)
|
43
|
-
Rails.cache.exist?(object.arid_cache_key(key))
|
44
44
|
end
|
45
45
|
|
46
|
+
#
|
47
|
+
# Fetching results
|
48
|
+
#
|
49
|
+
|
46
50
|
def self.fetch_count(object, key, opts, &block)
|
47
51
|
CacheProxy.new(object, key, opts, &block).fetch_count
|
48
52
|
end
|
@@ -54,16 +58,20 @@ module AridCache
|
|
54
58
|
def initialize(object, key, opts, &block)
|
55
59
|
self.object = object
|
56
60
|
self.key = key
|
57
|
-
self.opts = opts || {}
|
61
|
+
self.opts = opts.symbolize_keys || {}
|
58
62
|
self.blueprint = AridCache.store.find(object, key)
|
59
|
-
self.cache_key = object.arid_cache_key(key)
|
60
|
-
self.cached = Rails.cache.read(cache_key)
|
61
63
|
self.block = block
|
62
64
|
self.records = nil
|
65
|
+
|
66
|
+
# The options from the blueprint merged with the options for this call
|
67
|
+
self.combined_options = self.blueprint.nil? ? self.opts : self.blueprint.opts.merge(self.opts)
|
68
|
+
|
69
|
+
self.cache_key = object.arid_cache_key(key, opts_for_cache_key)
|
70
|
+
self.cached = Rails.cache.read(cache_key, opts_for_cache)
|
63
71
|
end
|
64
72
|
|
65
73
|
def fetch_count
|
66
|
-
if
|
74
|
+
if refresh_cache?
|
67
75
|
execute_count
|
68
76
|
elsif cached.is_a?(AridCache::CacheProxy::Result)
|
69
77
|
cached.has_count? ? cached.count : execute_count
|
@@ -77,13 +85,26 @@ module AridCache
|
|
77
85
|
end
|
78
86
|
|
79
87
|
def fetch
|
80
|
-
if
|
88
|
+
if refresh_cache?
|
81
89
|
execute_find
|
82
90
|
elsif cached.is_a?(AridCache::CacheProxy::Result)
|
83
|
-
if cached.has_ids?
|
84
|
-
klass =
|
85
|
-
if
|
86
|
-
|
91
|
+
if cached.has_ids?
|
92
|
+
klass = cached.klass || object_base_class
|
93
|
+
if combined_options.include?(:page)
|
94
|
+
if combined_options.include?(:order) # order and paginate in the database
|
95
|
+
klass.paginate(cached.ids, opts_for_find.merge(opts_for_paginate(klass)))
|
96
|
+
else # paginate in memory
|
97
|
+
paged_ids = cached.ids.paginate(opts_for_paginate(klass))
|
98
|
+
paged_ids.replace(klass.find(paged_ids, preserve_order(opts_for_find, paged_ids)))
|
99
|
+
end
|
100
|
+
elsif combined_options.include?(:limit) || combined_options.include?(:offset)
|
101
|
+
if combined_options.include?(:order) # limit and offset in the database
|
102
|
+
klass.find(cached.ids, opts_for_find)
|
103
|
+
else # limit and offset in memory
|
104
|
+
offset, limit = combined_options.delete(:offset) || 0, combined_options.delete(:limit) || cached.count
|
105
|
+
ids = cached.ids[offset, limit]
|
106
|
+
klass.find(ids, preserve_order(opts_for_find, ids))
|
107
|
+
end
|
87
108
|
else
|
88
109
|
klass.find(cached.ids, opts_for_find)
|
89
110
|
end
|
@@ -97,8 +118,12 @@ module AridCache
|
|
97
118
|
|
98
119
|
private
|
99
120
|
|
121
|
+
def refresh_cache?
|
122
|
+
cached.nil? || opts[:force]
|
123
|
+
end
|
124
|
+
|
100
125
|
def get_records
|
101
|
-
block = block || blueprint.proc
|
126
|
+
block = block || (blueprint && blueprint.proc)
|
102
127
|
self.records = block.nil? ? object.instance_eval(key) : object.instance_eval(&block)
|
103
128
|
end
|
104
129
|
|
@@ -119,7 +144,7 @@ module AridCache
|
|
119
144
|
cached.klass = object_base_class
|
120
145
|
end
|
121
146
|
end
|
122
|
-
Rails.cache.write(cache_key, cached)
|
147
|
+
Rails.cache.write(cache_key, cached, opts_for_cache)
|
123
148
|
|
124
149
|
self.cached = cached
|
125
150
|
return_records(records)
|
@@ -142,7 +167,7 @@ module AridCache
|
|
142
167
|
# If you do want a specialized, modified, or subset of the collection it's best
|
143
168
|
# to define it in a block and have a new cache for it:
|
144
169
|
#
|
145
|
-
# model.my_special_collection { the_collection(:order => 'new order') }
|
170
|
+
# model.my_special_collection { the_collection(:order => 'new order', :limit => 10) }
|
146
171
|
def return_records(records)
|
147
172
|
if opts.include?(:page)
|
148
173
|
records = records.respond_to?(:to_a) ? records.to_a : records
|
@@ -180,30 +205,58 @@ module AridCache
|
|
180
205
|
cached = records # some base type, cache it as itself
|
181
206
|
end
|
182
207
|
|
183
|
-
Rails.cache.write(cache_key, cached)
|
208
|
+
Rails.cache.write(cache_key, cached, opts_for_cache)
|
184
209
|
self.cached = cached
|
185
210
|
cached.respond_to?(:count) ? cached.count : cached
|
186
211
|
end
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
212
|
+
|
213
|
+
OPTIONS_FOR_PAGINATE = [:page, :per_page, :total_entries]
|
214
|
+
|
215
|
+
# @arg klass target class, if supplied and no :per_page option is specified, calls
|
216
|
+
# klass.per_page to get it
|
217
|
+
def opts_for_paginate(klass = nil)
|
218
|
+
paginate_opts = combined_options.reject { |k,v| !OPTIONS_FOR_PAGINATE.include?(k) }
|
219
|
+
paginate_opts[:per_page] = klass.per_page if klass && !paginate_opts.include?(:per_page)
|
191
220
|
paginate_opts
|
192
221
|
end
|
193
222
|
|
194
|
-
|
195
|
-
|
223
|
+
OPTIONS_FOR_FIND = [ :conditions, :include, :joins, :limit, :offset, :order, :select, :readonly, :group, :having, :from, :lock ]
|
224
|
+
|
196
225
|
def opts_for_find
|
197
|
-
|
198
|
-
find_opts.delete_if { |k,v| !VALID_FIND_OPTIONS.include?(k) }
|
226
|
+
combined_options.reject { |k,v| !OPTIONS_FOR_FIND.include?(k) }
|
199
227
|
end
|
200
228
|
|
201
|
-
|
202
|
-
|
229
|
+
OPTIONS_FOR_CACHE = [ :expires_in ]
|
230
|
+
|
231
|
+
def opts_for_cache
|
232
|
+
combined_options.reject { |k,v| !OPTIONS_FOR_CACHE.include?(k) }
|
203
233
|
end
|
234
|
+
|
235
|
+
OPTIONS_FOR_CACHE_KEY = [ :auto_expire ]
|
204
236
|
|
205
|
-
def
|
206
|
-
|
237
|
+
def opts_for_cache_key
|
238
|
+
combined_options.reject { |k,v| !OPTIONS_FOR_CACHE_KEY.include?(k) }
|
239
|
+
end
|
240
|
+
|
241
|
+
def object_base_class
|
242
|
+
object.is_a?(Class) ? object : object.class
|
207
243
|
end
|
244
|
+
|
245
|
+
# Preserve the original order of the results if no :order option is specified.
|
246
|
+
# The method we use depends on the database adapter because only MySQL
|
247
|
+
# supports the ORDER BY FIELD() function.
|
248
|
+
# @arg options hash options for find
|
249
|
+
# @arg ids array of ids to order by
|
250
|
+
def preserve_order(options, ids)
|
251
|
+
return options if options.include?(:order)
|
252
|
+
if ::ActiveRecord::Base.is_mysql_adapter?
|
253
|
+
options[:order] = "FIELD(id,#{ids.join(',')})"
|
254
|
+
else
|
255
|
+
order = ''
|
256
|
+
ids.each_index { |i| order << "WHEN id=#{ids[i]} THEN #{i+1} " }
|
257
|
+
options[:order] = "CASE " + order + " END"
|
258
|
+
end
|
259
|
+
options
|
260
|
+
end
|
208
261
|
end
|
209
262
|
end
|
data/lib/arid_cache/helpers.rb
CHANGED
@@ -23,7 +23,9 @@ module AridCache
|
|
23
23
|
define(object, $1, opts, :fetch_count, key)
|
24
24
|
else
|
25
25
|
raise ArgumentError.new("#{object} doesn't respond to #{key} or #{$1}! Cannot dynamically create query to get the count, please call with a block.")
|
26
|
-
end
|
26
|
+
end
|
27
|
+
elsif AridCache.store.has?(object, key)
|
28
|
+
method_for_cached(object, key, :fetch)
|
27
29
|
elsif object.respond_to?(key)
|
28
30
|
define(object, key, opts, &block)
|
29
31
|
else
|
@@ -38,9 +40,6 @@ module AridCache
|
|
38
40
|
#
|
39
41
|
# @return an AridCache::Store::Blueprint.
|
40
42
|
def define(object, key, opts, fetch_method=:fetch, method_name=nil, &block)
|
41
|
-
if block.nil? && !object.respond_to?(key)
|
42
|
-
raise ArgumentError.new("#{object} doesn't respond to #{key}! Cannot dynamically create a block for your cache item.")
|
43
|
-
end
|
44
43
|
|
45
44
|
# FIXME: Pass default options to store.add
|
46
45
|
# Pass nil in for now until we get the cache_ calls working.
|
@@ -57,7 +56,7 @@ module AridCache
|
|
57
56
|
#
|
58
57
|
# When the cache_ methods are supported, those options should be
|
59
58
|
# remembered and applied to the collection however.
|
60
|
-
blueprint = AridCache.store.
|
59
|
+
blueprint = AridCache.store.add_object_cache_configuration(object, key, nil, block)
|
61
60
|
method_for_cached(object, key, fetch_method, method_name)
|
62
61
|
blueprint
|
63
62
|
end
|
data/lib/arid_cache/store.rb
CHANGED
@@ -1,22 +1,21 @@
|
|
1
1
|
module AridCache
|
2
2
|
class Store < Hash
|
3
|
-
extend ActiveSupport::Memoizable
|
4
3
|
|
5
4
|
# AridCache::Store::Blueprint
|
6
5
|
#
|
7
6
|
# Stores options and blocks that are used to generate results for finds
|
8
7
|
# and counts.
|
9
|
-
Blueprint = Struct.new(:
|
8
|
+
Blueprint = Struct.new(:klass, :key, :opts, :proc) do
|
10
9
|
|
11
|
-
def initialize(
|
10
|
+
def initialize(klass, key, opts={}, proc=nil)
|
12
11
|
self.key = key
|
13
12
|
self.klass = klass
|
14
13
|
self.proc = proc
|
15
14
|
self.opts = opts
|
16
15
|
end
|
17
16
|
|
18
|
-
def klass=(
|
19
|
-
self['klass'] =
|
17
|
+
def klass=(object) # store the class name only
|
18
|
+
self['klass'] = object.is_a?(Class) ? object.name : object.class.name
|
20
19
|
end
|
21
20
|
|
22
21
|
def klass
|
@@ -40,6 +39,35 @@ module AridCache
|
|
40
39
|
end
|
41
40
|
end
|
42
41
|
|
42
|
+
#
|
43
|
+
# Capture cache configurations and blocks and store them in the store.
|
44
|
+
#
|
45
|
+
# A block is evaluated within the scope of this class. The blocks
|
46
|
+
# contains calls to methods which define the caches and give options
|
47
|
+
# for each cache.
|
48
|
+
#
|
49
|
+
# Don't instantiate directly. Rather instantiate the Instance- or
|
50
|
+
# ClassCacheConfiguration.
|
51
|
+
Configuration = Struct.new(:klass, :global_opts) do
|
52
|
+
|
53
|
+
def initialize(klass, global_opts={})
|
54
|
+
self.global_opts = global_opts
|
55
|
+
self.klass = klass
|
56
|
+
end
|
57
|
+
|
58
|
+
def method_missing(key, *args, &block)
|
59
|
+
opts = global_opts.merge(args.empty? ? {} : args.first)
|
60
|
+
case self
|
61
|
+
when InstanceCacheConfiguration
|
62
|
+
AridCache.store.add_instance_cache_configuration(klass, key, opts, block)
|
63
|
+
else
|
64
|
+
AridCache.store.add_class_cache_configuration(klass, key, opts, block)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
class InstanceCacheConfiguration < Configuration; end
|
69
|
+
class ClassCacheConfiguration < Configuration; end
|
70
|
+
|
43
71
|
def has?(object, key)
|
44
72
|
self.include?(object_store_key(object, key))
|
45
73
|
end
|
@@ -57,17 +85,27 @@ module AridCache
|
|
57
85
|
self[object_store_key(object, key)]
|
58
86
|
end
|
59
87
|
|
60
|
-
def
|
88
|
+
def add_instance_cache_configuration(klass, key, opts, proc)
|
89
|
+
store_key = instance_store_key(klass, key)
|
90
|
+
self[store_key] = AridCache::Store::Blueprint.new(klass, key, opts, proc)
|
91
|
+
end
|
92
|
+
|
93
|
+
def add_class_cache_configuration(klass, key, opts, proc)
|
94
|
+
store_key = class_store_key(klass, key)
|
95
|
+
self[store_key] = AridCache::Store::Blueprint.new(klass, key, opts, proc)
|
96
|
+
end
|
97
|
+
|
98
|
+
def add_object_cache_configuration(object, key, opts, proc)
|
61
99
|
store_key = object_store_key(object, key)
|
62
|
-
self[store_key] = AridCache::Store::Blueprint.new(key,
|
100
|
+
self[store_key] = AridCache::Store::Blueprint.new(object, key, opts, proc)
|
63
101
|
end
|
64
|
-
|
102
|
+
|
65
103
|
def find_or_create(object, key)
|
66
104
|
store_key = object_store_key(object, key)
|
67
105
|
if self.include?(store_key)
|
68
106
|
self[store_key]
|
69
107
|
else
|
70
|
-
self[store_key] = AridCache::Store::Blueprint.new(
|
108
|
+
self[store_key] = AridCache::Store::Blueprint.new(object, key)
|
71
109
|
end
|
72
110
|
end
|
73
111
|
|
@@ -76,9 +114,10 @@ module AridCache
|
|
76
114
|
def initialize
|
77
115
|
end
|
78
116
|
|
117
|
+
def class_store_key(klass, key); klass.name.downcase + '-' + key.to_s; end
|
118
|
+
def instance_store_key(klass, key); klass.name.downcase.pluralize + '-' + key.to_s; end
|
79
119
|
def object_store_key(object, key)
|
80
|
-
|
120
|
+
case object; when Class; class_store_key(object, key); else; instance_store_key(object.class, key); end
|
81
121
|
end
|
82
|
-
memoize :object_store_key
|
83
122
|
end
|
84
123
|
end
|
data/lib/arid_cache.rb
CHANGED
@@ -14,8 +14,8 @@ module AridCache
|
|
14
14
|
AridCache::CacheProxy
|
15
15
|
end
|
16
16
|
|
17
|
-
def self.
|
18
|
-
AridCache::CacheProxy.
|
17
|
+
def self.clear_caches
|
18
|
+
AridCache::CacheProxy.clear_caches
|
19
19
|
end
|
20
20
|
|
21
21
|
def self.clear_class_caches(object)
|
@@ -37,7 +37,7 @@ module AridCache
|
|
37
37
|
base.send(:include, AridCache::ActiveRecord)
|
38
38
|
end
|
39
39
|
|
40
|
-
# Initializes
|
40
|
+
# Initializes AridCache for Rails.
|
41
41
|
#
|
42
42
|
# This method is called by `init.rb`,
|
43
43
|
# which is run by Rails on startup.
|