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.
@@ -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
- def self.clear_all_caches
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 cached.nil? || opts[:force]
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 cached.nil? || opts[:force]
88
+ if refresh_cache?
81
89
  execute_find
82
90
  elsif cached.is_a?(AridCache::CacheProxy::Result)
83
- if cached.has_ids? # paginate and fetch here
84
- klass = find_class_of_results
85
- if opts.include?(:page)
86
- klass.paginate(cached.ids, opts_for_paginate)
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
- def opts_for_paginate
189
- paginate_opts = blueprint.nil? ? opts.symbolize_keys : blueprint.opts.merge(opts.symbolize_keys)
190
- paginate_opts[:total_entries] = cached.count
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
- VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset, :order, :select, :readonly, :group, :having, :from, :lock ]
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
- find_opts = blueprint.nil? ? opts.symbolize_keys : blueprint.opts.merge(opts.symbolize_keys)
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
- def object_base_class
202
- object.is_a?(Class) ? object : object.class
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 find_class_of_results
206
- opts[:class] || (blueprint && blueprint.opts[:class]) || cached.klass || object_base_class
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
@@ -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.add(object, key, block, nil)
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
@@ -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(:key, :klass, :proc, :opts) do
8
+ Blueprint = Struct.new(:klass, :key, :opts, :proc) do
10
9
 
11
- def initialize(key, klass, proc=nil, opts={})
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=(value) # store the base class of *value*
19
- self['klass'] = value.is_a?(Class) ? value.name : value.class.name
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 add(object, key, proc, opts)
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, object, proc, opts)
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(key, object)
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
- (object.is_a?(Class) ? object.name.downcase : object.class.name.pluralize.downcase) + '-' + key.to_s
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.clear_all_caches
18
- AridCache::CacheProxy.clear_all_caches
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 ARID Cache for Rails.
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.