arid_cache 0.0.5 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +19 -69
- data/Rakefile +1 -5
- data/VERSION +1 -1
- data/arid_cache.gemspec +5 -7
- data/lib/arid_cache/active_record.rb +29 -52
- data/lib/arid_cache/cache_proxy.rb +85 -150
- data/lib/arid_cache/store.rb +36 -62
- data/lib/arid_cache.rb +1 -33
- data/rails/init.rb +1 -1
- data/rails/install.rb +1 -0
- data/rails/uninstall.rb +1 -0
- data/test/arid_cache_test.rb +40 -118
- data/test/db/schema.rb +0 -1
- data/test/fixtures/companies.yml +2 -3
- data/test/fixtures/users.yml +3 -5
- data/test/models/user.rb +1 -9
- data/test/test_helper.rb +0 -4
- metadata +5 -8
- data/init.rb +0 -6
- data/lib/arid_cache/helpers.rb +0 -86
data/README.rdoc
CHANGED
@@ -1,50 +1,10 @@
|
|
1
1
|
== ARID Cache
|
2
2
|
|
3
|
-
ARID Cache
|
3
|
+
ARID Cache is a DRY ActiveRecord ID cache.
|
4
4
|
|
5
|
-
|
5
|
+
== Example
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
ARID Cache gives you counts for free. When a large collection is stored in the cache
|
10
|
-
ARID Cache stores the count as well so the next time you want request the count it
|
11
|
-
just takes a single read from the cache. This is also supported for your non-ActiveRecord
|
12
|
-
collections if the collection <tt>responds_to?(:count)</tt>.
|
13
|
-
|
14
|
-
Given that we have a cache like <tt>album.cached_tracks</tt> we can get the count by calling <tt>album.cached_tracks_count</tt>.
|
15
|
-
|
16
|
-
=== Auto-expiring cache keys
|
17
|
-
|
18
|
-
Caches on model instances automatically incorporate the ActiveRecord <tt>cache_key</tt> which includes the <tt>updated_at</tt> timestamp of that instance, making them auto-expire when the instance is updated.
|
19
|
-
|
20
|
-
Caches on your model classes (like on the results of named scopes) will not expire however.
|
21
|
-
|
22
|
-
ARID cache provides methods to help you expire your caches.
|
23
|
-
|
24
|
-
AridCache.clear_all_caches => expires all ARID Cache caches
|
25
|
-
Model.clear_all_caches => expires class and instance-level caches for this model
|
26
|
-
Model.clear_instance_caches => expires instance-level caches for this model
|
27
|
-
Model.clear_class_caches => expires class-level caches for this model
|
28
|
-
|
29
|
-
These methods are also available on model instances.
|
30
|
-
|
31
|
-
ARID Cache keys are based on the method you call to create the cache. For example:
|
32
|
-
Album.cached_featured_albums => cache key is arid-cache-album-featured_albums
|
33
|
-
album.cached_top_tracks => cache key like arid-cache-albums/2-20090930200-top_tracks
|
34
|
-
|
35
|
-
=== Support for pagination and options to <tt>find</tt>
|
36
|
-
|
37
|
-
ARID cache performs pagination and applies <tt>:limit</tt> and <tt>:offset</tt> to the IDs in memory and only selects the page/sub-set from the database, directly from the target table.
|
38
|
-
|
39
|
-
You can pass options like <tt>:include</tt> (or any other valid <tt>find</tt> options) to augment the results of your cached query.
|
40
|
-
|
41
|
-
=== Efficiency
|
42
|
-
|
43
|
-
ARID Cache intercepts calls to <tt>cached_</tt> methods using <tt>method_missing</tt> then defines those methods on your models as they are called, so they bypass method missing on subsequent calls.
|
44
|
-
|
45
|
-
== Examples
|
46
|
-
|
47
|
-
==== Given the following model:
|
7
|
+
Given the following model:
|
48
8
|
|
49
9
|
class User < ActiveRecord::Base
|
50
10
|
include AridCache
|
@@ -53,11 +13,12 @@ ARID Cache intercepts calls to <tt>cached_</tt> methods using <tt>method_missing
|
|
53
13
|
named_scope :active, :conditions => [ 'updated_at <= ', 5.minutes.ago ]
|
54
14
|
end
|
55
15
|
|
56
|
-
|
16
|
+
ARID Cache provides these methods:
|
57
17
|
|
58
18
|
User.cached_active # caches the user IDs and the count
|
59
19
|
User.cached_active_count # gets the count for free
|
60
20
|
|
21
|
+
user = User.first
|
61
22
|
user.cached_pets # caches the pets IDs and the count
|
62
23
|
user.cached_pets_count # gets the count for free
|
63
24
|
|
@@ -67,9 +28,9 @@ selects where the IDs are the cached IDs.
|
|
67
28
|
|
68
29
|
It also gives you paging using WillPaginate. The IDs from the cache are paginated and
|
69
30
|
only that page is selected from the database - again directly from the table, without
|
70
|
-
any complex joins.
|
31
|
+
any complex joins or anything.
|
71
32
|
|
72
|
-
|
33
|
+
Some examples of pagination:
|
73
34
|
|
74
35
|
User.cached_active.paginate(:page => 1, :per_page => 30)
|
75
36
|
User.cached_active.paginate(:page => 1)
|
@@ -79,32 +40,21 @@ You can also include options for find, such as <tt>:join</tt>, <tt>:include</tt>
|
|
79
40
|
|
80
41
|
User.cached_active.paginate(:page => 1, :include => :preferences)
|
81
42
|
User.cached_active.paginate(:page => 1, :order => 'created_at DESC') # don't change the order, just enforce it
|
82
|
-
|
83
|
-
You can limit the results returned using <tt>:limit</tt> and <tt>:offset</tt>:
|
84
|
-
|
85
|
-
user.cached_pets(:limit => 2, :include => :toys)
|
86
|
-
user.cached_pets(:limit => 2, :offset => 3, :include => :toys)
|
87
43
|
|
88
|
-
|
89
|
-
|
90
|
-
User
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
@tracks = @genre.cached_highlight_tracks(:order => 'release_date DESC', :include => [:album, :artist]) do
|
97
|
-
cached_tracks(:order => 'release_date DESC', :limit => 10, :include => [:album, :artist])
|
98
|
-
end
|
99
|
-
@artists = @genre.cached_highlight_artists do
|
100
|
-
cached_artists(:limit => 10)
|
101
|
-
end
|
102
|
-
@albums = @genre.cached_highlight_albums(:order => 'release_date DESC', :include => :artist) do
|
103
|
-
cached_albums(:order => 'release_date DESC', :limit => 3, :include => :artist)
|
104
|
-
end
|
44
|
+
You can configure your cached blocks in your models:
|
45
|
+
|
46
|
+
class User < ActiveRecord::Base
|
47
|
+
include AridCache
|
48
|
+
has_many :pets
|
49
|
+
has_one :preferences
|
50
|
+
named_scope :active, :conditions => [ 'updated_at <= ', 5.minutes.ago ]
|
105
51
|
|
106
|
-
|
52
|
+
self.cached_active_users(:order => 'created_at DESC', :include => :preferences) do
|
53
|
+
self.active
|
54
|
+
end
|
55
|
+
end
|
107
56
|
|
57
|
+
|
108
58
|
== Note on Patches/Pull Requests
|
109
59
|
|
110
60
|
* Fork the project.
|
data/Rakefile
CHANGED
@@ -6,11 +6,7 @@ begin
|
|
6
6
|
Jeweler::Tasks.new do |gem|
|
7
7
|
gem.name = "arid_cache"
|
8
8
|
gem.summary = %Q{Automates efficient caching of your ActiveRecord collections, gives you counts for free and supports pagination.}
|
9
|
-
gem.description =
|
10
|
-
ARID Cache makes caching easy and effective. ARID cache supports caching on all your model named scopes, class methods and instance methods right out of the box. ARID cache prevents caching logic from cluttering your models and clarifies your logic by making explicit calls to cached result sets.
|
11
|
-
|
12
|
-
ARID Cache is designed for handling large, expensive ActiveRecord collections but is equally useful for caching anything else as well.
|
13
|
-
END
|
9
|
+
gem.description = %Q{}
|
14
10
|
gem.email = "kjvarga@gmail.com"
|
15
11
|
gem.homepage = "http://github.com/kjvarga/arid_cache"
|
16
12
|
gem.authors = ["Karl Varga"]
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0
|
1
|
+
0.1.0
|
data/arid_cache.gemspec
CHANGED
@@ -5,14 +5,12 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{arid_cache}
|
8
|
-
s.version = "0.0
|
8
|
+
s.version = "0.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{2009-12-
|
13
|
-
s.description = %q{
|
14
|
-
ARID Cache is designed for handling large, expensive ActiveRecord collections but is equally useful for caching anything else as well.
|
15
|
-
}
|
12
|
+
s.date = %q{2009-12-21}
|
13
|
+
s.description = %q{}
|
16
14
|
s.email = %q{kjvarga@gmail.com}
|
17
15
|
s.extra_rdoc_files = [
|
18
16
|
"LICENSE",
|
@@ -25,13 +23,13 @@ ARID Cache is designed for handling large, expensive ActiveRecord collections bu
|
|
25
23
|
"Rakefile",
|
26
24
|
"VERSION",
|
27
25
|
"arid_cache.gemspec",
|
28
|
-
"init.rb",
|
29
26
|
"lib/arid_cache.rb",
|
30
27
|
"lib/arid_cache/active_record.rb",
|
31
28
|
"lib/arid_cache/cache_proxy.rb",
|
32
|
-
"lib/arid_cache/helpers.rb",
|
33
29
|
"lib/arid_cache/store.rb",
|
34
30
|
"rails/init.rb",
|
31
|
+
"rails/install.rb",
|
32
|
+
"rails/uninstall.rb",
|
35
33
|
"spec/arid_cache_spec.rb",
|
36
34
|
"spec/spec.opts",
|
37
35
|
"spec/spec_helper.rb",
|
@@ -1,67 +1,44 @@
|
|
1
1
|
module AridCache
|
2
|
-
module ActiveRecord
|
3
|
-
def self.included(base)
|
4
|
-
base.extend MirrorMethods
|
5
|
-
base.send :include, MirrorMethods
|
6
|
-
base.class_eval do
|
7
|
-
alias_method_chain :method_missing, :arid_cache
|
8
|
-
end
|
9
|
-
class << base
|
10
|
-
alias_method_chain :method_missing, :arid_cache
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
2
|
+
module ActiveRecord
|
14
3
|
module MirrorMethods
|
15
|
-
def clear_all_caches
|
16
|
-
AridCache.cache.clear_class_caches(self)
|
17
|
-
AridCache.cache.clear_instance_caches(self)
|
18
|
-
end
|
19
|
-
|
20
|
-
def clear_class_caches
|
21
|
-
AridCache.cache.clear_class_caches(self)
|
22
|
-
end
|
23
|
-
|
24
|
-
def clear_instance_caches
|
25
|
-
AridCache.cache.clear_instance_caches(self)
|
26
|
-
end
|
27
|
-
|
28
|
-
def get_singleton
|
29
|
-
class << self; self; end
|
30
|
-
end
|
31
4
|
|
32
5
|
# Return a cache key for the given key e.g.
|
33
6
|
# User.arid_cache_key('companies') => 'user-companies'
|
34
|
-
# User.first.arid_cache_key('companies') => 'users/
|
35
|
-
def arid_cache_key(key)
|
36
|
-
|
37
|
-
|
7
|
+
# User.first.arid_cache_key('companies') => 'users/1-companies'
|
8
|
+
def arid_cache_key(key, suffix=nil)
|
9
|
+
arid_cache_key = (self.is_a?(Class) ? self.name.downcase : self.cache_key) + '-' + key.to_s
|
10
|
+
suffix.nil? ? arid_cache_key : arid_cache_key + suffix
|
11
|
+
('arid-cache-' + arid_cache_key).to_sym
|
38
12
|
end
|
39
13
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
super(method, include_private)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
protected
|
14
|
+
def clear_cache
|
15
|
+
AridCache.cache.clear(self)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Return the cache store from the class
|
19
|
+
def cache_store
|
20
|
+
(self.is_a?(Class) ? self : self.class).send(:class_variable_get, :@@cache_store)
|
21
|
+
end
|
53
22
|
|
54
23
|
# Intercept methods beginning with <tt>cached_</tt>
|
55
|
-
def method_missing_with_arid_cache(method, *args, &block)
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
elsif method.to_s =~ /^cached_(.*)$/
|
60
|
-
AridCache.lookup(self, $1, opts, &block)
|
24
|
+
def method_missing_with_arid_cache(method, *args, &block)
|
25
|
+
if method.to_s =~ /^cached_(.*)$/
|
26
|
+
args = args.empty? ? {} : args.first
|
27
|
+
cache_store.query($1, args, self, &block)
|
61
28
|
else
|
62
29
|
method_missing_without_arid_cache(method, *args)
|
63
30
|
end
|
31
|
+
end
|
32
|
+
alias_method :method_missing_without_arid_cache, :method_missing
|
33
|
+
alias_method :method_missing, :method_missing_with_arid_cache
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.included(base)
|
37
|
+
base.extend MirrorMethods
|
38
|
+
base.send :include, MirrorMethods
|
39
|
+
base.class_eval do
|
40
|
+
@@cache_store = AridCache::Store.new
|
64
41
|
end
|
65
42
|
end
|
66
43
|
end
|
67
|
-
end
|
44
|
+
end
|
@@ -1,164 +1,96 @@
|
|
1
|
+
# AridCache::Cache is a singleton instance
|
1
2
|
module AridCache
|
2
3
|
class CacheProxy
|
3
|
-
|
4
|
-
|
5
|
-
# AridCache::CacheProxy::Result
|
6
|
-
#
|
7
|
-
# This struct is stored in the cache and stores information we need
|
8
|
-
# to re-query for results.
|
9
|
-
Result = Struct.new(:ids, :klass, :count) do
|
4
|
+
Struct.new('Result', :opts, :ids, :klass, :count) do
|
10
5
|
|
11
6
|
def has_count?
|
12
7
|
!count.nil?
|
13
8
|
end
|
14
|
-
|
9
|
+
|
15
10
|
def has_ids?
|
16
11
|
!ids.nil?
|
17
12
|
end
|
18
|
-
|
19
|
-
def klass=(value)
|
20
|
-
self['klass'] = value.is_a?(Class) ? value.name : value
|
21
|
-
end
|
22
|
-
|
23
|
-
def klass
|
24
|
-
self['klass'].constantize unless self['klass'].nil?
|
25
|
-
end
|
26
13
|
end
|
27
|
-
|
28
|
-
def self.clear_all_caches
|
29
|
-
Rails.cache.delete_matched(%r[arid-cache-.*])
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.clear_class_caches(object)
|
33
|
-
key = (object.is_a?(Class) ? object : object.class).name.downcase + '-'
|
34
|
-
Rails.cache.delete_matched(%r[arid-cache-#{key}.*])
|
35
|
-
end
|
36
|
-
|
37
|
-
def self.clear_instance_caches(object)
|
38
|
-
key = (object.is_a?(Class) ? object : object.class).name.pluralize.downcase
|
39
|
-
Rails.cache.delete_matched(%r[arid-cache-#{key}.*])
|
40
|
-
end
|
41
14
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
15
|
+
# Clear the cache of all arid cache entries.
|
16
|
+
#
|
17
|
+
# If *object* is passed, only clear cached entries for that object's
|
18
|
+
# class and instances e.g.
|
19
|
+
# User.clear_cache deletes 'arid-cache-users/1-companies' as well
|
20
|
+
# as 'arid-cache-user-companies'
|
21
|
+
def clear(object=nil)
|
22
|
+
key = 'arid-cache-'
|
23
|
+
key += (object.is_a?(Class) ? object : object.class).name.downcase unless object.nil?
|
24
|
+
Rails.cache.delete_matched(%r[#{key}.*])
|
48
25
|
end
|
49
|
-
|
50
|
-
def self.
|
51
|
-
|
26
|
+
|
27
|
+
def self.instance
|
28
|
+
@@singleton_instance ||= self.new
|
52
29
|
end
|
53
|
-
|
54
|
-
def
|
55
|
-
|
56
|
-
self.key = key
|
57
|
-
self.opts = opts || {}
|
58
|
-
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
|
-
self.block = block
|
62
|
-
self.records = nil
|
30
|
+
|
31
|
+
def has?(object, key)
|
32
|
+
Rails.cache.exist?(object.arid_cache_key(key))
|
63
33
|
end
|
64
|
-
|
65
|
-
def fetch_count
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
cached
|
72
|
-
elsif cached.respond_to?(:count)
|
73
|
-
cached.count
|
34
|
+
|
35
|
+
def fetch_count(blueprint)
|
36
|
+
cached = Rails.cache.read(blueprint.cache_key)
|
37
|
+
if cached.nil?
|
38
|
+
execute_count(blueprint)
|
39
|
+
elsif cached.is_a?(Struct::Result)
|
40
|
+
cached.has_count? ? cached.count : execute_count(blueprint)
|
74
41
|
else
|
75
|
-
cached #
|
42
|
+
cached # some base type, return it
|
76
43
|
end
|
77
44
|
end
|
78
|
-
|
79
|
-
def fetch
|
80
|
-
|
81
|
-
|
82
|
-
|
45
|
+
|
46
|
+
def fetch(blueprint, opts)
|
47
|
+
cached = Rails.cache.read(blueprint.cache_key)
|
48
|
+
if cached.nil?
|
49
|
+
execute_find(blueprint, opts)
|
50
|
+
elsif cached.is_a?(Struct::Result)
|
83
51
|
if cached.has_ids? # paginate and fetch here
|
84
|
-
klass = find_class_of_results
|
85
52
|
if opts.include?(:page)
|
86
|
-
klass.paginate(cached.ids, opts_for_paginate)
|
53
|
+
blueprint.klass.paginate(cached.ids, opts_for_paginate(opts, cached))
|
87
54
|
else
|
88
|
-
klass.find(cached.ids, opts_for_find)
|
55
|
+
blueprint.klass.find(cached.ids, opts_for_find(opts, cached))
|
89
56
|
end
|
90
57
|
else
|
91
|
-
execute_find
|
58
|
+
execute_find(blueprint, opts)
|
92
59
|
end
|
93
60
|
else
|
94
61
|
cached # some base type, return it
|
95
62
|
end
|
96
63
|
end
|
97
|
-
|
64
|
+
|
98
65
|
private
|
99
66
|
|
100
|
-
def
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
cached.
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
elsif !records.empty?
|
117
|
-
cached.klass = records.first.class
|
118
|
-
else
|
119
|
-
cached.klass = object_base_class
|
120
|
-
end
|
67
|
+
def execute_find(blueprint, opts)
|
68
|
+
records = blueprint.proc.call
|
69
|
+
|
70
|
+
if !records.is_a?(Enumerable)
|
71
|
+
return records # some base type, return it
|
72
|
+
end
|
73
|
+
|
74
|
+
# Update Rails cache and return the records
|
75
|
+
cached = Struct::Result.new(blueprint.opts)
|
76
|
+
cached.ids = records.collect(&:id)
|
77
|
+
cached.count = records.size
|
78
|
+
if records.respond_to?(:proxy_reflection) # association proxy
|
79
|
+
cached.klass = records.proxy_reflection.klass
|
80
|
+
elsif records.is_a?(Enumerable)
|
81
|
+
cached.klass = records.empty? ? blueprint.klass : records.first.class
|
82
|
+
RAILS_DEFAULT_LOGGER.info("** AridCache: inferring class of collection for cache #{blueprint.cache_key} to be #{cached.klass}")
|
121
83
|
end
|
122
|
-
Rails.cache.write(cache_key, cached)
|
123
84
|
|
124
|
-
|
125
|
-
|
126
|
-
end
|
127
|
-
|
128
|
-
# Convert records to an array before calling paginate. If we don't do this
|
129
|
-
# and the result is a named scope, paginate will trigger an additional query
|
130
|
-
# to load the page rather than just using the records we have already fetched.
|
131
|
-
#
|
132
|
-
# If we are not paginating and the options include :limit (and optionally :offset)
|
133
|
-
# apply the limit and offset to the records before returning them.
|
134
|
-
#
|
135
|
-
# Otherwise we have an issue where all the records are returned the first time
|
136
|
-
# the collection is loaded, but on subsequent calls the options_for_find are
|
137
|
-
# included and you get different results. Note that with options like :order
|
138
|
-
# this cannot be helped. We don't want to modify the query that generates the
|
139
|
-
# collection because the idea is to allow getting different perspectives of the
|
140
|
-
# cached collection without relying on modifying the collection as a whole.
|
141
|
-
#
|
142
|
-
# If you do want a specialized, modified, or subset of the collection it's best
|
143
|
-
# to define it in a block and have a new cache for it:
|
144
|
-
#
|
145
|
-
# model.my_special_collection { the_collection(:order => 'new order') }
|
146
|
-
def return_records(records)
|
147
|
-
if opts.include?(:page)
|
148
|
-
records = records.respond_to?(:to_a) ? records.to_a : records
|
149
|
-
records.respond_to?(:paginate) ? records.paginate(opts_for_paginate) : records
|
150
|
-
elsif opts.include?(:limit)
|
151
|
-
records = records.respond_to?(:to_a) ? records.to_a : records
|
152
|
-
offset = opts[:offset] || 0
|
153
|
-
records[offset, opts[:limit]]
|
154
|
-
else
|
155
|
-
records
|
156
|
-
end
|
85
|
+
Rails.cache.write(blueprint.cache_key, cached)
|
86
|
+
opts.include?(:page) ? records.paginate(opts_for_paginate(opts, cached)) : records
|
157
87
|
end
|
158
88
|
|
159
|
-
def execute_count
|
160
|
-
|
161
|
-
|
89
|
+
def execute_count(blueprint)
|
90
|
+
records = blueprint.proc.call
|
91
|
+
|
92
|
+
# Update Rails cache and return the count
|
93
|
+
cached = Struct::Result.new(blueprint.opts)
|
162
94
|
|
163
95
|
# Just get the count if we can.
|
164
96
|
#
|
@@ -171,39 +103,42 @@ module AridCache
|
|
171
103
|
# and passes the respond_to? on to it.
|
172
104
|
if records.respond_to?(:proxy_reflection) || records.respond_to?(:proxy_options)
|
173
105
|
cached.count = records.count # just get the count
|
174
|
-
cached.klass =
|
175
|
-
elsif records.is_a?(Enumerable)
|
106
|
+
cached.klass = blueprint.klass
|
107
|
+
elsif records.is_a?(Enumerable)
|
176
108
|
cached.ids = records.collect(&:id) # get everything now that we have it
|
177
109
|
cached.count = records.size
|
178
|
-
cached.klass = records.empty? ?
|
110
|
+
cached.klass = records.empty? ? blueprint.klass : records.first.class
|
111
|
+
Rails.logger.info("** AridCache: inferring class of collection for cache #{blueprint.cache_key} to be #{cached.klass}")
|
179
112
|
else
|
180
113
|
cached = records # some base type, cache it as itself
|
181
114
|
end
|
182
115
|
|
183
|
-
Rails.cache.write(cache_key, cached)
|
184
|
-
|
185
|
-
|
116
|
+
Rails.cache.write(blueprint.cache_key, cached)
|
117
|
+
cached.count
|
118
|
+
end
|
119
|
+
|
120
|
+
def paginate(records, opts, proc=nil)
|
121
|
+
if !proc.nil?
|
122
|
+
ids = opts.include?(:page) ? records.paginate(opts) : records
|
123
|
+
records = proc.call(ids)
|
124
|
+
ids.is_a?(WillPaginate::Collection) ? ids.replace(records) : records
|
125
|
+
else
|
126
|
+
opts.include?(:page) ? records.paginate(opts) : records
|
127
|
+
end
|
186
128
|
end
|
187
129
|
|
188
|
-
def opts_for_paginate
|
189
|
-
|
190
|
-
|
191
|
-
|
130
|
+
def opts_for_paginate(opts, cached)
|
131
|
+
opts = cached.opts.merge(opts.symbolize_keys)
|
132
|
+
opts[:total_entries] = cached.count
|
133
|
+
opts
|
192
134
|
end
|
193
135
|
|
194
|
-
|
195
|
-
|
196
|
-
|
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) }
|
199
|
-
end
|
200
|
-
|
201
|
-
def object_base_class
|
202
|
-
object.is_a?(Class) ? object : object.class
|
136
|
+
def opts_for_find(opts, cached)
|
137
|
+
opts = cached.opts.merge(opts.symbolize_keys)
|
138
|
+
opts.values_at([:include, :joins, :conditions, :order, :group, :having]).compact
|
203
139
|
end
|
204
|
-
|
205
|
-
def
|
206
|
-
opts[:class] || (blueprint && blueprint.opts[:class]) || cached.klass || object_base_class
|
140
|
+
|
141
|
+
def initialize
|
207
142
|
end
|
208
143
|
end
|
209
144
|
end
|
data/lib/arid_cache/store.rb
CHANGED
@@ -1,84 +1,58 @@
|
|
1
1
|
module AridCache
|
2
2
|
class Store < Hash
|
3
|
-
|
3
|
+
Struct.new('Item', :cache_key, :proc, :klass, :opts)
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
# Stores options and blocks that are used to generate results for finds
|
8
|
-
# and counts.
|
9
|
-
Blueprint = Struct.new(:key, :klass, :proc, :opts) do
|
5
|
+
def query(key, opts, object, &block)
|
6
|
+
return store(object, key, Proc.new, opts) if block_given? # store a proc
|
10
7
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
def klass
|
23
|
-
self['klass'].constantize unless self['klass'].nil?
|
24
|
-
end
|
25
|
-
|
26
|
-
def opts=(value)
|
27
|
-
self['opts'] = value.symbolize_keys! unless !value.respond_to?(:symbolize_keys)
|
28
|
-
end
|
29
|
-
|
30
|
-
def opts
|
31
|
-
self['opts'] || {}
|
32
|
-
end
|
33
|
-
|
34
|
-
def proc(object=nil)
|
35
|
-
if self['proc'].nil? && !object.nil?
|
36
|
-
self['proc'] = key
|
8
|
+
if has?(object, key)
|
9
|
+
AridCache.cache.fetch(find(object, key), opts)
|
10
|
+
elsif key =~ /(.*)_count$/
|
11
|
+
if has?(object, $1)
|
12
|
+
AridCache.cache.fetch_count(find(object, $1))
|
13
|
+
elsif object.respond_to?(key)
|
14
|
+
AridCache.cache.fetch_count(find_or_create(object, key))
|
15
|
+
elsif object.respond_to?($1)
|
16
|
+
AridCache.cache.fetch_count(find_or_create(object, $1))
|
37
17
|
else
|
38
|
-
|
18
|
+
raise ArgumentError.new("#{object} doesn't respond to #{key} or #{$1}! Cannot dynamically create query to get the count.")
|
19
|
+
end
|
20
|
+
else
|
21
|
+
if object.respond_to?(key)
|
22
|
+
AridCache.cache.fetch(find_or_create(object, key), opts)
|
23
|
+
else
|
24
|
+
raise ArgumentError.new("#{object} doesn't respond to #{key}! Cannot dynamically create query.")
|
39
25
|
end
|
40
|
-
end
|
26
|
+
end
|
41
27
|
end
|
42
28
|
|
43
|
-
|
44
|
-
|
29
|
+
# Store a proc
|
30
|
+
def store(object, key, proc, opts)
|
31
|
+
cache_key = object.arid_cache_key(key)
|
32
|
+
self[cache_key] = Struct::Item.new(cache_key, proc, (object.is_a?(Class) ? object : object.class), opts.symbolize_keys!)
|
45
33
|
end
|
46
34
|
|
47
|
-
# Empty the proc store
|
48
|
-
def delete!
|
49
|
-
delete_if { true }
|
50
|
-
end
|
51
|
-
|
52
|
-
def self.instance
|
53
|
-
@@singleton_instance ||= self.new
|
54
|
-
end
|
55
|
-
|
56
35
|
def find(object, key)
|
57
|
-
self[
|
58
|
-
end
|
59
|
-
|
60
|
-
def add(object, key, proc, opts)
|
61
|
-
store_key = object_store_key(object, key)
|
62
|
-
self[store_key] = AridCache::Store::Blueprint.new(key, object, proc, opts)
|
36
|
+
self[object.arid_cache_key(key)]
|
63
37
|
end
|
64
38
|
|
39
|
+
# Find or dynamically create a proc
|
65
40
|
def find_or_create(object, key)
|
66
|
-
|
67
|
-
if
|
68
|
-
self[
|
41
|
+
cache_key = object.arid_cache_key(key)
|
42
|
+
if include?(cache_key)
|
43
|
+
self[cache_key]
|
69
44
|
else
|
70
|
-
self[
|
45
|
+
self[cache_key] = Struct::Item.new(cache_key, Proc.new { object.send(key) }, (object.is_a?(Class) ? object : object.class))
|
71
46
|
end
|
72
47
|
end
|
73
48
|
|
74
|
-
|
75
|
-
|
76
|
-
def initialize
|
49
|
+
def has?(object, key)
|
50
|
+
self.include?(object.arid_cache_key(key))
|
77
51
|
end
|
78
52
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
53
|
+
# Empty the proc store
|
54
|
+
def delete!
|
55
|
+
delete_if { true }
|
56
|
+
end
|
83
57
|
end
|
84
58
|
end
|
data/lib/arid_cache.rb
CHANGED
@@ -1,47 +1,15 @@
|
|
1
|
-
dir = File.dirname(__FILE__)
|
2
|
-
$LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
|
3
|
-
|
4
|
-
require 'arid_cache/helpers'
|
5
1
|
require 'arid_cache/store'
|
6
2
|
require 'arid_cache/active_record'
|
7
3
|
require 'arid_cache/cache_proxy'
|
8
4
|
|
9
5
|
module AridCache
|
10
|
-
extend AridCache::Helpers
|
11
6
|
class Error < StandardError; end
|
12
7
|
|
13
8
|
def self.cache
|
14
|
-
AridCache::CacheProxy
|
9
|
+
AridCache::CacheProxy.instance
|
15
10
|
end
|
16
11
|
|
17
|
-
def self.clear_all_caches
|
18
|
-
AridCache::CacheProxy.clear_all_caches
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.clear_class_caches(object)
|
22
|
-
AridCache::CacheProxy.clear_class_caches(object)
|
23
|
-
end
|
24
|
-
|
25
|
-
def self.clear_instance_caches(object)
|
26
|
-
AridCache::CacheProxy.clear_instance_caches(object)
|
27
|
-
end
|
28
|
-
|
29
|
-
def self.store
|
30
|
-
AridCache::Store.instance
|
31
|
-
end
|
32
|
-
|
33
|
-
# The old method of including this module, if you don't want to
|
34
|
-
# extend active record. Just add 'include AridCache' to your
|
35
|
-
# model class.
|
36
12
|
def self.included(base)
|
37
13
|
base.send(:include, AridCache::ActiveRecord)
|
38
14
|
end
|
39
|
-
|
40
|
-
# Initializes ARID Cache for Rails.
|
41
|
-
#
|
42
|
-
# This method is called by `init.rb`,
|
43
|
-
# which is run by Rails on startup.
|
44
|
-
def self.init_rails
|
45
|
-
::ActiveRecord::Base.send(:include, AridCache::ActiveRecord)
|
46
|
-
end
|
47
15
|
end
|
data/rails/init.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
require 'arid_cache'
|
data/rails/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
data/rails/uninstall.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Uninstall hook code here
|
data/test/arid_cache_test.rb
CHANGED
@@ -3,44 +3,37 @@ require File.join(File.dirname(__FILE__), 'test_helper')
|
|
3
3
|
class AridCacheTest < ActiveSupport::TestCase
|
4
4
|
def setup
|
5
5
|
Rails.cache.clear
|
6
|
-
AridCache.store.delete!
|
7
6
|
get_user
|
8
7
|
end
|
9
|
-
|
10
|
-
test "initializes needed objects" do
|
11
|
-
assert_instance_of AridCache::Store, AridCache.store
|
12
|
-
assert_same AridCache::CacheProxy, AridCache.cache
|
13
|
-
end
|
14
|
-
|
8
|
+
|
15
9
|
test "should respond to methods" do
|
16
|
-
|
17
|
-
|
18
|
-
assert_instance_of AridCache::Store,
|
10
|
+
assert_respond_to(User, :cache_store)
|
11
|
+
assert_respond_to(User.first, :cache_store)
|
12
|
+
assert_instance_of AridCache::Store, User.cache_store
|
13
|
+
assert_same User.cache_store, User.first.cache_store
|
19
14
|
end
|
20
15
|
|
21
|
-
test "should not clobber
|
22
|
-
assert_respond_to User.first, :name
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
# Shouldn't mess with your model's method_missing
|
16
|
+
test "should not clobber method_missing" do
|
17
|
+
assert_respond_to User.first, :name
|
18
|
+
end
|
19
|
+
|
20
|
+
test "should allow access to valid methods" do
|
28
21
|
assert_nothing_raised { User.first.is_high? }
|
29
|
-
assert User.first.is_high?
|
30
|
-
end
|
22
|
+
assert User.first.is_high?
|
23
|
+
end
|
31
24
|
|
32
25
|
test "should allow me to cache on the model" do
|
33
26
|
assert_nothing_raised do
|
34
27
|
define_model_cache(User)
|
35
28
|
end
|
36
|
-
|
29
|
+
assert_instance_of(Proc, User.cache_store[User.arid_cache_key('companies')].proc)
|
37
30
|
end
|
38
31
|
|
39
32
|
test "should allow me to cache on the instance" do
|
40
33
|
assert_nothing_raised do
|
41
34
|
define_instance_cache(@user)
|
42
35
|
end
|
43
|
-
|
36
|
+
assert_instance_of(Proc, @user.cache_store[@user.arid_cache_key('companies')].proc)
|
44
37
|
end
|
45
38
|
|
46
39
|
test "should raise an error on invalid dynamic caches" do
|
@@ -51,7 +44,7 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
51
44
|
|
52
45
|
test "should create dynamic caches given valid arguments" do
|
53
46
|
assert_nothing_raised { @user.cached_companies }
|
54
|
-
|
47
|
+
assert_instance_of(Proc, @user.cache_store[@user.arid_cache_key('companies')].proc)
|
55
48
|
end
|
56
49
|
|
57
50
|
test "counts queries correctly" do
|
@@ -65,9 +58,9 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
65
58
|
end
|
66
59
|
|
67
60
|
test "paginates results" do
|
68
|
-
results = @user.cached_companies(:page => 1
|
61
|
+
results = @user.cached_companies(:page => 1)
|
69
62
|
assert_kind_of WillPaginate::Collection, results
|
70
|
-
assert_equal
|
63
|
+
assert_equal 2, results.size
|
71
64
|
assert_equal @user.companies.count, results.total_entries
|
72
65
|
assert_equal 1, results.current_page
|
73
66
|
end
|
@@ -82,16 +75,15 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
82
75
|
test "works for different pages" do
|
83
76
|
results = @user.cached_companies(:page => 2, :per_page => 3)
|
84
77
|
assert_kind_of WillPaginate::Collection, results
|
85
|
-
|
86
|
-
assert_equal @user.companies.count, results.total_entries
|
87
|
-
assert_equal 2, results.current_page
|
78
|
+
assert_equal (@user.companies.count-3)%3, results.size
|
79
|
+
assert_equal @user.companies.count, results.total_entries
|
88
80
|
end
|
89
|
-
|
81
|
+
|
90
82
|
test "ignores random parameters" do
|
91
83
|
result = @user.cached_companies(:invalid => :params, 'random' => 'values', :user_id => 3)
|
92
84
|
assert_equal @user.companies, result
|
93
85
|
end
|
94
|
-
|
86
|
+
|
95
87
|
test "passes on options to find" do
|
96
88
|
actual = @user.cached_companies(:order => 'users.id DESC')
|
97
89
|
expected = @user.companies
|
@@ -107,101 +99,31 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
107
99
|
end
|
108
100
|
|
109
101
|
test "gets the count only if it's requested first" do
|
110
|
-
count = @user.companies.count
|
111
|
-
assert_queries(1) do
|
112
|
-
assert_equal count, @user.cached_companies_count
|
113
|
-
assert_equal count, @user.cached_companies_count
|
114
|
-
end
|
115
|
-
assert_queries(1) do
|
116
|
-
assert_equal count, @user.cached_companies.size
|
117
|
-
assert_equal count, @user.cached_companies_count
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
test "calling cache_ defines methods on the object" do
|
122
|
-
assert !User.method_defined?(:cached_favorite_companies)
|
123
|
-
User.cache_favorite_companies(:order => 'name DESC') do
|
124
|
-
User.companies
|
125
|
-
end
|
126
|
-
assert User.respond_to?(:cached_favorite_companies)
|
127
|
-
assert_nothing_raised do
|
128
|
-
User.method(:cached_favorite_companies)
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
test "applies limit and offset" do
|
133
|
-
@user.cached_limit_companies do
|
134
|
-
companies
|
135
|
-
end
|
136
|
-
assert_equal 2, @user.cached_limit_companies(:limit => 2).size
|
137
|
-
assert_equal 3, @user.cached_limit_companies(:limit => 3).size
|
138
|
-
assert_equal @user.companies.all(:limit => 2, :offset => 1), @user.cached_limit_companies(:limit => 2, :offset => 1)
|
139
|
-
assert_equal @user.companies.size, @user.cached_limit_companies.size
|
140
|
-
User.cached_successful_limit_companies do
|
141
|
-
User.successful
|
142
|
-
end
|
143
|
-
raise User.cached_successful_limit_companies.inspect
|
144
|
-
assert_equal 2, User.cached_successful_limit_companies(:limit => 2).size
|
145
|
-
assert_equal 3, User.cached_successful_limit_companies(:limit => 3).size
|
146
|
-
assert_equal User.successful.all(:limit => 2, :offset => 1), User.cached_successful_limit_companies(:limit => 2, :offset => 1)
|
147
|
-
assert_equal User.successful.size, User.cached_successful_limit_companies.size
|
148
|
-
end
|
149
|
-
|
150
|
-
test "pagination should not result in an extra query" do
|
151
|
-
assert_queries(1) do
|
152
|
-
@user.cached_big_companies(:page => 1)
|
153
|
-
end
|
154
102
|
assert_queries(1) do
|
155
|
-
|
103
|
+
assert_equal 5, @user.cached_companies_count
|
104
|
+
assert_equal 5, @user.cached_companies_count
|
156
105
|
end
|
157
|
-
end
|
158
|
-
|
159
|
-
test "should support a 'force' option" do
|
160
|
-
# ActiveRecord caches the result of the proc, so we need to
|
161
|
-
# use different instances of the user to test the force option.
|
162
|
-
uncached_user = User.first
|
163
|
-
companies = @user.companies
|
164
|
-
size = companies.size
|
165
106
|
assert_queries(1) do
|
166
|
-
assert_equal
|
167
|
-
assert_equal
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
assert_equal one.companies, one.cached_companies
|
181
|
-
assert_equal two.companies, two.cached_companies
|
182
|
-
end
|
183
|
-
|
184
|
-
test "should handle arrays of non-active record instances" do
|
185
|
-
assert_equal @user.pet_names, @user.cached_pet_names
|
186
|
-
assert_equal @user.pet_names, @user.cached_pet_names
|
187
|
-
assert_equal @user.pet_names.count, @user.cached_pet_names_count
|
188
|
-
end
|
189
|
-
|
190
|
-
test "should empty the Rails cache" do
|
191
|
-
define_model_cache(User)
|
192
|
-
@user.cached_companies
|
193
|
-
User.cached_companies
|
194
|
-
assert Rails.cache.exist?(@user.arid_cache_key('companies'))
|
195
|
-
assert Rails.cache.exist?(User.arid_cache_key('companies'))
|
196
|
-
User.clear_cache
|
197
|
-
assert Rails.cache.exist?(@user.arid_cache_key('companies'))
|
198
|
-
assert Rails.cache.exist?(User.arid_cache_key('companies'))
|
199
|
-
end
|
107
|
+
assert_equal 5, @user.cached_companies.size
|
108
|
+
assert_equal 5, @user.cached_companies_count
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
# test "should empty the Rails cache" do
|
113
|
+
# @user.cached_companies
|
114
|
+
# User.cached_companies
|
115
|
+
# assert Rails.cache.exist?(@user.arid_cache_key('companies'))
|
116
|
+
# assert Rails.cache.exist?(User.arid_cache_key('companies'))
|
117
|
+
# User.clear_cache
|
118
|
+
# assert Rails.cache.exist?(@user.arid_cache_key('companies'))
|
119
|
+
# assert Rails.cache.exist?(User.arid_cache_key('companies'))
|
120
|
+
# end
|
200
121
|
|
201
122
|
protected
|
202
123
|
|
203
124
|
def get_user
|
204
125
|
@user = User.first
|
126
|
+
@user.cache_store.delete!
|
205
127
|
@user.clear_cache
|
206
128
|
define_instance_cache(@user)
|
207
129
|
@user
|
@@ -219,13 +141,13 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
219
141
|
end
|
220
142
|
|
221
143
|
def define_instance_cache(user)
|
222
|
-
user.
|
144
|
+
user.cached_companies(:per_page => 2) do
|
223
145
|
user.companies
|
224
146
|
end
|
225
147
|
end
|
226
148
|
|
227
149
|
def define_model_cache(model)
|
228
|
-
model.
|
150
|
+
model.cached_companies(:per_page => 2) do
|
229
151
|
model.companies
|
230
152
|
end
|
231
153
|
end
|
data/test/db/schema.rb
CHANGED
data/test/fixtures/companies.yml
CHANGED
data/test/fixtures/users.yml
CHANGED
data/test/models/user.rb
CHANGED
@@ -1,17 +1,9 @@
|
|
1
1
|
require 'arid_cache'
|
2
2
|
|
3
3
|
class User < ActiveRecord::Base
|
4
|
+
include AridCache
|
4
5
|
has_many :companies, :foreign_key => :owner_id
|
5
6
|
named_scope :companies, :joins => :companies
|
6
|
-
named_scope :successful, :joins => :companies, :conditions => 'companies.employees > 50'
|
7
|
-
|
8
|
-
def big_companies
|
9
|
-
companies.find :all, :conditions => [ 'employees > 20' ]
|
10
|
-
end
|
11
|
-
|
12
|
-
def pet_names
|
13
|
-
['Fuzzy', 'Peachy']
|
14
|
-
end
|
15
7
|
|
16
8
|
def method_missing(method, *args)
|
17
9
|
if method == :is_high?
|
data/test/test_helper.rb
CHANGED
@@ -7,11 +7,7 @@ require 'active_support'
|
|
7
7
|
require 'active_support/test_case'
|
8
8
|
require 'test/unit' # required by ActiveSupport::TestCase
|
9
9
|
require 'will_paginate'
|
10
|
-
require 'ruby-debug'
|
11
|
-
|
12
|
-
# Activate ARID Cache
|
13
10
|
require 'arid_cache'
|
14
|
-
AridCache.init_rails
|
15
11
|
|
16
12
|
# Setup logging
|
17
13
|
log = File.expand_path(File.join(File.dirname(__FILE__), 'log', 'test.log'))
|
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.0
|
4
|
+
version: 0.1.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: 2009-12-
|
12
|
+
date: 2009-12-21 00:00:00 +08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -42,10 +42,7 @@ dependencies:
|
|
42
42
|
- !ruby/object:Gem::Version
|
43
43
|
version: "0"
|
44
44
|
version:
|
45
|
-
description:
|
46
|
-
ARID Cache makes caching easy and effective. ARID cache supports caching on all your model named scopes, class methods and instance methods right out of the box. ARID cache prevents caching logic from cluttering your models and clarifies your logic by making explicit calls to cached result sets.
|
47
|
-
ARID Cache is designed for handling large, expensive ActiveRecord collections but is equally useful for caching anything else as well.
|
48
|
-
|
45
|
+
description: ""
|
49
46
|
email: kjvarga@gmail.com
|
50
47
|
executables: []
|
51
48
|
|
@@ -61,13 +58,13 @@ files:
|
|
61
58
|
- Rakefile
|
62
59
|
- VERSION
|
63
60
|
- arid_cache.gemspec
|
64
|
-
- init.rb
|
65
61
|
- lib/arid_cache.rb
|
66
62
|
- lib/arid_cache/active_record.rb
|
67
63
|
- lib/arid_cache/cache_proxy.rb
|
68
|
-
- lib/arid_cache/helpers.rb
|
69
64
|
- lib/arid_cache/store.rb
|
70
65
|
- rails/init.rb
|
66
|
+
- rails/install.rb
|
67
|
+
- rails/uninstall.rb
|
71
68
|
- spec/arid_cache_spec.rb
|
72
69
|
- spec/spec.opts
|
73
70
|
- spec/spec_helper.rb
|
data/init.rb
DELETED
data/lib/arid_cache/helpers.rb
DELETED
@@ -1,86 +0,0 @@
|
|
1
|
-
module AridCache
|
2
|
-
module Helpers
|
3
|
-
|
4
|
-
# Lookup something from the cache.
|
5
|
-
#
|
6
|
-
# If no block is provided, create one dynamically. If a block is
|
7
|
-
# provided, it is only used the first time it is encountered.
|
8
|
-
# This allows you to dynamically define your caches while still
|
9
|
-
# returning the results of your query.
|
10
|
-
#
|
11
|
-
# @return a WillPaginate::Collection if the options include :page,
|
12
|
-
# a Fixnum count if the request is for a count or the results of
|
13
|
-
# the ActiveRecord query otherwise.
|
14
|
-
def lookup(object, key, opts, &block)
|
15
|
-
if !block.nil?
|
16
|
-
define(object, key, opts, &block)
|
17
|
-
elsif key =~ /(.*)_count$/
|
18
|
-
if AridCache.store.has?(object, $1)
|
19
|
-
method_for_cached(object, $1, :fetch_count, key)
|
20
|
-
elsif object.respond_to?(key)
|
21
|
-
define(object, key, opts, :fetch_count)
|
22
|
-
elsif object.respond_to?($1)
|
23
|
-
define(object, $1, opts, :fetch_count, key)
|
24
|
-
else
|
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
|
27
|
-
elsif object.respond_to?(key)
|
28
|
-
define(object, key, opts, &block)
|
29
|
-
else
|
30
|
-
raise ArgumentError.new("#{object} doesn't respond to #{key}! Cannot dynamically create query, please call with a block.")
|
31
|
-
end
|
32
|
-
object.send("cached_#{key}", opts)
|
33
|
-
end
|
34
|
-
|
35
|
-
# Store the options and optional block for a call to the cache.
|
36
|
-
#
|
37
|
-
# If no block is provided, create one dynamically.
|
38
|
-
#
|
39
|
-
# @return an AridCache::Store::Blueprint.
|
40
|
-
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
|
-
|
45
|
-
# FIXME: Pass default options to store.add
|
46
|
-
# Pass nil in for now until we get the cache_ calls working.
|
47
|
-
# This means that the first time you define a dynamic cache
|
48
|
-
# (by passing in a block), the options you used are not
|
49
|
-
# stored in the blueprint and applied to each subsequent call.
|
50
|
-
#
|
51
|
-
# Otherwise we have a situation where a :limit passed in to the
|
52
|
-
# first call persists when no options are passed in on subsequent calls,
|
53
|
-
# but if a different :limit is passed in that limit is applied.
|
54
|
-
#
|
55
|
-
# I think in this scenario one would expect no limit to be applied
|
56
|
-
# if no options are passed in.
|
57
|
-
#
|
58
|
-
# When the cache_ methods are supported, those options should be
|
59
|
-
# remembered and applied to the collection however.
|
60
|
-
blueprint = AridCache.store.add(object, key, block, nil)
|
61
|
-
method_for_cached(object, key, fetch_method, method_name)
|
62
|
-
blueprint
|
63
|
-
end
|
64
|
-
|
65
|
-
private
|
66
|
-
|
67
|
-
def method_for_cached(object, key, fetch_method=:fetch, method_name=nil)
|
68
|
-
method_name = "cached_" + (method_name || key)
|
69
|
-
if object.is_a?(Class)
|
70
|
-
(class << object; self; end).instance_eval do
|
71
|
-
define_method(method_name) do |*args, &block|
|
72
|
-
opts = args.empty? ? {} : args.first
|
73
|
-
AridCache.cache.send(fetch_method, self, key, opts, &block)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
else
|
77
|
-
object.class_eval do
|
78
|
-
define_method(method_name) do |*args, &block|
|
79
|
-
opts = args.empty? ? {} : args.first
|
80
|
-
AridCache.cache.send(fetch_method, self, key, opts, &block)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|
86
|
-
end
|