cache_advance 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +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
|