aub-cache_advance 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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