aub-cache_advance 1.0.9 → 1.1.0

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