aub-cache_advance 1.0.9 → 1.1.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.
@@ -1,5 +1,5 @@
1
1
  Gem::Specification.new do |s|
2
- s.version = '1.0.9'
2
+ s.version = '1.1.0'
3
3
  s.date = %q{2009-01-08}
4
4
 
5
5
  s.name = %q{cache_advance}
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
7
7
  s.authors = ['Aubrey Holland']
8
8
  s.description = %q{A system for spiffy declarative caching}
9
9
  s.email = %q{aubrey@patch.com}
10
- s.files = %w(cache_advance.gemspec lib/cache_advance/active_record_sweeper.rb lib/cache_advance/cache_set.rb lib/cache_advance/mapper.rb lib/cache_advance/named_cache.rb lib/cache_advance/rails_cache.rb lib/cache_advance.rb rails/init.rb Rakefile README.textile 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/rails_cache_test.rb test/test_helper.rb TODO.textile)
10
+ s.files = %w(README.textile Rakefile TODO.textile 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
11
  s.homepage = %q{http://github.com/aub/cache_advance}
12
12
  s.require_paths = ['lib']
13
13
  s.rubygems_version = %q{1.2.0}
@@ -15,7 +15,7 @@ Gem::Specification.new do |s|
15
15
  s.has_rdoc = true
16
16
  s.extra_rdoc_files = ['README.textile']
17
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 test/rails_cache_test.rb)
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
19
 
20
20
  if s.respond_to? :specification_version then
21
21
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
data/lib/cache_advance.rb CHANGED
@@ -1,9 +1,23 @@
1
- require 'cache_advance/named_cache'
2
1
  require 'cache_advance/cache_set'
2
+ require 'cache_advance/cached_key_list'
3
3
  require 'cache_advance/mapper'
4
+ require 'cache_advance/named_cache'
5
+ require 'cache_advance/named_cache_configuration'
4
6
 
5
7
  module CacheAdvance
6
8
  class UnknownNamedCacheException < Exception; end
7
-
8
- Caches = CacheSet.new
9
+
10
+ class << self
11
+ attr_reader :cache_set
12
+ attr_accessor :caching_enabled
13
+ end
14
+
15
+ @cache_set = nil
16
+ @caching_enabled = true
17
+
18
+ def self.define_caches(store)
19
+ @cache_set = CacheSet.new(store)
20
+ yield Mapper.new(@cache_set)
21
+ @cache_set.setup_complete # This allows the cache set to finalize some of its configuration
22
+ end
9
23
  end
@@ -26,7 +26,7 @@ module CacheAdvance
26
26
 
27
27
  def expire_caches_for(object)
28
28
  class_symbol = object.class.name.underscore.to_sym
29
- CacheAdvance::Caches.expire_for_class(class_symbol)
29
+ CacheAdvance.cache_set.expire_for_class(class_symbol)
30
30
  end
31
31
  end
32
32
  end
@@ -4,16 +4,22 @@ module CacheAdvance
4
4
  attr_reader :qualifiers
5
5
  attr_reader :plugins
6
6
 
7
- def initialize
8
- @named_caches = {}
9
- @qualifiers = {}
10
- @plugins = []
7
+ def initialize(store)
8
+ @store, @named_caches, @qualifiers, @plugins = store, {}, {}, []
9
+ end
10
+
11
+ def setup_complete
12
+
11
13
  end
12
14
 
13
15
  def apply(cache_name, request, options, &block)
14
- named_cache = @named_caches[cache_name]
15
- raise UnknownNamedCacheException if named_cache.nil?
16
- named_cache.value_for(request, options, &block)
16
+ if CacheAdvance.caching_enabled
17
+ named_cache = @named_caches[cache_name]
18
+ raise UnknownNamedCacheException if named_cache.nil?
19
+ named_cache.value_for(request, options, &block)
20
+ else
21
+ block.call
22
+ end
17
23
  end
18
24
 
19
25
  def add_qualifier(name, proc)
@@ -25,7 +31,7 @@ module CacheAdvance
25
31
  end
26
32
 
27
33
  def add_named_cache(name, options)
28
- @named_caches[name] = NamedCache.new(name, options, self, @cache)
34
+ @named_caches[name] = NamedCache.new(name, options, self, @store)
29
35
  end
30
36
 
31
37
  def define_caches
@@ -42,10 +48,6 @@ module CacheAdvance
42
48
  end
43
49
  end
44
50
 
45
- def cache_type=(type)
46
- @cache = type.new
47
- end
48
-
49
51
  def sweeper_type=(type)
50
52
  @sweeper_type = type
51
53
  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
@@ -1,53 +1,37 @@
1
- module CacheAdvance
1
+ module CacheAdvance
2
2
  class NamedCache
3
- STORED_KEY = 'STORED_CACHES'
3
+
4
+ ENABLED_CHECK_INTERVAL = 60
4
5
 
5
- def initialize(name, params, cache_set, cache)
6
+ def initialize(name, params, cache_set, store)
6
7
  @name = name.to_s
7
8
  @params = params
8
9
  @cache_set = cache_set
9
- @cache = cache
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
10
14
  end
11
-
12
- def key_for(request, suffix='')
13
- key = @name.dup
14
- key << suffix.to_s
15
-
16
- qualifiers.each do |q|
17
- if (qualifier = @cache_set.qualifiers[q])
18
- this_one = qualifier.call(request)
19
- key << this_one.to_s unless this_one.nil?
20
- end
21
- end if qualifiers
22
- key
23
- end
24
-
15
+
25
16
  def value_for(request, options, &block)
17
+ return block.call unless enabled?
18
+
26
19
  key = key_for(request, options[:key])
27
-
28
- if (cache = @cache.read(key))
29
- each_plugin { |p| p.send('after_read', @name, key, request, cache) if p.respond_to?('after_read') }
30
- return cache
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
31
24
  end
32
-
25
+
33
26
  each_plugin { |p| p.send('before_render', @name, key, request) if p.respond_to?('before_render') }
34
27
  result = block.call
35
28
  each_plugin { |p| p.send('after_render', @name, key, request, result) if p.respond_to?('after_render') }
36
29
  each_plugin { |p| p.send('before_write', @name, key, request, result) if p.respond_to?('before_write') }
37
- @cache.write(key, result, rails_options)
38
- each_plugin { |p| p.send('after_write', @name, key, request, result) if p.respond_to?('after_write') }
39
-
40
- add_to_cached_keys_list(key)
41
-
30
+ write_to_store(key, result)
31
+ each_plugin { |p| p.send('after_write', @name, key, request, result) if p.respond_to?('after_write') }
42
32
  result
43
33
  end
44
-
45
- def rails_options
46
- options = {}
47
- options[:expires_in] = expiration_time if expiration_time
48
- options
49
- end
50
-
34
+
51
35
  def expire_for(type)
52
36
  if expiration_types.include?(type)
53
37
  expire_all
@@ -55,45 +39,82 @@ module CacheAdvance
55
39
  end
56
40
 
57
41
  def expire_all
58
- if (data = @cache.read(@name + STORED_KEY))
59
- data = Array(Marshal.load(data))
60
- data.each { |key| @cache.delete(key) }
61
- else
62
- @cache.delete(@name)
63
- end
42
+ delete_all_from_store
43
+ end
44
+
45
+ def all_cached_keys
46
+ @cached_key_list.all_keys
64
47
  end
65
48
 
66
49
  def expiration_types
67
50
  Array(@params[:expiration_types])
68
51
  end
69
52
 
70
- def expiration_time
71
- @params[:expiration_time]
53
+ def title
54
+ @params[:title] || @name.to_s
72
55
  end
73
56
 
74
- def qualifiers
75
- Array(@params[:qualifiers])
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
76
68
  end
77
69
 
78
70
  protected
79
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
+
80
93
  def each_plugin
81
94
  @cache_set.plugins.each do |p|
82
95
  yield p
83
96
  end
84
97
  end
85
98
 
86
- def add_to_cached_keys_list(key)
87
- unless expiration_types.blank? || key == @name
88
- if (data = @cache.read(@name + STORED_KEY))
89
- data = Array(Marshal.load(data))
90
- else
91
- data = []
92
- end
93
- unless data.include?(key)
94
- @cache.write(@name + STORED_KEY, Marshal.dump(data << key))
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
95
103
  end
96
- 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])
97
118
  end
