arid_cache 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +69 -19
- data/Rakefile +5 -1
- data/VERSION +1 -1
- data/arid_cache.gemspec +7 -5
- data/init.rb +6 -0
- data/lib/arid_cache/active_record.rb +52 -29
- data/lib/arid_cache/cache_proxy.rb +150 -85
- data/lib/arid_cache/helpers.rb +86 -0
- data/lib/arid_cache/store.rb +62 -36
- data/lib/arid_cache.rb +33 -1
- data/rails/init.rb +1 -1
- data/test/arid_cache_test.rb +118 -40
- data/test/db/schema.rb +1 -0
- data/test/fixtures/companies.yml +3 -2
- data/test/fixtures/users.yml +5 -3
- data/test/models/user.rb +9 -1
- data/test/test_helper.rb +4 -0
- metadata +8 -5
- data/rails/install.rb +0 -1
- data/rails/uninstall.rb +0 -1
data/README.rdoc
CHANGED
@@ -1,10 +1,50 @@
|
|
1
1
|
== ARID Cache
|
2
2
|
|
3
|
-
ARID Cache
|
3
|
+
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.
|
4
4
|
|
5
|
-
|
5
|
+
ARID Cache is designed for handling large, expensive ActiveRecord collections but is equally useful for caching anything else as well.
|
6
6
|
|
7
|
-
|
7
|
+
=== Counts for free
|
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:
|
8
48
|
|
9
49
|
class User < ActiveRecord::Base
|
10
50
|
include AridCache
|
@@ -13,12 +53,11 @@ Given the following model:
|
|
13
53
|
named_scope :active, :conditions => [ 'updated_at <= ', 5.minutes.ago ]
|
14
54
|
end
|
15
55
|
|
16
|
-
ARID Cache provides these methods:
|
56
|
+
==== ARID Cache provides these methods:
|
17
57
|
|
18
58
|
User.cached_active # caches the user IDs and the count
|
19
59
|
User.cached_active_count # gets the count for free
|
20
60
|
|
21
|
-
user = User.first
|
22
61
|
user.cached_pets # caches the pets IDs and the count
|
23
62
|
user.cached_pets_count # gets the count for free
|
24
63
|
|
@@ -28,9 +67,9 @@ selects where the IDs are the cached IDs.
|
|
28
67
|
|
29
68
|
It also gives you paging using WillPaginate. The IDs from the cache are paginated and
|
30
69
|
only that page is selected from the database - again directly from the table, without
|
31
|
-
any complex joins
|
70
|
+
any complex joins.
|
32
71
|
|
33
|
-
Some examples of pagination:
|
72
|
+
==== Some examples of pagination:
|
34
73
|
|
35
74
|
User.cached_active.paginate(:page => 1, :per_page => 30)
|
36
75
|
User.cached_active.paginate(:page => 1)
|
@@ -40,21 +79,32 @@ You can also include options for find, such as <tt>:join</tt>, <tt>:include</tt>
|
|
40
79
|
|
41
80
|
User.cached_active.paginate(:page => 1, :include => :preferences)
|
42
81
|
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)
|
43
87
|
|
44
|
-
You can
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
has_many :pets
|
49
|
-
has_one :preferences
|
50
|
-
named_scope :active, :conditions => [ 'updated_at <= ', 5.minutes.ago ]
|
51
|
-
|
52
|
-
self.cached_active_users(:order => 'created_at DESC', :include => :preferences) do
|
53
|
-
self.active
|
54
|
-
end
|
88
|
+
==== You can dynamically create caches
|
89
|
+
|
90
|
+
User.cached_most_active_users do
|
91
|
+
self.active.find(:order => 'activity DESC', :limit => 10)
|
55
92
|
end
|
56
|
-
|
57
93
|
|
94
|
+
Dynamic caches that make use of other cached collections:
|
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
|
105
|
+
|
106
|
+
More docs to come...
|
107
|
+
|
58
108
|
== Note on Patches/Pull Requests
|
59
109
|
|
60
110
|
* Fork the project.
|
data/Rakefile
CHANGED
@@ -6,7 +6,11 @@ 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 =
|
9
|
+
gem.description = <<-END.gsub(/^\s+/, '')
|
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
|
10
14
|
gem.email = "kjvarga@gmail.com"
|
11
15
|
gem.homepage = "http://github.com/kjvarga/arid_cache"
|
12
16
|
gem.authors = ["Karl Varga"]
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.1
|
data/arid_cache.gemspec
CHANGED
@@ -5,12 +5,14 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{arid_cache}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.1"
|
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{
|
12
|
+
s.date = %q{2009-12-25}
|
13
|
+
s.description = %q{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.
|
14
|
+
ARID Cache is designed for handling large, expensive ActiveRecord collections but is equally useful for caching anything else as well.
|
15
|
+
}
|
14
16
|
s.email = %q{kjvarga@gmail.com}
|
15
17
|
s.extra_rdoc_files = [
|
16
18
|
"LICENSE",
|
@@ -23,13 +25,13 @@ Gem::Specification.new do |s|
|
|
23
25
|
"Rakefile",
|
24
26
|
"VERSION",
|
25
27
|
"arid_cache.gemspec",
|
28
|
+
"init.rb",
|
26
29
|
"lib/arid_cache.rb",
|
27
30
|
"lib/arid_cache/active_record.rb",
|
28
31
|
"lib/arid_cache/cache_proxy.rb",
|
32
|
+
"lib/arid_cache/helpers.rb",
|
29
33
|
"lib/arid_cache/store.rb",
|
30
34
|
"rails/init.rb",
|
31
|
-
"rails/install.rb",
|
32
|
-
"rails/uninstall.rb",
|
33
35
|
"spec/arid_cache_spec.rb",
|
34
36
|
"spec/spec.opts",
|
35
37
|
"spec/spec_helper.rb",
|
data/init.rb
ADDED
@@ -1,44 +1,67 @@
|
|
1
1
|
module AridCache
|
2
|
-
module ActiveRecord
|
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
|
+
|
3
14
|
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
|
4
31
|
|
5
32
|
# Return a cache key for the given key e.g.
|
6
33
|
# User.arid_cache_key('companies') => 'user-companies'
|
7
|
-
# User.first.arid_cache_key('companies') => 'users/
|
8
|
-
def arid_cache_key(key
|
9
|
-
|
10
|
-
|
11
|
-
('arid-cache-' + arid_cache_key).to_sym
|
34
|
+
# User.first.arid_cache_key('companies') => 'users/20090120091123-companies'
|
35
|
+
def arid_cache_key(key)
|
36
|
+
key = (self.is_a?(Class) ? self.name.downcase : self.cache_key) + '-' + key.to_s
|
37
|
+
'arid-cache-' + key
|
12
38
|
end
|
13
39
|
|
14
|
-
def
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
40
|
+
def respond_to?(method, include_private = false) #:nodoc:
|
41
|
+
if (method.to_s =~ /^class_cache_.*|cache_.*|cached_.*(_count)?$/).nil?
|
42
|
+
super(method, include_private)
|
43
|
+
elsif method.to_s =~ /^cached_(.*)_count$/
|
44
|
+
AridCache.store.has?(self, "#{$1}_count") || AridCache.store.has?(self, $1) || super("#{$1}_count", include_private) || super($1, include_private)
|
45
|
+
elsif method.to_s =~ /^cached_(.*)$/
|
46
|
+
AridCache.store.has?(self, $1) || super($1, include_private)
|
47
|
+
else
|
48
|
+
super(method, include_private)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
22
53
|
|
23
54
|
# Intercept methods beginning with <tt>cached_</tt>
|
24
|
-
def method_missing_with_arid_cache(method, *args, &block)
|
25
|
-
|
26
|
-
|
27
|
-
|
55
|
+
def method_missing_with_arid_cache(method, *args, &block) #:nodoc:
|
56
|
+
opts = args.empty? ? {} : args.first
|
57
|
+
if method.to_s =~ /^cache_(.*)$/
|
58
|
+
AridCache.define(self, $1, opts, &block)
|
59
|
+
elsif method.to_s =~ /^cached_(.*)$/
|
60
|
+
AridCache.lookup(self, $1, opts, &block)
|
28
61
|
else
|
29
62
|
method_missing_without_arid_cache(method, *args)
|
30
63
|
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
|
41
64
|
end
|
42
65
|
end
|
43
66
|
end
|
44
|
-
end
|
67
|
+
end
|
@@ -1,96 +1,164 @@
|
|
1
|
-
# AridCache::Cache is a singleton instance
|
2
1
|
module AridCache
|
3
2
|
class CacheProxy
|
4
|
-
|
3
|
+
attr_accessor :object, :key, :opts, :blueprint, :cached, :cache_key, :block, :records
|
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
|
5
10
|
|
6
11
|
def has_count?
|
7
12
|
!count.nil?
|
8
13
|
end
|
9
|
-
|
14
|
+
|
10
15
|
def has_ids?
|
11
16
|
!ids.nil?
|
12
17
|
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
|
13
26
|
end
|
27
|
+
|
28
|
+
def self.clear_all_caches
|
29
|
+
Rails.cache.delete_matched(%r[arid-cache-.*])
|
30
|
+
end
|
14
31
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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}.*])
|
25
|
-
end
|
26
|
-
|
27
|
-
def self.instance
|
28
|
-
@@singleton_instance ||= self.new
|
29
|
-
end
|
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
|
30
36
|
|
31
|
-
def
|
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
|
+
|
42
|
+
def self.has?(object, key)
|
32
43
|
Rails.cache.exist?(object.arid_cache_key(key))
|
33
44
|
end
|
34
|
-
|
35
|
-
def fetch_count(
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
45
|
+
|
46
|
+
def self.fetch_count(object, key, opts, &block)
|
47
|
+
CacheProxy.new(object, key, opts, &block).fetch_count
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.fetch(object, key, opts, &block)
|
51
|
+
CacheProxy.new(object, key, opts, &block).fetch
|
52
|
+
end
|
53
|
+
|
54
|
+
def initialize(object, key, opts, &block)
|
55
|
+
self.object = object
|
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
|
63
|
+
end
|
64
|
+
|
65
|
+
def fetch_count
|
66
|
+
if cached.nil? || opts[:force]
|
67
|
+
execute_count
|
68
|
+
elsif cached.is_a?(AridCache::CacheProxy::Result)
|
69
|
+
cached.has_count? ? cached.count : execute_count
|
70
|
+
elsif cached.is_a?(Fixnum)
|
71
|
+
cached
|
72
|
+
elsif cached.respond_to?(:count)
|
73
|
+
cached.count
|
41
74
|
else
|
42
|
-
cached #
|
75
|
+
cached # what else can we do? return it
|
43
76
|
end
|
44
77
|
end
|
45
|
-
|
46
|
-
def fetch
|
47
|
-
cached
|
48
|
-
|
49
|
-
|
50
|
-
elsif cached.is_a?(Struct::Result)
|
78
|
+
|
79
|
+
def fetch
|
80
|
+
if cached.nil? || opts[:force]
|
81
|
+
execute_find
|
82
|
+
elsif cached.is_a?(AridCache::CacheProxy::Result)
|
51
83
|
if cached.has_ids? # paginate and fetch here
|
84
|
+
klass = find_class_of_results
|
52
85
|
if opts.include?(:page)
|
53
|
-
|
86
|
+
klass.paginate(cached.ids, opts_for_paginate)
|
54
87
|
else
|
55
|
-
|
88
|
+
klass.find(cached.ids, opts_for_find)
|
56
89
|
end
|
57
90
|
else
|
58
|
-
execute_find
|
91
|
+
execute_find
|
59
92
|
end
|
60
93
|
else
|
61
94
|
cached # some base type, return it
|
62
95
|
end
|
63
96
|
end
|
64
|
-
|
97
|
+
|
65
98
|
private
|
66
99
|
|
67
|
-
def
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
cached.
|
80
|
-
|
81
|
-
|
82
|
-
|
100
|
+
def get_records
|
101
|
+
block = block || blueprint.proc
|
102
|
+
self.records = block.nil? ? object.instance_eval(key) : object.instance_eval(&block)
|
103
|
+
end
|
104
|
+
|
105
|
+
def execute_find
|
106
|
+
get_records
|
107
|
+
cached = AridCache::CacheProxy::Result.new
|
108
|
+
|
109
|
+
if !records.is_a?(Enumerable) || (!records.empty? && !records.first.is_a?(::ActiveRecord::Base))
|
110
|
+
cached = records # some base type, cache it as itself
|
111
|
+
else
|
112
|
+
cached.ids = records.collect(&:id)
|
113
|
+
cached.count = records.size
|
114
|
+
if records.respond_to?(:proxy_reflection) # association proxy
|
115
|
+
cached.klass = records.proxy_reflection.klass
|
116
|
+
elsif !records.empty?
|
117
|
+
cached.klass = records.first.class
|
118
|
+
else
|
119
|
+
cached.klass = object_base_class
|
120
|
+
end
|
83
121
|
end
|
122
|
+
Rails.cache.write(cache_key, cached)
|
84
123
|
|
85
|
-
|
86
|
-
|
124
|
+
self.cached = cached
|
125
|
+
return_records(records)
|
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
|
87
157
|
end
|
88
158
|
|
89
|
-
def execute_count
|
90
|
-
|
91
|
-
|
92
|
-
# Update Rails cache and return the count
|
93
|
-
cached = Struct::Result.new(blueprint.opts)
|
159
|
+
def execute_count
|
160
|
+
get_records
|
161
|
+
cached = AridCache::CacheProxy::Result.new
|
94
162
|
|
95
163
|
# Just get the count if we can.
|
96
164
|
#
|
@@ -103,42 +171,39 @@ module AridCache
|
|
103
171
|
# and passes the respond_to? on to it.
|
104
172
|
if records.respond_to?(:proxy_reflection) || records.respond_to?(:proxy_options)
|
105
173
|
cached.count = records.count # just get the count
|
106
|
-
cached.klass =
|
107
|
-
elsif records.is_a?(Enumerable)
|
174
|
+
cached.klass = object_base_class
|
175
|
+
elsif records.is_a?(Enumerable) && (records.empty? || records.first.is_a?(::ActiveRecord::Base))
|
108
176
|
cached.ids = records.collect(&:id) # get everything now that we have it
|
109
177
|
cached.count = records.size
|
110
|
-
cached.klass = records.empty? ?
|
111
|
-
Rails.logger.info("** AridCache: inferring class of collection for cache #{blueprint.cache_key} to be #{cached.klass}")
|
178
|
+
cached.klass = records.empty? ? object_base_class : records.first.class
|
112
179
|
else
|
113
180
|
cached = records # some base type, cache it as itself
|
114
181
|
end
|
115
182
|
|
116
|
-
Rails.cache.write(
|
117
|
-
cached
|
118
|
-
|
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
|
183
|
+
Rails.cache.write(cache_key, cached)
|
184
|
+
self.cached = cached
|
185
|
+
cached.respond_to?(:count) ? cached.count : cached
|
128
186
|
end
|
129
187
|
|
130
|
-
def opts_for_paginate
|
131
|
-
|
132
|
-
|
133
|
-
|
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
|
191
|
+
paginate_opts
|
134
192
|
end
|
135
193
|
|
136
|
-
|
137
|
-
|
138
|
-
|
194
|
+
VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset, :order, :select, :readonly, :group, :having, :from, :lock ]
|
195
|
+
|
196
|
+
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) }
|
139
199
|
end
|
140
|
-
|
141
|
-
def
|
200
|
+
|
201
|
+
def object_base_class
|
202
|
+
object.is_a?(Class) ? object : object.class
|
203
|
+
end
|
204
|
+
|
205
|
+
def find_class_of_results
|
206
|
+
opts[:class] || (blueprint && blueprint.opts[:class]) || cached.klass || object_base_class
|
142
207
|
end
|
143
208
|
end
|
144
209
|
end
|
@@ -0,0 +1,86 @@
|
|
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
|
data/lib/arid_cache/store.rb
CHANGED
@@ -1,58 +1,84 @@
|
|
1
1
|
module AridCache
|
2
2
|
class Store < Hash
|
3
|
-
|
3
|
+
extend ActiveSupport::Memoizable
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
# AridCache::Store::Blueprint
|
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
|
7
10
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
11
|
+
def initialize(key, klass, proc=nil, opts={})
|
12
|
+
self.key = key
|
13
|
+
self.klass = klass
|
14
|
+
self.proc = proc
|
15
|
+
self.opts = opts
|
16
|
+
end
|
17
|
+
|
18
|
+
def klass=(value) # store the base class of *value*
|
19
|
+
self['klass'] = value.is_a?(Class) ? value.name : value.class.name
|
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
|
23
37
|
else
|
24
|
-
|
38
|
+
self['proc']
|
25
39
|
end
|
26
|
-
end
|
40
|
+
end
|
27
41
|
end
|
28
42
|
|
29
|
-
|
30
|
-
|
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!)
|
43
|
+
def has?(object, key)
|
44
|
+
self.include?(object_store_key(object, key))
|
33
45
|
end
|
34
46
|
|
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
|
+
|
35
56
|
def find(object, key)
|
36
|
-
self[object
|
57
|
+
self[object_store_key(object, key)]
|
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)
|
37
63
|
end
|
38
64
|
|
39
|
-
# Find or dynamically create a proc
|
40
65
|
def find_or_create(object, key)
|
41
|
-
|
42
|
-
if include?(
|
43
|
-
self[
|
66
|
+
store_key = object_store_key(object, key)
|
67
|
+
if self.include?(store_key)
|
68
|
+
self[store_key]
|
44
69
|
else
|
45
|
-
self[
|
70
|
+
self[store_key] = AridCache::Store::Blueprint.new(key, object)
|
46
71
|
end
|
47
72
|
end
|
48
73
|
|
49
|
-
|
50
|
-
|
74
|
+
protected
|
75
|
+
|
76
|
+
def initialize
|
51
77
|
end
|
52
78
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
79
|
+
def object_store_key(object, key)
|
80
|
+
(object.is_a?(Class) ? object.name.downcase : object.class.name.pluralize.downcase) + '-' + key.to_s
|
81
|
+
end
|
82
|
+
memoize :object_store_key
|
57
83
|
end
|
58
84
|
end
|
data/lib/arid_cache.rb
CHANGED
@@ -1,15 +1,47 @@
|
|
1
|
+
dir = File.dirname(__FILE__)
|
2
|
+
$LOAD_PATH.unshift dir unless $LOAD_PATH.include?(dir)
|
3
|
+
|
4
|
+
require 'arid_cache/helpers'
|
1
5
|
require 'arid_cache/store'
|
2
6
|
require 'arid_cache/active_record'
|
3
7
|
require 'arid_cache/cache_proxy'
|
4
8
|
|
5
9
|
module AridCache
|
10
|
+
extend AridCache::Helpers
|
6
11
|
class Error < StandardError; end
|
7
12
|
|
8
13
|
def self.cache
|
9
|
-
AridCache::CacheProxy
|
14
|
+
AridCache::CacheProxy
|
10
15
|
end
|
11
16
|
|
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.
|
12
36
|
def self.included(base)
|
13
37
|
base.send(:include, AridCache::ActiveRecord)
|
14
38
|
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
|
15
47
|
end
|
data/rails/init.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
Kernel.load File.join(File.dirname(__FILE__), '..', 'init.rb')
|
data/test/arid_cache_test.rb
CHANGED
@@ -3,37 +3,44 @@ 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!
|
6
7
|
get_user
|
7
8
|
end
|
8
|
-
|
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
|
+
|
9
15
|
test "should respond to methods" do
|
10
|
-
|
11
|
-
|
12
|
-
assert_instance_of AridCache::Store,
|
13
|
-
assert_same User.cache_store, User.first.cache_store
|
16
|
+
assert User.respond_to?(:clear_cache)
|
17
|
+
assert User.first.respond_to?(:clear_cache)
|
18
|
+
assert_instance_of AridCache::Store, AridCache.store
|
14
19
|
end
|
15
20
|
|
16
|
-
test "should not clobber
|
17
|
-
assert_respond_to User.first, :name
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
+
test "should not clobber model methods" do
|
22
|
+
assert_respond_to User.first, :name
|
23
|
+
assert_respond_to Company.first, :name
|
24
|
+
assert_nothing_raised { User.first.name }
|
25
|
+
assert_nothing_raised { Company.first.name }
|
26
|
+
|
27
|
+
# Shouldn't mess with your model's method_missing
|
21
28
|
assert_nothing_raised { User.first.is_high? }
|
22
|
-
assert User.first.is_high?
|
23
|
-
end
|
29
|
+
assert User.first.is_high?
|
30
|
+
end
|
24
31
|
|
25
32
|
test "should allow me to cache on the model" do
|
26
33
|
assert_nothing_raised do
|
27
34
|
define_model_cache(User)
|
28
35
|
end
|
29
|
-
assert_instance_of(Proc,
|
36
|
+
#assert_instance_of(Proc, AridCache.store[User.arid_cache_key('companies')].proc)
|
30
37
|
end
|
31
38
|
|
32
39
|
test "should allow me to cache on the instance" do
|
33
40
|
assert_nothing_raised do
|
34
41
|
define_instance_cache(@user)
|
35
42
|
end
|
36
|
-
assert_instance_of(Proc,
|
43
|
+
#assert_instance_of(Proc, AridCache.store[AridCache.store.store_key@user.arid_cache_key('companies')].proc)
|
37
44
|
end
|
38
45
|
|
39
46
|
test "should raise an error on invalid dynamic caches" do
|
@@ -44,7 +51,7 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
44
51
|
|
45
52
|
test "should create dynamic caches given valid arguments" do
|
46
53
|
assert_nothing_raised { @user.cached_companies }
|
47
|
-
assert_instance_of(Proc,
|
54
|
+
#assert_instance_of(Proc, AridCache.store[@user.arid_cache_key('companies')].proc)
|
48
55
|
end
|
49
56
|
|
50
57
|
test "counts queries correctly" do
|
@@ -58,9 +65,9 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
58
65
|
end
|
59
66
|
|
60
67
|
test "paginates results" do
|
61
|
-
results = @user.cached_companies(:page => 1)
|
68
|
+
results = @user.cached_companies(:page => 1, :per_page => 3)
|
62
69
|
assert_kind_of WillPaginate::Collection, results
|
63
|
-
assert_equal
|
70
|
+
assert_equal 3, results.size
|
64
71
|
assert_equal @user.companies.count, results.total_entries
|
65
72
|
assert_equal 1, results.current_page
|
66
73
|
end
|
@@ -75,15 +82,16 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
75
82
|
test "works for different pages" do
|
76
83
|
results = @user.cached_companies(:page => 2, :per_page => 3)
|
77
84
|
assert_kind_of WillPaginate::Collection, results
|
78
|
-
|
79
|
-
assert_equal @user.companies.count, results.total_entries
|
85
|
+
assert results.size <= 3
|
86
|
+
assert_equal @user.companies.count, results.total_entries
|
87
|
+
assert_equal 2, results.current_page
|
80
88
|
end
|
81
|
-
|
89
|
+
|
82
90
|
test "ignores random parameters" do
|
83
91
|
result = @user.cached_companies(:invalid => :params, 'random' => 'values', :user_id => 3)
|
84
92
|
assert_equal @user.companies, result
|
85
93
|
end
|
86
|
-
|
94
|
+
|
87
95
|
test "passes on options to find" do
|
88
96
|
actual = @user.cached_companies(:order => 'users.id DESC')
|
89
97
|
expected = @user.companies
|
@@ -99,31 +107,101 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
99
107
|
end
|
100
108
|
|
101
109
|
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
|
102
154
|
assert_queries(1) do
|
103
|
-
|
104
|
-
assert_equal 5, @user.cached_companies_count
|
155
|
+
User.cached_companies(:page => 1)
|
105
156
|
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
|
106
165
|
assert_queries(1) do
|
107
|
-
assert_equal
|
108
|
-
assert_equal
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
166
|
+
assert_equal companies, @user.cached_companies
|
167
|
+
assert_equal size, @user.cached_companies_count
|
168
|
+
assert_equal size, uncached_user.cached_companies_count
|
169
|
+
end
|
170
|
+
assert_queries(2) do
|
171
|
+
assert_equal companies, uncached_user.cached_companies(:force => true)
|
172
|
+
assert_equal size, uncached_user.cached_companies_count(:force => true)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
test "should handle various different model instances" do
|
177
|
+
one = User.first
|
178
|
+
two = User.first :offset => 1
|
179
|
+
assert_not_same one, two
|
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
|
121
200
|
|
122
201
|
protected
|
123
202
|
|
124
203
|
def get_user
|
125
204
|
@user = User.first
|
126
|
-
@user.cache_store.delete!
|
127
205
|
@user.clear_cache
|
128
206
|
define_instance_cache(@user)
|
129
207
|
@user
|
@@ -141,13 +219,13 @@ class AridCacheTest < ActiveSupport::TestCase
|
|
141
219
|
end
|
142
220
|
|
143
221
|
def define_instance_cache(user)
|
144
|
-
user.
|
222
|
+
user.cache_companies(:per_page => 2) do
|
145
223
|
user.companies
|
146
224
|
end
|
147
225
|
end
|
148
226
|
|
149
227
|
def define_model_cache(model)
|
150
|
-
model.
|
228
|
+
model.cache_companies(:per_page => 2) do
|
151
229
|
model.companies
|
152
230
|
end
|
153
231
|
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,9 +1,17 @@
|
|
1
1
|
require 'arid_cache'
|
2
2
|
|
3
3
|
class User < ActiveRecord::Base
|
4
|
-
include AridCache
|
5
4
|
has_many :companies, :foreign_key => :owner_id
|
6
5
|
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
|
7
15
|
|
8
16
|
def method_missing(method, *args)
|
9
17
|
if method == :is_high?
|
data/test/test_helper.rb
CHANGED
@@ -7,7 +7,11 @@ 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
|
10
13
|
require 'arid_cache'
|
14
|
+
AridCache.init_rails
|
11
15
|
|
12
16
|
# Setup logging
|
13
17
|
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.1.
|
4
|
+
version: 0.1.1
|
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-25 00:00:00 +08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -42,7 +42,10 @@ dependencies:
|
|
42
42
|
- !ruby/object:Gem::Version
|
43
43
|
version: "0"
|
44
44
|
version:
|
45
|
-
description:
|
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
|
+
|
46
49
|
email: kjvarga@gmail.com
|
47
50
|
executables: []
|
48
51
|
|
@@ -58,13 +61,13 @@ files:
|
|
58
61
|
- Rakefile
|
59
62
|
- VERSION
|
60
63
|
- arid_cache.gemspec
|
64
|
+
- init.rb
|
61
65
|
- lib/arid_cache.rb
|
62
66
|
- lib/arid_cache/active_record.rb
|
63
67
|
- lib/arid_cache/cache_proxy.rb
|
68
|
+
- lib/arid_cache/helpers.rb
|
64
69
|
- lib/arid_cache/store.rb
|
65
70
|
- rails/init.rb
|
66
|
-
- rails/install.rb
|
67
|
-
- rails/uninstall.rb
|
68
71
|
- spec/arid_cache_spec.rb
|
69
72
|
- spec/spec.opts
|
70
73
|
- spec/spec_helper.rb
|
data/rails/install.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
# Install hook code here
|
data/rails/uninstall.rb
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
# Uninstall hook code here
|