cache_advance 1.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/CHANGELOG +8 -0
- data/README.textile +47 -0
- data/Rakefile +23 -0
- data/TODO.textile +6 -0
- data/VERSION +1 -0
- data/cache_advance.gemspec +68 -0
- data/lib/cache_advance/active_record_sweeper.rb +32 -0
- data/lib/cache_advance/cache_set.rb +57 -0
- data/lib/cache_advance/cached_key_list.rb +46 -0
- data/lib/cache_advance/lock.rb +40 -0
- data/lib/cache_advance/mapper.rb +29 -0
- data/lib/cache_advance/named_cache.rb +124 -0
- data/lib/cache_advance/named_cache_configuration.rb +7 -0
- data/lib/cache_advance.rb +21 -0
- data/rails/init.rb +52 -0
- data/script/console +5 -0
- data/test/active_record_sweeper_test.rb +32 -0
- data/test/cache_mock.rb +21 -0
- data/test/cache_set_test.rb +40 -0
- data/test/key_test.rb +41 -0
- data/test/mapper_test.rb +46 -0
- data/test/named_cache_test.rb +29 -0
- data/test/test_helper.rb +10 -0
- metadata +84 -0
data/.gitignore
ADDED
data/CHANGELOG
ADDED
data/README.textile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
h1. cache advance
|
2
|
+
|
3
|
+
CacheAdvance is a wrapper around the Rails caching system that provides a simple, centralized
|
4
|
+
configuration file for defining caches and an even simpler way to apply them.
|
5
|
+
|
6
|
+
Written by "Aubrey Holland":mailto:aubreyholland@gmail.com.
|
7
|
+
|
8
|
+
h3. download
|
9
|
+
|
10
|
+
Github: "Page":http://github.com/aub/cache_advance/tree/master
|
11
|
+
|
12
|
+
Gem: <pre>gem install aub-cache_advance --source http://gems.github.com</pre>
|
13
|
+
|
14
|
+
Note: if you install using the gem from Github, you'll need this
|
15
|
+
in your environment.rb if you want to use Rails 2.1's dependency manager:
|
16
|
+
|
17
|
+
<pre><code>
|
18
|
+
config.gem 'aub-cache_advance', :lib => 'cache_advance', :source => 'http://gems.github.com'
|
19
|
+
</code></pre>
|
20
|
+
|
21
|
+
h3. cache definition
|
22
|
+
|
23
|
+
Caches are defined in the file config/caches.rb, which will be loaded automatically by the gem.
|
24
|
+
This file is similar in format to the rails routes config file, allowing you to specify named
|
25
|
+
caches and configure their keys and how they will be expired.
|
26
|
+
|
27
|
+
<pre><code>
|
28
|
+
CacheAdvance::Caches.define_caches do |config|
|
29
|
+
|
30
|
+
config.qualifier(:params) do |request|
|
31
|
+
request.params
|
32
|
+
end
|
33
|
+
|
34
|
+
config.plugin :template_handler_observer_cache_plugin
|
35
|
+
|
36
|
+
config.content_block :expiration_time => 10.minutes, :qualifiers => [ :subdomain, :params ]
|
37
|
+
|
38
|
+
config.change_towns_all :expiration_types => [ :publication ]
|
39
|
+
config.change_towns_limited :expiration_types => [ :publication ]
|
40
|
+
|
41
|
+
config.publication_twitter_update :expiration_time => 10.minutes, :qualifiers => [ :subdomain ]
|
42
|
+
|
43
|
+
config.weather_widget :expiration_time => 10.minutes, :qualifiers => [ :subdomain ]
|
44
|
+
end
|
45
|
+
</code></pre>
|
46
|
+
|
47
|
+
..............More later.
|
data/Rakefile
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/testtask'
|
4
|
+
|
5
|
+
FileList['tasks/**/*.rake'].each { |file| load file }
|
6
|
+
|
7
|
+
task :default => 'test'
|
8
|
+
|
9
|
+
begin
|
10
|
+
require 'jeweler'
|
11
|
+
Jeweler::Tasks.new do |gemspec|
|
12
|
+
gemspec.name = 'cache_advance'
|
13
|
+
gemspec.summary = 'A declarative system for caching with ActiveRecord'
|
14
|
+
gemspec.email = 'aubreyholland@gmail.com'
|
15
|
+
gemspec.homepage = 'http://github.com/aub/cache_advance/tree/master'
|
16
|
+
gemspec.description = 'hmm'
|
17
|
+
gemspec.authors = ['Aubrey Holland']
|
18
|
+
end
|
19
|
+
Jeweler::GemcutterTasks.new
|
20
|
+
rescue LoadError
|
21
|
+
puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
22
|
+
end
|
23
|
+
|
data/TODO.textile
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
- Fix to allow keys of > 256 characters with memcache
|
2
|
+
- Make test coverage not pitiful
|
3
|
+
- Add ability for cache to expire when a specific object changes
|
4
|
+
- Apply plugins only to specific caches
|
5
|
+
- Test with caching strategies other than memcache
|
6
|
+
- Improve documentation
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.1.4
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{cache_advance}
|
8
|
+
s.version = "1.1.4"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Aubrey Holland"]
|
12
|
+
s.date = %q{2009-10-21}
|
13
|
+
s.description = %q{hmm}
|
14
|
+
s.email = %q{aubreyholland@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.textile"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
"CHANGELOG",
|
21
|
+
"README.textile",
|
22
|
+
"Rakefile",
|
23
|
+
"TODO.textile",
|
24
|
+
"VERSION",
|
25
|
+
"cache_advance.gemspec",
|
26
|
+
"lib/cache_advance.rb",
|
27
|
+
"lib/cache_advance/active_record_sweeper.rb",
|
28
|
+
"lib/cache_advance/cache_set.rb",
|
29
|
+
"lib/cache_advance/cached_key_list.rb",
|
30
|
+
"lib/cache_advance/lock.rb",
|
31
|
+
"lib/cache_advance/mapper.rb",
|
32
|
+
"lib/cache_advance/named_cache.rb",
|
33
|
+
"lib/cache_advance/named_cache_configuration.rb",
|
34
|
+
"rails/init.rb",
|
35
|
+
"script/console",
|
36
|
+
"test/active_record_sweeper_test.rb",
|
37
|
+
"test/cache_mock.rb",
|
38
|
+
"test/cache_set_test.rb",
|
39
|
+
"test/key_test.rb",
|
40
|
+
"test/mapper_test.rb",
|
41
|
+
"test/named_cache_test.rb",
|
42
|
+
"test/test_helper.rb"
|
43
|
+
]
|
44
|
+
s.homepage = %q{http://github.com/aub/cache_advance/tree/master}
|
45
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
46
|
+
s.require_paths = ["lib"]
|
47
|
+
s.rubygems_version = %q{1.3.5}
|
48
|
+
s.summary = %q{A declarative system for caching with ActiveRecord}
|
49
|
+
s.test_files = [
|
50
|
+
"test/active_record_sweeper_test.rb",
|
51
|
+
"test/cache_mock.rb",
|
52
|
+
"test/cache_set_test.rb",
|
53
|
+
"test/key_test.rb",
|
54
|
+
"test/mapper_test.rb",
|
55
|
+
"test/named_cache_test.rb",
|
56
|
+
"test/test_helper.rb"
|
57
|
+
]
|
58
|
+
|
59
|
+
if s.respond_to? :specification_version then
|
60
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
61
|
+
s.specification_version = 3
|
62
|
+
|
63
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
64
|
+
else
|
65
|
+
end
|
66
|
+
else
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'activerecord'
|
3
|
+
require 'active_record/observer'
|
4
|
+
|
5
|
+
module CacheAdvance
|
6
|
+
class ActiveRecordSweeper < ::ActiveRecord::Observer
|
7
|
+
|
8
|
+
def self.initialize_observed(classes)
|
9
|
+
observe(classes)
|
10
|
+
end
|
11
|
+
|
12
|
+
def reload_sweeper
|
13
|
+
observed_classes.each do |klass|
|
14
|
+
klass.name.constantize.add_observer(self)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def after_create(object)
|
19
|
+
expire_caches_for(object)
|
20
|
+
end
|
21
|
+
|
22
|
+
alias_method :after_update, :after_create
|
23
|
+
alias_method :after_destroy, :after_create
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def expire_caches_for(object)
|
28
|
+
class_symbol = object.class.name.underscore.to_sym
|
29
|
+
CacheAdvance.cache_set.expire_for_class(class_symbol)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
module CacheAdvance
|
2
|
+
class CacheSet
|
3
|
+
attr_reader :named_caches
|
4
|
+
attr_reader :qualifiers
|
5
|
+
attr_reader :plugins
|
6
|
+
|
7
|
+
def initialize(store)
|
8
|
+
@store, @named_caches, @qualifiers, @plugins = store, {}, {}, []
|
9
|
+
end
|
10
|
+
|
11
|
+
def setup_complete
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
def apply(cache_name, request, options, &block)
|
16
|
+
cache_name = cache_name.to_sym
|
17
|
+
if CacheAdvance.caching_enabled
|
18
|
+
named_cache = @named_caches[cache_name]
|
19
|
+
raise UnknownNamedCacheException if named_cache.nil?
|
20
|
+
named_cache.value_for(request, options, &block)
|
21
|
+
else
|
22
|
+
block.call
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def add_qualifier(name, proc)
|
27
|
+
@qualifiers[name] = proc
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_plugin(plugin)
|
31
|
+
@plugins << plugin
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_named_cache(name, options)
|
35
|
+
name = name.to_sym
|
36
|
+
@named_caches[name] = NamedCache.new(name, options, self, @store)
|
37
|
+
end
|
38
|
+
|
39
|
+
def define_caches
|
40
|
+
yield Mapper.new(self)
|
41
|
+
end
|
42
|
+
|
43
|
+
def create_sweepers
|
44
|
+
@sweeper_type.initialize_observed(@named_caches.values.map { |c| c.expiration_types }.flatten.compact.uniq)
|
45
|
+
end
|
46
|
+
|
47
|
+
def expire_for_class(class_name)
|
48
|
+
@named_caches.values.each do |named_cache|
|
49
|
+
named_cache.expire_for(class_name)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def sweeper_type=(type)
|
54
|
+
@sweeper_type = type
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module CacheAdvance
|
2
|
+
class CachedKeyList
|
3
|
+
def initialize(store, cache_key, expiration_time=nil)
|
4
|
+
@store, @cache_key, @expiration_time = store, cache_key, expiration_time
|
5
|
+
end
|
6
|
+
|
7
|
+
def all_keys
|
8
|
+
key_list.keys
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_key(key)
|
12
|
+
Lock.new(@store).execute_locked(@cache_key) do
|
13
|
+
data = key_list
|
14
|
+
unless data.has_key?(key)
|
15
|
+
data[key] = @expiration_time.nil? ? nil : Time.now + @expiration_time
|
16
|
+
@store.set(@cache_key, data)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def delete_key(key)
|
22
|
+
Lock.new(@store).execute_locked(@cache_key) do
|
23
|
+
data = key_list
|
24
|
+
if data.has_key?(key)
|
25
|
+
data.delete(key)
|
26
|
+
@store.set(@cache_key, data)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def clear
|
32
|
+
@store.set(@cache_key, {})
|
33
|
+
end
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def key_list
|
38
|
+
list = @store.get(@cache_key) || {}
|
39
|
+
if @expiration_time
|
40
|
+
now = Time.now
|
41
|
+
list.delete_if { |k,v| v <= now }
|
42
|
+
end
|
43
|
+
list
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module CacheAdvance
|
2
|
+
class Lock
|
3
|
+
|
4
|
+
class LockAcquisitionFailureException < Exception; end
|
5
|
+
|
6
|
+
DEFAULT_RETRIES = 5
|
7
|
+
DEFAULT_EXPIRATION_TIME = 30
|
8
|
+
|
9
|
+
def initialize(store)
|
10
|
+
@store = store
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute_locked(key, lock_expiry = DEFAULT_EXPIRATION_TIME, retries = DEFAULT_RETRIES)
|
14
|
+
begin
|
15
|
+
acquire(key, lock_expiry, retries)
|
16
|
+
yield
|
17
|
+
ensure
|
18
|
+
release_lock(key)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def acquire(key, lock_expiry = DEFAULT_EXPIRATION_TIME, retries = DEFAULT_RETRIES)
|
23
|
+
retries.times do |count|
|
24
|
+
begin
|
25
|
+
return if @store.set("lock/#{key}", Process.pid, lock_expiry) == "STORED\r\n"
|
26
|
+
end
|
27
|
+
exponential_sleep(count) unless count == retries - 1
|
28
|
+
end
|
29
|
+
raise LockAcquisitionFailureException, "Couldn't acquire memcache lock for: #{key}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def release_lock(key)
|
33
|
+
@store.delete("lock/#{key}")
|
34
|
+
end
|
35
|
+
|
36
|
+
def exponential_sleep(count)
|
37
|
+
sleep((2**count) / 10.0)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
gem 'activesupport'
|
2
|
+
require 'active_support/core_ext'
|
3
|
+
|
4
|
+
module CacheAdvance
|
5
|
+
class Mapper
|
6
|
+
def initialize(cache_set)
|
7
|
+
@cache_set = cache_set
|
8
|
+
end
|
9
|
+
|
10
|
+
def qualifier(name, &proc)
|
11
|
+
@cache_set.add_qualifier(name, proc)
|
12
|
+
end
|
13
|
+
|
14
|
+
def plugin(name)
|
15
|
+
if name.is_a?(Symbol)
|
16
|
+
plugin = name.to_s.camelcase.constantize.new
|
17
|
+
elsif name.is_a?(Class)
|
18
|
+
plugin = name.new
|
19
|
+
else
|
20
|
+
plugin = name
|
21
|
+
end
|
22
|
+
@cache_set.add_plugin(plugin)
|
23
|
+
end
|
24
|
+
|
25
|
+
def method_missing(method, options={})
|
26
|
+
@cache_set.add_named_cache(method, options)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module CacheAdvance
|
2
|
+
class NamedCache
|
3
|
+
|
4
|
+
ENABLED_CHECK_INTERVAL = 60
|
5
|
+
|
6
|
+
def initialize(name, params, cache_set, store)
|
7
|
+
@name = name.to_s
|
8
|
+
@params = params
|
9
|
+
@cache_set = cache_set
|
10
|
+
@store = store
|
11
|
+
@cached_key_list = CachedKeyList.new(@store, "#{@name}/STORED_CACHES", expiration_time)
|
12
|
+
@enabled_check_time = Time.now + ENABLED_CHECK_INTERVAL
|
13
|
+
@enabled = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def value_for(request, options, &block)
|
17
|
+
return block.call unless enabled?
|
18
|
+
|
19
|
+
key = key_for(request, options[:key])
|
20
|
+
|
21
|
+
if (value = read_from_store(key))
|
22
|
+
each_plugin { |p| p.send('after_read', @name, key, value, options) if p.respond_to?('after_read') }
|
23
|
+
return value
|
24
|
+
end
|
25
|
+
|
26
|
+
each_plugin { |p| p.send('before_render', @name, key, options) if p.respond_to?('before_render') }
|
27
|
+
result = block.call
|
28
|
+
each_plugin { |p| p.send('after_render', @name, key, result, options) if p.respond_to?('after_render') }
|
29
|
+
each_plugin { |p| p.send('before_write', @name, key, result, options) if p.respond_to?('before_write') }
|
30
|
+
write_to_store(key, result)
|
31
|
+
each_plugin { |p| p.send('after_write', @name, key, result, options) if p.respond_to?('after_write') }
|
32
|
+
result
|
33
|
+
end
|
34
|
+
|
35
|
+
def expire_for(type)
|
36
|
+
if expiration_types.include?(type)
|
37
|
+
expire_all
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def expire_all
|
42
|
+
delete_all_from_store
|
43
|
+
end
|
44
|
+
|
45
|
+
def all_cached_keys
|
46
|
+
@cached_key_list.all_keys
|
47
|
+
end
|
48
|
+
|
49
|
+
def expiration_types
|
50
|
+
Array(@params[:expiration_types])
|
51
|
+
end
|
52
|
+
|
53
|
+
def title
|
54
|
+
@params[:title] || @name.to_s
|
55
|
+
end
|
56
|
+
|
57
|
+
def enabled=(state)
|
58
|
+
@enabled = !!state
|
59
|
+
@store.set(enabled_key, @enabled)
|
60
|
+
end
|
61
|
+
|
62
|
+
def enabled?
|
63
|
+
if @enabled.nil? || Time.now >= @enabled_check_time
|
64
|
+
@enabled = [nil, true].include?(read_from_store(enabled_key, false))
|
65
|
+
@enabled_check_time = Time.now + ENABLED_CHECK_INTERVAL
|
66
|
+
end
|
67
|
+
@enabled
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
def read_from_store(key, add_to_key_list=true)
|
73
|
+
data = @store.get(key)
|
74
|
+
# this is to prevent a situation where the cached key list forgets
|
75
|
+
# about keys that are actually cached.
|
76
|
+
if data && add_to_key_list
|
77
|
+
@cached_key_list.add_key(key)
|
78
|
+
end
|
79
|
+
data
|
80
|
+
end
|
81
|
+
|
82
|
+
def write_to_store(key, value)
|
83
|
+
expiration_time ? @store.set(key, value, expiration_time) : @store.set(key, value)
|
84
|
+
@cached_key_list.add_key(key)
|
85
|
+
end
|
86
|
+
|
87
|
+
def delete_from_store(key)
|
88
|
+
@store.delete(key)
|
89
|
+
@cached_key_list.delete_key(key)
|
90
|
+
end
|
91
|
+
|
92
|
+
def delete_all_from_store
|
93
|
+
@cached_key_list.all_keys.each { |key| delete_from_store(key) }
|
94
|
+
@cached_key_list.clear
|
95
|
+
end
|
96
|
+
|
97
|
+
def each_plugin
|
98
|
+
@cache_set.plugins.each do |p|
|
99
|
+
yield p
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def key_for(request, suffix='')
|
104
|
+
qualifier_data = qualifiers.map do |q|
|
105
|
+
if (qualifier = @cache_set.qualifiers[q])
|
106
|
+
(qualifier.call(request) || '').to_s
|
107
|
+
end
|
108
|
+
end.join('/')
|
109
|
+
"#{@name}/#{suffix}/[#{qualifier_data}]".gsub(/\s/, '')
|
110
|
+
end
|
111
|
+
|
112
|
+
def enabled_key
|
113
|
+
"#{@name}/ENABLED_STATUS"
|
114
|
+
end
|
115
|
+
|
116
|
+
def expiration_time
|
117
|
+
@params[:expiration_time]
|
118
|
+
end
|
119
|
+
|
120
|
+
def qualifiers
|
121
|
+
Array(@params[:qualifiers])
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
%w( cache_set cached_key_list lock mapper named_cache named_cache_configuration).each do |file|
|
2
|
+
require File.join(File.dirname(__FILE__), 'cache_advance', file)
|
3
|
+
end
|
4
|
+
|
5
|
+
module CacheAdvance
|
6
|
+
class UnknownNamedCacheException < Exception; end
|
7
|
+
|
8
|
+
class << self
|
9
|
+
attr_reader :cache_set
|
10
|
+
attr_accessor :caching_enabled
|
11
|
+
end
|
12
|
+
|
13
|
+
@cache_set = nil
|
14
|
+
@caching_enabled = true
|
15
|
+
|
16
|
+
def self.define_caches(store)
|
17
|
+
@cache_set = CacheSet.new(store)
|
18
|
+
yield Mapper.new(@cache_set)
|
19
|
+
@cache_set.setup_complete # This allows the cache set to finalize some of its configuration
|
20
|
+
end
|
21
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'cache_advance'
|
2
|
+
require 'cache_advance/active_record_sweeper'
|
3
|
+
|
4
|
+
require "#{RAILS_ROOT}/config/caches"
|
5
|
+
require 'dispatcher'
|
6
|
+
|
7
|
+
# Setup the sweeper and cache types as appropriate for Rails.
|
8
|
+
CacheAdvance.cache_set.sweeper_type = CacheAdvance::ActiveRecordSweeper
|
9
|
+
|
10
|
+
CacheAdvance.caching_enabled = config.action_controller.perform_caching
|
11
|
+
|
12
|
+
# This is the helper method that can be used in rails views/controllers/helpers.
|
13
|
+
# If caching is disabled, just make it yield the results of the block.
|
14
|
+
if config.action_controller.perform_caching
|
15
|
+
ActionController::Base.helper do
|
16
|
+
def cache_it(cache, options={}, &block)
|
17
|
+
CacheAdvance.cache_set.plugins.each do |plugin|
|
18
|
+
options.merge!(plugin.cache_it_options(self)) if plugin.respond_to?('cache_it_options')
|
19
|
+
end
|
20
|
+
CacheAdvance.cache_set.apply(cache, request, options) do
|
21
|
+
capture(&block)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
else
|
26
|
+
ActionController::Base.helper do
|
27
|
+
def cache_it(cache, options={}, &block)
|
28
|
+
capture(&block)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
ActionMailer::Base.helper do
|
34
|
+
def cache_it(cache, options={}, &block)
|
35
|
+
capture(&block)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# This will get called after the standard rails environment is initialized.
|
40
|
+
config.after_initialize do
|
41
|
+
if config.action_controller.perform_caching
|
42
|
+
# This hooks the sweepers into the observer system and adds it to the list.
|
43
|
+
CacheAdvance.cache_set.create_sweepers
|
44
|
+
ActiveRecord::Base.observers << CacheAdvance::ActiveRecordSweeper
|
45
|
+
|
46
|
+
# In development mode, the models we observe get reloaded with each request. Using
|
47
|
+
# this hook allows us to reload the observer relationships each time as well.
|
48
|
+
ActionController::Dispatcher.to_prepare(:cache_advance_reload) do
|
49
|
+
CacheAdvance::ActiveRecordSweeper.instance.reload_sweeper
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/script/console
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper.rb')
|
2
|
+
|
3
|
+
require 'cache_advance/active_record_sweeper'
|
4
|
+
|
5
|
+
class Article; end
|
6
|
+
class Publication; end
|
7
|
+
|
8
|
+
class ActiveRecordSweeperTest < Test::Unit::TestCase
|
9
|
+
|
10
|
+
def setup
|
11
|
+
@sweeper = CacheAdvance::ActiveRecordSweeper.instance
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_should_call_observe_with_a_given_set_of_classes
|
15
|
+
CacheAdvance::ActiveRecordSweeper.expects(:observe).with([:publication, :article])
|
16
|
+
CacheAdvance::ActiveRecordSweeper.initialize_observed([:publication, :article])
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_should_re_add_observers
|
20
|
+
CacheAdvance::ActiveRecordSweeper.initialize_observed([:publication, :article])
|
21
|
+
Article.expects(:add_observer).with(@sweeper)
|
22
|
+
Publication.expects(:add_observer).with(@sweeper)
|
23
|
+
@sweeper.reload_sweeper
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_should_expire_caches_on_changes
|
27
|
+
CacheAdvance.cache_set.expects(:expire_for_class).with(:publication).times(3)
|
28
|
+
%w(after_create after_update after_destroy).each do |method|
|
29
|
+
@sweeper.send(method, Publication.new)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/test/cache_mock.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
module CacheAdvance
|
2
|
+
class CacheMock
|
3
|
+
def initialize
|
4
|
+
@values = {}
|
5
|
+
end
|
6
|
+
|
7
|
+
def get(key)
|
8
|
+
@values[key]
|
9
|
+
end
|
10
|
+
|
11
|
+
def set(key, value, options={})
|
12
|
+
result = @values.has_key?(key) ? @values[key] : "STORED\r\n"
|
13
|
+
@values[key] = value
|
14
|
+
result
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete(key)
|
18
|
+
@values[key] = nil
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
|
3
|
+
class Plugin; end
|
4
|
+
|
5
|
+
require 'cache_advance/active_record_sweeper'
|
6
|
+
|
7
|
+
class CacheSetTest < Test::Unit::TestCase
|
8
|
+
def setup
|
9
|
+
@cache = CacheAdvance::CacheMock.new
|
10
|
+
@cache_set = CacheAdvance::CacheSet.new(@cache)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_define_caches_should_yield_a_mapper
|
14
|
+
@cache_set.define_caches do |mapper|
|
15
|
+
assert_equal CacheAdvance::Mapper, mapper.class
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_should_apply_the_cache_if_found
|
20
|
+
request = mock
|
21
|
+
options = { :key => 'hippo' }
|
22
|
+
@cache_set.add_named_cache(:kewl, {})
|
23
|
+
@cache_set.named_caches[:kewl].expects(:value_for).with(request, options)
|
24
|
+
@cache_set.apply(:kewl, request, options) { }
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_apply_should_throw_exception_with_invalid_name
|
28
|
+
assert_raise CacheAdvance::UnknownNamedCacheException do
|
29
|
+
@cache_set.apply(:total_hack, mock(), {}) { }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_should_pass_expiration_types_to_the_sweeper
|
34
|
+
@cache_set.sweeper_type = CacheAdvance::ActiveRecordSweeper
|
35
|
+
@cache_set.add_named_cache(:kewl, { :expiration_types => [:publication, :article] })
|
36
|
+
@cache_set.add_named_cache(:howza, { :expiration_types => [:publication] })
|
37
|
+
CacheAdvance::ActiveRecordSweeper.expects(:initialize_observed).with([:publication, :article])
|
38
|
+
@cache_set.create_sweepers
|
39
|
+
end
|
40
|
+
end
|
data/test/key_test.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'test_helper')
|
2
|
+
|
3
|
+
class KeyTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@cache = CacheAdvance::CacheMock.new
|
7
|
+
@cache_set = CacheAdvance::CacheSet.new(@cache)
|
8
|
+
end
|
9
|
+
|
10
|
+
def test_should_symbolize_the_cache_name
|
11
|
+
@cache_set.add_named_cache('booya', {})
|
12
|
+
assert_nothing_raised do
|
13
|
+
@cache_set.apply(:booya, nil, :key => 'abc') do
|
14
|
+
'123'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_should_symbolize_the_cache_name_when_using_it
|
20
|
+
@cache_set.add_named_cache(:booya, {})
|
21
|
+
assert_nothing_raised do
|
22
|
+
@cache_set.apply('booya', nil, :key => 'abc') do
|
23
|
+
'123'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_should_remove_whitespace_from_the_key
|
29
|
+
@cache_set.add_named_cache(:booya, {})
|
30
|
+
result = @cache_set.apply(:booya, nil, :key => 'abc def') do
|
31
|
+
'123'
|
32
|
+
end
|
33
|
+
assert_equal '123', @cache.get('booya/abcdef/[]')
|
34
|
+
assert_equal '123', result
|
35
|
+
@cache.set('booya/abcdef/[]', '234')
|
36
|
+
result = @cache_set.apply(:booya, nil, :key => 'abc def') do
|
37
|
+
'123'
|
38
|
+
end
|
39
|
+
assert_equal '234', result
|
40
|
+
end
|
41
|
+
end
|
data/test/mapper_test.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
|
3
|
+
class Hack
|
4
|
+
end
|
5
|
+
|
6
|
+
class MapperTest < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@cache = CacheAdvance::CacheMock.new
|
10
|
+
@cache_set = CacheAdvance::CacheSet.new(@cache)
|
11
|
+
@mapper = CacheAdvance::Mapper.new(@cache_set)
|
12
|
+
end
|
13
|
+
|
14
|
+
def test_qualifier
|
15
|
+
@mapper.qualifier(:thirty_four) do
|
16
|
+
34
|
17
|
+
end
|
18
|
+
assert_equal 1, @cache_set.qualifiers.size
|
19
|
+
assert_equal 34, @cache_set.qualifiers[:thirty_four].call
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_plugin_from_symbol
|
23
|
+
@mapper.plugin(:hack)
|
24
|
+
assert_equal 1, @cache_set.plugins.size
|
25
|
+
assert_equal Hack, @cache_set.plugins.first.class
|
26
|
+
end
|
27
|
+
|
28
|
+
def test_plugin_from_class
|
29
|
+
@mapper.plugin(Hack)
|
30
|
+
assert_equal 1, @cache_set.plugins.size
|
31
|
+
assert_equal Hack, @cache_set.plugins.first.class
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_plugin_from_object
|
35
|
+
hack = Hack.new
|
36
|
+
@mapper.plugin(hack)
|
37
|
+
assert_equal 1, @cache_set.plugins.size
|
38
|
+
assert_equal hack, @cache_set.plugins.first
|
39
|
+
end
|
40
|
+
|
41
|
+
def test_adding_caches_through_method_missing
|
42
|
+
@mapper.say_what :option => 2
|
43
|
+
assert_equal 1, @cache_set.named_caches.size
|
44
|
+
assert_equal CacheAdvance::NamedCache, @cache_set.named_caches[:say_what].class
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
|
3
|
+
class NamedCacheTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@request = mock
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_key_for_in_basic_case
|
10
|
+
create_named_cache
|
11
|
+
assert_equal 'test_cache//[]', @named_cache.send(:key_for, @request)
|
12
|
+
assert_equal 'test_cache/suf/[]', @named_cache.send(:key_for, @request, 'suf')
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_key_with_qualifiers
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
|
21
|
+
def create_named_cache(options={})
|
22
|
+
@name = options[:name] || 'test_cache'
|
23
|
+
@params = {}
|
24
|
+
@cache = CacheAdvance::CacheMock.new
|
25
|
+
@cache_set = CacheAdvance::CacheSet.new(@cache)
|
26
|
+
@named_cache = CacheAdvance::NamedCache.new(@name, @params, @cache_set, @cache)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cache_advance
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.1.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aubrey Holland
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-10-21 00:00:00 -04:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: hmm
|
17
|
+
email: aubreyholland@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- README.textile
|
24
|
+
files:
|
25
|
+
- .gitignore
|
26
|
+
- CHANGELOG
|
27
|
+
- README.textile
|
28
|
+
- Rakefile
|
29
|
+
- TODO.textile
|
30
|
+
- VERSION
|
31
|
+
- cache_advance.gemspec
|
32
|
+
- lib/cache_advance.rb
|
33
|
+
- lib/cache_advance/active_record_sweeper.rb
|
34
|
+
- lib/cache_advance/cache_set.rb
|
35
|
+
- lib/cache_advance/cached_key_list.rb
|
36
|
+
- lib/cache_advance/lock.rb
|
37
|
+
- lib/cache_advance/mapper.rb
|
38
|
+
- lib/cache_advance/named_cache.rb
|
39
|
+
- lib/cache_advance/named_cache_configuration.rb
|
40
|
+
- rails/init.rb
|
41
|
+
- script/console
|
42
|
+
- test/active_record_sweeper_test.rb
|
43
|
+
- test/cache_mock.rb
|
44
|
+
- test/cache_set_test.rb
|
45
|
+
- test/key_test.rb
|
46
|
+
- test/mapper_test.rb
|
47
|
+
- test/named_cache_test.rb
|
48
|
+
- test/test_helper.rb
|
49
|
+
has_rdoc: true
|
50
|
+
homepage: http://github.com/aub/cache_advance/tree/master
|
51
|
+
licenses: []
|
52
|
+
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options:
|
55
|
+
- --charset=UTF-8
|
56
|
+
require_paths:
|
57
|
+
- lib
|
58
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: "0"
|
63
|
+
version:
|
64
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: "0"
|
69
|
+
version:
|
70
|
+
requirements: []
|
71
|
+
|
72
|
+
rubyforge_project:
|
73
|
+
rubygems_version: 1.3.5
|
74
|
+
signing_key:
|
75
|
+
specification_version: 3
|
76
|
+
summary: A declarative system for caching with ActiveRecord
|
77
|
+
test_files:
|
78
|
+
- test/active_record_sweeper_test.rb
|
79
|
+
- test/cache_mock.rb
|
80
|
+
- test/cache_set_test.rb
|
81
|
+
- test/key_test.rb
|
82
|
+
- test/mapper_test.rb
|
83
|
+
- test/named_cache_test.rb
|
84
|
+
- test/test_helper.rb
|