98
119
  end
99
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
data/rails/init.rb CHANGED
@@ -1,20 +1,20 @@
1
1
  require 'cache_advance'
2
2
  require 'cache_advance/active_record_sweeper'
3
- require 'cache_advance/rails_cache'
4
-
5
- # Setup the sweeper and cache types as appropriate for Rails.
6
- CacheAdvance::Caches.sweeper_type = CacheAdvance::ActiveRecordSweeper
7
- CacheAdvance::Caches.cache_type = CacheAdvance::RailsCache
8
3
 
9
4
  require "#{RAILS_ROOT}/config/caches"
10
5
  require 'dispatcher'
11
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
12
  # This is the helper method that can be used in rails views/controllers/helpers.
13
13
  # If caching is disabled, just make it yield the results of the block.
14
14
  if config.action_controller.perform_caching
15
15
  ActionController::Base.helper do
16
16
  def cache_it(cache, options={}, &block)
17
- CacheAdvance::Caches.apply(cache, request, options) do
17
+ CacheAdvance.cache_set.apply(cache, request, options) do
18
18
  capture(&block)
19
19
  end
20
20
  end
@@ -26,7 +26,7 @@ else
26
26
  end
27
27
  end
28
28
  end
29
-
29
+
30
30
  ActionMailer::Base.helper do
