aub-cache_advance 0.2.0

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/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,62 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/rdoctask'
5
+ require 'rake/gempackagetask'
6
+ require 'date'
7
+
8
+ desc 'Default: run unit tests.'
9
+ task :default => :test
10
+
11
+ desc 'Test the system.'
12
+ Rake::TestTask.new(:test) do |t|
13
+ t.libs << 'lib'
14
+ t.pattern = 'test/**/*_test.rb'
15
+ t.verbose = true
16
+ end
17
+
18
+ desc 'Generate documentation for the system.'
19
+ Rake::RDocTask.new(:rdoc) do |rdoc|
20
+ rdoc.rdoc_dir = 'rdoc'
21
+ rdoc.title = 'Cache Advance'
22
+ rdoc.options << '--line-numbers' << '--inline-source' << "--main" << "README.textile"
23
+ rdoc.rdoc_files.include('README.textile')
24
+ rdoc.rdoc_files.include('lib/**/*.rb')
25
+ end
26
+
27
+ spec = Gem::Specification.new do |s|
28
+ s.name = %q{cache_advance}
29
+ s.version = "1.0.0"
30
+ s.summary = %q{A system for spiffy declarative caching}
31
+ s.description = %q{A system for spiffy declarative caching}
32
+ s.homepage = 'http://github.com/aub/cache_advance'
33
+
34
+ s.files = FileList['[A-Z]*', 'lib/**/*.rb', 'rails/**/*.rb', 'test/**/*.rb']
35
+ s.require_path = 'lib'
36
+ s.test_files = Dir[*['test/**/*_test.rb']]
37
+
38
+ s.has_rdoc = true
39
+ s.extra_rdoc_files = ["README.textile"]
40
+ s.rdoc_options = ['--line-numbers', '--inline-source', "--main", "README.textile"]
41
+
42
+ s.authors = ["Aubrey Holland"]
43
+ s.email = %q{aubrey@patch.com}
44
+
45
+ s.platform = Gem::Platform::RUBY
46
+ s.add_dependency(%q<activesupport>, [">= 1.0"])
47
+ end
48
+
49
+ Rake::GemPackageTask.new spec do |pkg|
50
+ pkg.need_tar = true
51
+ pkg.need_zip = true
52
+ end
53
+
54
+ desc "Clean files generated by rake tasks"
55
+ task :clobber => [:clobber_rdoc, :clobber_package]
56
+
57
+ desc "Generate a gemspec file"
58
+ task :gemspec do
59
+ File.open("#{spec.name}.gemspec", 'w') do |f|
60
+ f.write spec.to_ruby
61
+ end
62
+ end
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
@@ -0,0 +1,24 @@
1
+ Gem::Specification.new do |s|
2
+ s.version = '0.2.0'
3
+ s.date = %q{2009-01-08}
4
+
5
+ s.name = %q{cache_advance}
6
+ s.required_rubygems_version = Gem::Requirement.new('>= 0') if s.respond_to? :required_rubygems_version=
7
+ s.authors = ['Aubrey Holland']
8
+ s.description = %q{A system for spiffy declarative caching}
9
+ s.email = %q{aubrey@patch.com}
10
+ s.files = %w(README.textile Rakefile TODO.textile cache_advance-1.0.9.gem cache_advance.gemspec lib/cache_advance.rb lib/cache_advance/active_record_sweeper.rb lib/cache_advance/cache_set.rb lib/cache_advance/cached_key_list.rb lib/cache_advance/lock.rb lib/cache_advance/mapper.rb lib/cache_advance/named_cache.rb lib/cache_advance/named_cache_configuration.rb rails/init.rb test/active_record_sweeper_test.rb test/cache_mock.rb test/cache_set_test.rb test/mapper_test.rb test/named_cache_test.rb test/spec/db/schema.rb test/spec/mapper_spec.rb test/spec/mocks/memcache.rb test/spec/spec_helper.rb test/test_helper.rb)
11
+ s.homepage = %q{http://github.com/aub/cache_advance}
12
+ s.require_paths = ['lib']
13
+ s.rubygems_version = %q{1.2.0}
14
+ s.summary = %q{A system for spiffy declarative caching}
15
+ s.has_rdoc = true
16
+ s.extra_rdoc_files = ['README.textile']
17
+ s.rdoc_options = ['--line-numbers', '--inline-source', '--main', 'README.textile']
18
+ s.test_files = %w(test/active_record_sweeper_test.rb test/cache_set_test.rb test/mapper_test.rb test/named_cache_test.rb)
19
+
20
+ if s.respond_to? :specification_version then
21
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
22
+ s.specification_version = 2
23
+ end
24
+ end
@@ -0,0 +1,32 @@
1
+ require 'rubygems'
2
+ gem 'activerecord'
3
+ require 'active_record'
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,51 @@
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
+ named_cache = @named_caches[cache_name]
17
+ raise UnknownNamedCacheException if named_cache.nil?
18
+ named_cache.value_for(request, options, &block)
19
+ end
20
+
21
+ def add_qualifier(name, proc)
22
+ @qualifiers[name] = proc
23
+ end
24
+
25
+ def add_plugin(plugin)
26
+ @plugins << plugin
27
+ end
28
+
29
+ def add_named_cache(name, options)
30
+ @named_caches[name] = NamedCache.new(name, options, self, @store)
31
+ end
32
+
33
+ def define_caches
34
+ yield Mapper.new(self)
35
+ end
36
+
37
+ def create_sweepers
38
+ @sweeper_type.initialize_observed(@named_caches.values.map { |c| c.expiration_types }.flatten.compact.uniq)
39
+ end
40
+
41
+ def expire_for_class(class_name)
42
+ @named_caches.values.each do |named_cache|
43
+ named_cache.expire_for(class_name)
44
+ end
45
+ end
46
+
47
+ def sweeper_type=(type)
48
+ @sweeper_type = type
49
+ end
50
+ end
51
+ 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
+ @runtime += Benchmark::measure { sleep((2**count) / 10.0) }
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,26 @@
1
+ module CacheAdvance
2
+ class Mapper
3
+ def initialize(cache_set)
4
+ @cache_set = cache_set
5
+ end
6
+
7
+ def qualifier(name, &proc)
8
+ @cache_set.add_qualifier(name, proc)
9
+ end
10
+
11
+ def plugin(name)
12
+ if name.is_a?(Symbol)
13
+ plugin = name.to_s.camelcase.constantize.new
14
+ elsif name.is_a?(Class)
15
+ plugin = name.new
16
+ else
17
+ plugin = name
18
+ end
19
+ @cache_set.add_plugin(plugin)
20
+ end
21
+
22
+ def method_missing(method, options={})
23
+ @cache_set.add_named_cache(method, options)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,120 @@
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, request, value) if p.respond_to?('after_read') }
23
+ return value
24
+ end
25
+
26
+ each_plugin { |p| p.send('before_render', @name, key, request) if p.respond_to?('before_render') }
27
+ result = block.call
28
+ each_plugin { |p| p.send('after_render', @name, key, request, result) if p.respond_to?('after_render') }
29
+ each_plugin { |p| p.send('before_write', @name, key, request, result) if p.respond_to?('before_write') }
30
+ write_to_store(key, result)
31
+ each_plugin { |p| p.send('after_write', @name, key, request, result) 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
+ write_to_store(enabled_key, @enabled, false)
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))
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)
73
+ @store.get(key)
74
+ end
75
+
76
+ def write_to_store(key, value, add_to_key_list=true)
77
+ expiration_time ? @store.set(key, value, expiration_time) : @store.set(key, value)
78
+ if add_to_key_list
79
+ @cached_key_list.add_key(key)
80
+ end
81
+ end
82
+
83
+ def delete_from_store(key)
84
+ @store.delete(key)
85
+ @cached_key_list.delete_key(key)
86
+ end
87
+
88
+ def delete_all_from_store
89
+ @cached_key_list.all_keys.each { |key| delete_from_store(key) }
90
+ @cached_key_list.clear
91
+ end
92
+
93
+ def each_plugin
94
+ @cache_set.plugins.each do |p|
95
+ yield p
96
+ end
97
+ end
98
+
99
+ def key_for(request, suffix='')
100
+ qualifier_data = qualifiers.map do |q|
101
+ if (qualifier = @cache_set.qualifiers[q])
102
+ (qualifier.call(request) || '').to_s
103
+ end
104
+ end.join('/')
105
+ "#{@name}/#{suffix}/[#{qualifier_data}]"
106
+ end
107
+
108
+ def enabled_key
109
+ "#{@name}/ENABLED_STATUS"
110
+ end
111
+
112
+ def expiration_time
113
+ @params[:expiration_time]
114
+ end
115
+
116
+ def qualifiers
117
+ Array(@params[:qualifiers])
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,7 @@
1
+ module CacheAdvance
2
+ class NamedCacheConfiguration
3
+ def initialize(options)
4
+ @options = options
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ require 'cache_advance/cache_set'
2
+ require 'cache_advance/cached_key_list'
3
+ require 'cache_advance/mapper'
4
+ require 'cache_advance/named_cache'
5
+ require 'cache_advance/named_cache_configuration'
6
+
7
+ module CacheAdvance
8
+ class UnknownNamedCacheException < Exception; end
9
+
10
+ class << self
11
+ attr_reader :cache_set
12
+ end
13
+
14
+ def self.define_caches(store)
15
+ @cache_set = CacheSet.new(store)
16
+ yield Mapper.new(@cache_set)
17
+ @cache_set.setup_complete # This allows the cache set to finalize some of its configuration
18
+ end
19
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,47 @@
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
+ # This is the helper method that can be used in rails views/controllers/helpers.
11
+ # If caching is disabled, just make it yield the results of the block.
12
+ if config.action_controller.perform_caching
13
+ ActionController::Base.helper do
14
+ def cache_it(cache, options={}, &block)
15
+ CacheAdvance.cache_set.apply(cache, request, options) do
16
+ capture(&block)
17
+ end
18
+ end
19
+ end
20
+ else
21
+ ActionController::Base.helper do
22
+ def cache_it(cache, options={}, &block)
23
+ capture(&block)
24
+ end
25
+ end
26
+ end
27
+
28
+ ActionMailer::Base.helper do
29
+ def cache_it(cache, options={}, &block)
30
+ capture(&block)
31
+ end
32
+ end
33
+
34
+ # This will get called after the standard rails environment is initialized.
35
+ config.after_initialize do
36
+ if config.action_controller.perform_caching
37
+ # This hooks the sweepers into the observer system and adds it to the list.
38
+ CacheAdvance.cache_set.create_sweepers
39
+ ActiveRecord::Base.observers << CacheAdvance::ActiveRecordSweeper
40
+
41
+ # In development mode, the models we observe get reloaded with each request. Using
42
+ # this hook allows us to reload the observer relationships each time as well.
43
+ ActionController::Dispatcher.to_prepare(:cache_advance_reload) do
44
+ CacheAdvance::ActiveRecordSweeper.instance.reload_sweeper
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,32 @@
1
+ require 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::Caches.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
@@ -0,0 +1,15 @@
1
+ module CacheAdvance
2
+ class CacheMock
3
+ def read(key)
4
+ 'abc'
5
+ end
6
+
7
+ def write(key, value)
8
+
9
+ end
10
+
11
+ def delete(key)
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,39 @@
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_set = CacheAdvance::CacheSet.new
10
+ end
11
+
12
+ def test_define_caches_should_yield_a_mapper
13
+ @cache_set.define_caches do |mapper|
14
+ assert_equal CacheAdvance::Mapper, mapper.class
15
+ end
16
+ end
17
+
18
+ def test_should_apply_the_cache_if_found
19
+ request = mock
20
+ options = { :key => 'hippo' }
21
+ @cache_set.add_named_cache(:kewl, {})
22
+ @cache_set.named_caches[:kewl].expects(:value_for).with(request, options)
23
+ @cache_set.apply(:kewl, request, options) { }
24
+ end
25
+
26
+ def test_apply_should_throw_exception_with_invalid_name
27
+ assert_raise CacheAdvance::UnknownNamedCacheException do
28
+ @cache_set.apply(:total_hack, mock(), {}) { }
29
+ end
30
+ end
31
+
32
+ def test_should_pass_expiration_types_to_the_sweeper
33
+ @cache_set.sweeper_type = CacheAdvance::ActiveRecordSweeper
34
+ @cache_set.add_named_cache(:kewl, { :expiration_types => [:publication, :article] })
35
+ @cache_set.add_named_cache(:howza, { :expiration_types => [:publication] })
36
+ CacheAdvance::ActiveRecordSweeper.expects(:initialize_observed).with([:publication, :article])
37
+ @cache_set.create_sweepers
38
+ end
39
+ end
@@ -0,0 +1,45 @@
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_set = CacheAdvance::CacheSet.new
10
+ @mapper = CacheAdvance::Mapper.new(@cache_set)
11
+ end
12
+
13
+ def test_qualifier
14
+ @mapper.qualifier(:thirty_four) do
15
+ 34
16
+ end
17
+ assert_equal 1, @cache_set.qualifiers.size
18
+ assert_equal 34, @cache_set.qualifiers[:thirty_four].call
19
+ end
20
+
21
+ def test_plugin_from_symbol
22
+ @mapper.plugin(:hack)
23
+ assert_equal 1, @cache_set.plugins.size
24
+ assert_equal Hack, @cache_set.plugins.first.class
25
+ end
26
+
27
+ def test_plugin_from_class
28
+ @mapper.plugin(Hack)
29
+ assert_equal 1, @cache_set.plugins.size
30
+ assert_equal Hack, @cache_set.plugins.first.class
31
+ end
32
+
33
+ def test_plugin_from_object
34
+ hack = Hack.new
35
+ @mapper.plugin(hack)
36
+ assert_equal 1, @cache_set.plugins.size
37
+ assert_equal hack, @cache_set.plugins.first
38
+ end
39
+
40
+ def test_adding_caches_through_method_missing
41
+ @mapper.say_what :option => 2
42
+ assert_equal 1, @cache_set.named_caches.size
43
+ assert_equal CacheAdvance::NamedCache, @cache_set.named_caches[:say_what].class
44
+ end
45
+ end
@@ -0,0 +1,126 @@
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.key_for(@request)
12
+ assert_equal 'test_cachesuf', @named_cache.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_set = CacheAdvance::CacheSet.new
25
+ @cache = CacheAdvance::CacheMock.new
26
+ @named_cache = CacheAdvance::NamedCache.new(@name, @params, @cache_set, @cache)
27
+ end
28
+ end
29
+
30
+
31
+ # module CacheAdvance
32
+ # class NamedCache
33
+ # STORED_KEY = 'STORED_CACHES'
34
+ #
35
+ # def initialize(name, params, configuration, cache)
36
+ # @name = name.to_s
37
+ # @params = params
38
+ # @configuration = configuration
39
+ # @cache = cache
40
+ # end
41
+ #
42
+ # def key_for(request, suffix='')
43
+ # key = @name.dup
44
+ # key << suffix.to_s
45
+ #
46
+ # qualifiers.each do |q|
47
+ # if (qualifier = @configuration.qualifiers[q])
48
+ # this_one = qualifier.call(request)
49
+ # key << this_one.to_s unless this_one.nil?
50
+ # end
51
+ # end if qualifiers
52
+ # key
53
+ # end
54
+ #
55
+ # def value_for(request, options, &block)
56
+ # key = key_for(request, options[:key])
57
+ #
58
+ # if (cache = @cache.read(key))
59
+ # call_plugins('after_read', key, request)
60
+ # return cache
61
+ # end
62
+ #
63
+ # call_plugins('before_write', key, request)
64
+ # result = block.call
65
+ # @cache.write(key, result, rails_options)
66
+ # call_plugins('after_write', key, request)
67
+ #
68
+ # add_to_cached_keys_list(key)
69
+ #
70
+ # result
71
+ # end
72
+ #
73
+ # def rails_options
74
+ # options = {}
75
+ # options[:expires_in] = expiration_time if expiration_time
76
+ # options
77
+ # end
78
+ #
79
+ # def expire_for(type)
80
+ # if expiration_types.include?(type)
81
+ # expire_all
82
+ # end
83
+ # end
84
+ #
85
+ # def expire_all
86
+ # if (data = @cache.read(@name + STORED_KEY))
87
+ # data = Array(Marshal.load(data))
88
+ # data.each { |key| @cache.delete(key) }
89
+ # else
90
+ # @cache.delete(@name)
91
+ # end
92
+ # end
93
+ #
94
+ # def expiration_types
95
+ # Array(@params[:expiration_types])
96
+ # end
97
+ #
98
+ # def expiration_time
99
+ # @params[:expiration_time]
100
+ # end
101
+ #
102
+ # def qualifiers
103
+ # Array(@params[:qualifiers])
104
+ # end
105
+ #
106
+ # protected
107
+ #
108
+ # def call_plugins(method, key, request)
109
+ # @configuration.plugins.each { |p| p.send(method, @name, key, request) if p.respond_to?(method) }
110
+ # end
111
+ #
112
+ # def add_to_cached_keys_list(key)
113
+ # unless expiration_types.blank? || key == @name
114
+ # if (data = @cache.read(@name + STORED_KEY))
115
+ # data = Array(Marshal.load(data))
116
+ # else
117
+ # data = []
118
+ # end
119
+ # unless data.include?(key)
120
+ # @cache.write(@name + STORED_KEY, Marshal.dump(data << key))
121
+ # end
122
+ # end
123
+ # end
124
+ # end
125
+ # end
126
+
@@ -0,0 +1,18 @@
1
+ ActiveRecord::Schema.define(:version => 1) do
2
+ create_table "publications", :force => true do |t|
3
+ t.string "name", "subdomain"
4
+ end
5
+
6
+ create_table "articles", :force => true do |t|
7
+ t.references 'publication'
8
+ t.string 'title'
9
+ end
10
+ end
11
+
12
+ class Publication < ActiveRecord::Base
13
+ has_many :articles
14
+ end
15
+
16
+ class Article < ActiveRecord::Base
17
+ belongs_to :publication
18
+ end
@@ -0,0 +1,16 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ module CacheAdvance
4
+ before :each do
5
+ @cache_set = CacheSet.new
6
+ @mapper = Mapper.new(@cache_set)
7
+ end
8
+
9
+ describe 'Mapper' do
10
+ it 'should pass qualifiers directly to the cache set' do
11
+ proc = Proc.new { 2 }
12
+ @cache_set.should_receive(:add_qualifier).with(proc)
13
+ @mapper.qualifier(:testy, proc)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,41 @@
1
+ class MemCache
2
+ attr_reader :data
3
+
4
+ def initialize
5
+ @data = {}
6
+ end
7
+
8
+ def decr(key, amount = 1)
9
+ end
10
+
11
+ def get(key, raw = false)
12
+ return @data[key]
13
+ end
14
+
15
+ def get_multi(*keys)
16
+ end
17
+
18
+ def incr(key, amount = 1)
19
+ end
20
+
21
+ def set(key, value, expiry = 0, raw = false)
22
+ @data[key] = value
23
+ end
24
+
25
+ def add(key, value, expiry = 0, raw = false)
26
+ end
27
+
28
+ def delete(key, expiry = 0)
29
+ @data.delete(key)
30
+ end
31
+
32
+ def flush_all
33
+ end
34
+
35
+ def reset
36
+ @data = {}
37
+ end
38
+
39
+ def stats
40
+ end
41
+ end
@@ -0,0 +1,30 @@
1
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
2
+ $: << File.join(File.dirname(__FILE__))
3
+
4
+ require 'rubygems'
5
+ require 'ruby-debug'
6
+
7
+ gem 'sqlite3-ruby'
8
+ require 'spec'
9
+ require 'cache_advance'
10
+
11
+ require 'activerecord'
12
+
13
+ ActiveRecord::Base.establish_connection(
14
+ :adapter => 'sqlite3',
15
+ :dbfile => ':memory:'
16
+ )
17
+
18
+ require File.join(File.dirname(__FILE__), 'db', 'schema')
19
+ require File.join(File.dirname(__FILE__), 'mocks', 'memcache')
20
+
21
+ Spec::Runner.configure do |config|
22
+ # config.mock_with :rr
23
+ config.before :each do
24
+ @memcache = MemCache.new
25
+
26
+ CacheAdvance.define_caches(@memcache) do |cache_config|
27
+ cache_config.publication
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,10 @@
1
+ $: << File.join(File.dirname(__FILE__), '..', 'lib')
2
+ $: << File.join(File.dirname(__FILE__))
3
+
4
+ require 'test/unit'
5
+ require 'rubygems'
6
+ require 'ruby-debug'
7
+
8
+ require 'cache_advance'
9
+ require 'mocha'
10
+ require 'cache_mock'
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: aub-cache_advance
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Aubrey Holland
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-01-08 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A system for spiffy declarative caching
17
+ email: aubrey@patch.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.textile
24
+ files:
25
+ - README.textile
26
+ - Rakefile
27
+ - TODO.textile
28
+ - cache_advance-1.0.9.gem
29
+ - cache_advance.gemspec
30
+ - lib/cache_advance.rb
31
+ - lib/cache_advance/active_record_sweeper.rb
32
+ - lib/cache_advance/cache_set.rb
33
+ - lib/cache_advance/cached_key_list.rb
34
+ - lib/cache_advance/lock.rb
35
+ - lib/cache_advance/mapper.rb
36
+ - lib/cache_advance/named_cache.rb
37
+ - lib/cache_advance/named_cache_configuration.rb
38
+ - rails/init.rb
39
+ - test/active_record_sweeper_test.rb
40
+ - test/cache_mock.rb
41
+ - test/cache_set_test.rb
42
+ - test/mapper_test.rb
43
+ - test/named_cache_test.rb
44
+ - test/spec/db/schema.rb
45
+ - test/spec/mapper_spec.rb
46
+ - test/spec/mocks/memcache.rb
47
+ - test/spec/spec_helper.rb
48
+ - test/test_helper.rb
49
+ has_rdoc: true
50
+ homepage: http://github.com/aub/cache_advance
51
+ post_install_message:
52
+ rdoc_options:
53
+ - --line-numbers
54
+ - --inline-source
55
+ - --main
56
+ - README.textile
57
+ require_paths:
58
+ - lib
59
+ required_ruby_version: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ version: "0"
64
+ version:
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "0"
70
+ version:
71
+ requirements: []
72
+
73
+ rubyforge_project:
74
+ rubygems_version: 1.2.0
75
+ signing_key:
76
+ specification_version: 2
77
+ summary: A system for spiffy declarative caching
78
+ test_files:
79
+ - test/active_record_sweeper_test.rb
80
+ - test/cache_set_test.rb
81
+ - test/mapper_test.rb
82
+ - test/named_cache_test.rb