31
31
  def cache_it(cache, options={}, &block)
32
32
  capture(&block)
@@ -37,7 +37,7 @@ end
37
37
  config.after_initialize do
38
38
  if config.action_controller.perform_caching
39
39
  # This hooks the sweepers into the observer system and adds it to the list.
40
- CacheAdvance::Caches.create_sweepers
40
+ CacheAdvance.cache_set.create_sweepers
41
41
  ActiveRecord::Base.observers << CacheAdvance::ActiveRecordSweeper
42
42
 
43
43
  # In development mode, the models we observe get reloaded with each request. Using
@@ -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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: aub-cache_advance
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.9
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aubrey Holland
@@ -22,24 +22,29 @@ extensions: []
22
22
  extra_rdoc_files:
23
23
  - README.textile
24
24
  files:
25
+ - README.textile
26
+ - Rakefile
27
+ - TODO.textile
25
28
  - cache_advance.gemspec
29
+ - lib/cache_advance.rb
26
30
  - lib/cache_advance/active_record_sweeper.rb
27
31
  - lib/cache_advance/cache_set.rb
32
+ - lib/cache_advance/cached_key_list.rb
33
+ - lib/cache_advance/lock.rb
28
34
  - lib/cache_advance/mapper.rb
29
35
  - lib/cache_advance/named_cache.rb
30
- - lib/cache_advance/rails_cache.rb
31
- - lib/cache_advance.rb
36
+ - lib/cache_advance/named_cache_configuration.rb
32
37
  - rails/init.rb
33
- - Rakefile
34
- - README.textile
35
38
  - test/active_record_sweeper_test.rb
36
39
  - test/cache_mock.rb
37
40
  - test/cache_set_test.rb
38
41
  - test/mapper_test.rb
39
42
  - test/named_cache_test.rb
40
- - test/rails_cache_test.rb
43
+ - test/spec/db/schema.rb
44
+ - test/spec/mapper_spec.rb
45
+ - test/spec/mocks/memcache.rb
46
+ - test/spec/spec_helper.rb
41
47
  - test/test_helper.rb
42
- - TODO.textile
43
48
  has_rdoc: true
44
49
  homepage: http://github.com/aub/cache_advance
45
50
  post_install_message:
@@ -74,4 +79,3 @@ test_files:
74
79
  - test/cache_set_test.rb
75
80
  - test/mapper_test.rb
76
81
  - test/named_cache_test.rb
77
- - test/rails_cache_test.rb
@@ -1,15 +0,0 @@
1
- module CacheAdvance
2
- class RailsCache
3
- def read(key)
4
- Rails.cache.read(key)
5
- end
6
-
7
- def write(key, value, options={})
8
- Rails.cache.write(key, value, options)
9
- end
10
-
11
- def delete(key)
12
- Rails.cache.delete(key)
13
- end
14
- end
15
- end
@@ -1,7 +0,0 @@
1
- require File.dirname(__FILE__) + '/test_helper.rb'
2
-
3
- class RailsCacheTest < Test::Unit::TestCase
4
- def test_empty
5
- assert true
6
- end
7
- end