cache_fu 0.1.2
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/LICENSE +18 -0
- data/README +18 -0
- data/defaults/extensions.rb.default +40 -0
- data/defaults/memcached.yml.default +34 -0
- data/defaults/memcached_ctl.default +81 -0
- data/lib/acts_as_cached/benchmarking.rb +82 -0
- data/lib/acts_as_cached/cache_methods.rb +307 -0
- data/lib/acts_as_cached/config.rb +111 -0
- data/lib/acts_as_cached/disabled.rb +30 -0
- data/lib/acts_as_cached/fragment_cache.rb +102 -0
- data/lib/acts_as_cached/local_cache.rb +44 -0
- data/lib/acts_as_cached/railtie.rb +13 -0
- data/lib/acts_as_cached/recipes.rb +8 -0
- data/lib/cache_fu.rb +62 -0
- data/lib/cache_fu.rb~ +61 -0
- data/lib/cache_fu.rb~5453d1247ce57bb1ea5ad70936b97707d5dbdab0 +1 -0
- data/lib/cache_fu.rb~5453d1247ce57bb1ea5ad70936b97707d5dbdab0_0 +1 -0
- data/lib/tasks/memcached.rake +59 -0
- data/rails/init.rb +34 -0
- data/test/benchmarking_test.rb +36 -0
- data/test/cache_test.rb +281 -0
- data/test/config_test.rb +128 -0
- data/test/disabled_test.rb +40 -0
- data/test/extensions_test.rb +33 -0
- data/test/fragment_cache_test.rb +250 -0
- data/test/helper.rb +215 -0
- data/test/local_cache_test.rb +43 -0
- data/test/sti_test.rb +36 -0
- metadata +103 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
module ActsAsCached
|
2
|
+
module Disabled
|
3
|
+
def fetch_cache_with_disabled(*args)
|
4
|
+
nil
|
5
|
+
end
|
6
|
+
|
7
|
+
def set_cache_with_disabled(*args)
|
8
|
+
args[1]
|
9
|
+
end
|
10
|
+
|
11
|
+
def expire_cache_with_disabled(*args)
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.add_to(klass)
|
16
|
+
return if klass.respond_to? :fetch_cache_with_disabled
|
17
|
+
klass.extend self
|
18
|
+
|
19
|
+
class << klass
|
20
|
+
alias_method_chain :fetch_cache, :disabled
|
21
|
+
alias_method_chain :set_cache, :disabled
|
22
|
+
alias_method_chain :expire_cache, :disabled
|
23
|
+
end
|
24
|
+
|
25
|
+
class << CACHE
|
26
|
+
include FragmentCache::DisabledExtensions
|
27
|
+
end if ActsAsCached.config[:fragments] && defined?(FragmentCache::DisabledExtensions)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module ActsAsCached
|
2
|
+
module FragmentCache
|
3
|
+
def self.setup!
|
4
|
+
class << CACHE
|
5
|
+
include Extensions
|
6
|
+
end
|
7
|
+
|
8
|
+
setup_fragment_cache_cache
|
9
|
+
setup_rails_for_memcache_fragments
|
10
|
+
setup_rails_for_action_cache_options
|
11
|
+
end
|
12
|
+
|
13
|
+
# add :ttl option to cache helper and set cache store memcache object
|
14
|
+
def self.setup_rails_for_memcache_fragments
|
15
|
+
::ActionController::Base.cache_store = CACHE
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.setup_fragment_cache_cache
|
19
|
+
Object.const_set(:FragmentCacheCache, Class.new { acts_as_cached :store => CACHE })
|
20
|
+
end
|
21
|
+
|
22
|
+
# add :ttl option to caches_action on the per action level by passing in a hash instead of an array
|
23
|
+
#
|
24
|
+
# Examples:
|
25
|
+
# caches_action :index # will use the default ttl from your memcache.yml, or 25 minutes
|
26
|
+
# caches_action :index => { :ttl => 5.minutes } # cache index action with 5 minute ttl
|
27
|
+
# caches_action :page, :feed, :index => { :ttl => 2.hours } # cache index action with 2 hours ttl, all others use default
|
28
|
+
#
|
29
|
+
def self.setup_rails_for_action_cache_options
|
30
|
+
::ActionController::Caching::Actions::ActionCacheFilter.class_eval do
|
31
|
+
# convert all actions into a hash keyed by action named, with a value of a ttl hash (to match other cache APIs)
|
32
|
+
def initialize(*actions, &block)
|
33
|
+
@options = actions.extract_options!
|
34
|
+
@actions = actions.inject(@options.except(:cache_path)) do |hsh, action|
|
35
|
+
action.is_a?(Hash) ? hsh.merge(action) : hsh.merge(action => { :ttl => nil })
|
36
|
+
end
|
37
|
+
@options.slice!(:cache_path)
|
38
|
+
end
|
39
|
+
|
40
|
+
# override to skip caching/rendering on evaluated if option
|
41
|
+
def before(controller)
|
42
|
+
return unless @actions.include?(controller.action_name.intern)
|
43
|
+
|
44
|
+
if @options
|
45
|
+
action_cache_path = ActionController::Caching::Actions::ActionCachePath.new(controller, path_options_for(controller, @options))
|
46
|
+
end
|
47
|
+
|
48
|
+
# should probably be like ActiveRecord::Validations.evaluate_condition. color me lazy.
|
49
|
+
if conditional = @actions[controller.action_name.intern][:if]
|
50
|
+
conditional = conditional.respond_to?(:call) ? conditional.call(controller) : controller.send(conditional)
|
51
|
+
end
|
52
|
+
@actions.delete(controller.action_name.intern) if conditional == false
|
53
|
+
|
54
|
+
cache = controller.read_fragment(action_cache_path.path)
|
55
|
+
if cache && (conditional || conditional.nil?)
|
56
|
+
controller.rendered_action_cache = true
|
57
|
+
if method(:set_content_type!).arity == 2
|
58
|
+
set_content_type!(controller, action_cache_path.extension)
|
59
|
+
else
|
60
|
+
set_content_type!(action_cache_path)
|
61
|
+
end
|
62
|
+
controller.send(:render, :text => cache)
|
63
|
+
false
|
64
|
+
else
|
65
|
+
# 1.2.x compatibility
|
66
|
+
controller.action_cache_path = action_cache_path if controller.respond_to? :action_cache_path
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# override to pass along the ttl hash
|
71
|
+
def after(controller)
|
72
|
+
return if !@actions.include?(controller.action_name.intern) || controller.rendered_action_cache
|
73
|
+
# 1.2.x compatibility
|
74
|
+
path = controller.respond_to?(:action_cache_path) ? controller.action_cache_path.path : ActionController::Caching::Actions::ActionCachePath.path_for(controller)
|
75
|
+
controller.write_fragment(path, controller.response.body, action_ttl(controller))
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def action_ttl(controller)
|
80
|
+
@actions[controller.action_name.intern]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
module Extensions
|
86
|
+
def read(*args)
|
87
|
+
return if ActsAsCached.config[:skip_gets]
|
88
|
+
FragmentCacheCache.cache_store(:get, args.first)
|
89
|
+
end
|
90
|
+
|
91
|
+
def write(name, content, options = {})
|
92
|
+
ttl = (options.is_a?(Hash) ? options[:ttl] : nil) || ActsAsCached.config[:ttl] || 25.minutes
|
93
|
+
FragmentCacheCache.cache_store(:set, name, content, ttl)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
module DisabledExtensions
|
98
|
+
def read(*args) nil end
|
99
|
+
def write(*args) "" end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module ActsAsCached
|
2
|
+
module LocalCache
|
3
|
+
@@local_cache = {}
|
4
|
+
mattr_accessor :local_cache
|
5
|
+
|
6
|
+
def fetch_cache_with_local_cache(*args)
|
7
|
+
@@local_cache[cache_key(args.first)] ||= fetch_cache_without_local_cache(*args)
|
8
|
+
end
|
9
|
+
|
10
|
+
def set_cache_with_local_cache(*args)
|
11
|
+
@@local_cache[cache_key(args.first)] = set_cache_without_local_cache(*args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def expire_cache_with_local_cache(*args)
|
15
|
+
@@local_cache.delete(cache_key(args.first))
|
16
|
+
expire_cache_without_local_cache(*args)
|
17
|
+
end
|
18
|
+
alias :clear_cache_with_local_cache :expire_cache_with_local_cache
|
19
|
+
|
20
|
+
def cached_with_local_cache?(*args)
|
21
|
+
!!@@local_cache[cache_key(args.first)] || cached_without_local_cache?(*args)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.add_to(klass)
|
25
|
+
return if klass.ancestors.include? self
|
26
|
+
klass.send :include, self
|
27
|
+
|
28
|
+
klass.class_eval do
|
29
|
+
%w( fetch_cache set_cache expire_cache clear_cache cached? ).each do |target|
|
30
|
+
alias_method_chain target, :local_cache
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module ActionController
|
38
|
+
class Base
|
39
|
+
def local_cache_for_request
|
40
|
+
ActsAsCached::LocalCache.add_to ActsAsCached::ClassMethods
|
41
|
+
ActsAsCached::LocalCache.local_cache = {}
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
Capistrano.configuration(:must_exist).load do
|
2
|
+
%w(start stop restart kill status).each do |cmd|
|
3
|
+
desc "#{cmd} your memcached servers"
|
4
|
+
task "memcached_#{cmd}".to_sym, :roles => :app do
|
5
|
+
run "RAILS_ENV=production #{ruby} #{current_path}/script/memcached_ctl #{cmd}"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
data/lib/cache_fu.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/acts_as_cached/config'
|
2
|
+
require File.dirname(__FILE__) + '/acts_as_cached/cache_methods'
|
3
|
+
require File.dirname(__FILE__) + '/acts_as_cached/fragment_cache'
|
4
|
+
require File.dirname(__FILE__) + '/acts_as_cached/benchmarking'
|
5
|
+
require File.dirname(__FILE__) + '/acts_as_cached/disabled'
|
6
|
+
require File.dirname(__FILE__) + '/acts_as_cached/local_cache'
|
7
|
+
require File.dirname(__FILE__) + '/acts_as_cached/railtie' if defined?(Rails::Railtie)
|
8
|
+
|
9
|
+
module ActsAsCached
|
10
|
+
@@config = {}
|
11
|
+
mattr_reader :config
|
12
|
+
|
13
|
+
def self.config=(options)
|
14
|
+
@@config = Config.setup options
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.skip_cache_gets=(boolean)
|
18
|
+
ActsAsCached.config[:skip_gets] = boolean
|
19
|
+
end
|
20
|
+
|
21
|
+
module Mixin
|
22
|
+
def acts_as_cached(options = {})
|
23
|
+
extend ClassMethods
|
24
|
+
include InstanceMethods
|
25
|
+
|
26
|
+
extend Extensions::ClassMethods if defined? Extensions::ClassMethods
|
27
|
+
include Extensions::InstanceMethods if defined? Extensions::InstanceMethods
|
28
|
+
|
29
|
+
options.symbolize_keys!
|
30
|
+
|
31
|
+
options[:store] ||= ActsAsCached.config[:store]
|
32
|
+
options[:ttl] ||= ActsAsCached.config[:ttl]
|
33
|
+
|
34
|
+
# convert the find_by shorthand
|
35
|
+
if find_by = options.delete(:find_by)
|
36
|
+
options[:finder] = "find_by_#{find_by}".to_sym
|
37
|
+
options[:cache_id] = find_by
|
38
|
+
end
|
39
|
+
|
40
|
+
cache_config.replace options.reject { |key,| not Config.valued_keys.include? key }
|
41
|
+
cache_options.replace options.reject { |key,| Config.valued_keys.include? key }
|
42
|
+
|
43
|
+
Disabled.add_to self and return if ActsAsCached.config[:disabled]
|
44
|
+
Benchmarking.add_to self if ActsAsCached.config[:benchmarking]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class CacheException < StandardError; end
|
49
|
+
class NoCacheStore < CacheException; end
|
50
|
+
class NoGetMulti < CacheException; end
|
51
|
+
end
|
52
|
+
|
53
|
+
Rails::Application.initializer("cache_fu") do
|
54
|
+
Object.send :include, ActsAsCached::Mixin
|
55
|
+
unless File.exists?(config_file = Rails.root.join('config', 'memcached.yml'))
|
56
|
+
error = "No config file found. If you used plugin version make sure you used `script/plugin install' or `rake memcached:cache_fu_install' if gem version and have memcached.yml in your config directory."
|
57
|
+
puts error
|
58
|
+
logger.error error
|
59
|
+
exit!
|
60
|
+
end
|
61
|
+
ActsAsCached.config = YAML.load(ERB.new(IO.read(config_file)).result)
|
62
|
+
end
|
data/lib/cache_fu.rb~
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/acts_as_cached/config'
|
2
|
+
require File.dirname(__FILE__) + '/acts_as_cached/cache_methods'
|
3
|
+
require File.dirname(__FILE__) + '/acts_as_cached/fragment_cache'
|
4
|
+
require File.dirname(__FILE__) + '/acts_as_cached/benchmarking'
|
5
|
+
require File.dirname(__FILE__) + '/acts_as_cached/disabled'
|
6
|
+
require File.dirname(__FILE__) + '/acts_as_cached/local_cache'
|
7
|
+
require File.dirname(__FILE__) + '/acts_as_cached/railtie' if defined?(Rails::Railtie)
|
8
|
+
|
9
|
+
module ActsAsCached
|
10
|
+
@@config = {}
|
11
|
+
mattr_reader :config
|
12
|
+
|
13
|
+
def self.config=(options)
|
14
|
+
@@config = Config.setup options
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.skip_cache_gets=(boolean)
|
18
|
+
ActsAsCached.config[:skip_gets] = boolean
|
19
|
+
end
|
20
|
+
|
21
|
+
module Mixin
|
22
|
+
def acts_as_cached(options = {})
|
23
|
+
extend ClassMethods
|
24
|
+
include InstanceMethods
|
25
|
+
|
26
|
+
extend Extensions::ClassMethods if defined? Extensions::ClassMethods
|
27
|
+
include Extensions::InstanceMethods if defined? Extensions::InstanceMethods
|
28
|
+
|
29
|
+
options.symbolize_keys!
|
30
|
+
|
31
|
+
options[:store] ||= ActsAsCached.config[:store]
|
32
|
+
options[:ttl] ||= ActsAsCached.config[:ttl]
|
33
|
+
|
34
|
+
# convert the find_by shorthand
|
35
|
+
if find_by = options.delete(:find_by)
|
36
|
+
options[:finder] = "find_by_#{find_by}".to_sym
|
37
|
+
options[:cache_id] = find_by
|
38
|
+
end
|
39
|
+
|
40
|
+
cache_config.replace options.reject { |key,| not Config.valued_keys.include? key }
|
41
|
+
cache_options.replace options.reject { |key,| Config.valued_keys.include? key }
|
42
|
+
|
43
|
+
Disabled.add_to self and return if ActsAsCached.config[:disabled]
|
44
|
+
Benchmarking.add_to self if ActsAsCached.config[:benchmarking]
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
class CacheException < StandardError; end
|
49
|
+
class NoCacheStore < CacheException; end
|
50
|
+
class NoGetMulti < CacheException; end
|
51
|
+
end
|
52
|
+
|
53
|
+
Object.send :include, ActsAsCached::Mixin
|
54
|
+
unless File.exists?(config_file = Rails.root.join('config', 'memcached.yml'))
|
55
|
+
error = "No config file found. If you used plugin version make sure you used `script/plugin install' or `rake memcached:cache_fu_install' if gem version and have memcached.yml in your config directory."
|
56
|
+
puts error
|
57
|
+
logger.error error
|
58
|
+
exit!
|
59
|
+
end
|
60
|
+
|
61
|
+
ActsAsCached.config = YAML.load(ERB.new(IO.read(config_file)).result)
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/acts_as_cached'
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/acts_as_cached'
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'erb'
|
3
|
+
|
4
|
+
namespace :memcached do
|
5
|
+
desc "Start memcached locally"
|
6
|
+
task :start do
|
7
|
+
ActsAsCached::Config.memcached ActsAsCached::Config.config_args
|
8
|
+
puts "memcached started"
|
9
|
+
end
|
10
|
+
|
11
|
+
desc "Restart memcached locally"
|
12
|
+
task :restart do
|
13
|
+
Rake::Task['memcached:stop'].invoke
|
14
|
+
Rake::Task['memcached:start'].invoke
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Stop memcached locally"
|
18
|
+
task :stop do
|
19
|
+
`killall memcached`
|
20
|
+
puts "memcached killed"
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "Adds the cache_fu config file"
|
24
|
+
task :cache_fu_install do
|
25
|
+
defaults_dir = File.join(File.dirname(__FILE__), '../../defaults')
|
26
|
+
|
27
|
+
config_yaml = File.join('.', 'config', 'memcached.yml')
|
28
|
+
default_yaml = File.join(defaults_dir, 'memcached.yml.default')
|
29
|
+
FileUtils.cp(default_yaml, config_yaml)
|
30
|
+
|
31
|
+
memcached_ctl = File.join('.', 'script', 'memcached_ctl')
|
32
|
+
default_ctl = File.join(defaults_dir, 'memcached_ctl.default')
|
33
|
+
FileUtils.cp(default_ctl, memcached_ctl)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
module ActsAsCached
|
38
|
+
module Config
|
39
|
+
def self.config
|
40
|
+
return @config if @config
|
41
|
+
config = YAML.load(ERB.new(IO.read(Rails.root.to_s + '/config/memcached.yml')).result)
|
42
|
+
@config = config['defaults'].merge(config['development'])
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.config_args
|
46
|
+
args = {
|
47
|
+
'-p' => Array(config['servers']).first.split(':').last,
|
48
|
+
'-c' => config['c_threshold'],
|
49
|
+
'-m' => config['memory'],
|
50
|
+
'-d' => ''
|
51
|
+
}
|
52
|
+
args.to_a * ' '
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.memcached(*args)
|
56
|
+
`/usr/bin/env memcached #{args * ' '}`
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
begin
|
2
|
+
require 'memcache'
|
3
|
+
rescue LoadError
|
4
|
+
end
|
5
|
+
|
6
|
+
begin
|
7
|
+
require 'memcached'
|
8
|
+
rescue LoadError
|
9
|
+
end
|
10
|
+
|
11
|
+
begin
|
12
|
+
require 'mem_cache_with_consistent_hashing'
|
13
|
+
rescue LoadError
|
14
|
+
end
|
15
|
+
|
16
|
+
puts "=> You should be using the `dalli' or `memcache-client' gem. You're using RubyMemcache!" if Object.const_defined?(:RubyMemcache)
|
17
|
+
|
18
|
+
require 'cache_fu'
|
19
|
+
|
20
|
+
Object.send :include, ActsAsCached::Mixin
|
21
|
+
|
22
|
+
unless File.exists?(config_file = Rails.root.join('config', 'memcached.yml'))
|
23
|
+
error = "No config file found. Make sure you used `script/plugin install' and have memcached.yml in your config directory."
|
24
|
+
puts error
|
25
|
+
logger.error error
|
26
|
+
exit!
|
27
|
+
end
|
28
|
+
|
29
|
+
ActsAsCached.config = YAML.load(ERB.new(IO.read(config_file)).result)
|
30
|
+
|
31
|
+
begin
|
32
|
+
require 'extensions'
|
33
|
+
rescue LoadError
|
34
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'helper')
|
2
|
+
|
3
|
+
ActsAsCached.config.clear
|
4
|
+
config = YAML.load_file(File.join(File.dirname(__FILE__), '../defaults/memcached.yml.default'))
|
5
|
+
config['test'] = config['development']
|
6
|
+
ActsAsCached.config = config
|
7
|
+
Story.send :acts_as_cached
|
8
|
+
|
9
|
+
context "When benchmarking is enabled" do
|
10
|
+
specify "ActionController::Base should respond to rendering_runtime_with_memcache" do
|
11
|
+
ActionController::Base.new.should.respond_to :rendering_runtime_with_memcache
|
12
|
+
end
|
13
|
+
|
14
|
+
specify "cachable Ruby classes should be respond to :logger" do
|
15
|
+
Story.should.respond_to :logger
|
16
|
+
end
|
17
|
+
|
18
|
+
specify "a cached object should gain a fetch_cache with and without benchmarking methods" do
|
19
|
+
Story.should.respond_to :fetch_cache_with_benchmarking
|
20
|
+
Story.should.respond_to :fetch_cache_without_benchmarking
|
21
|
+
end
|
22
|
+
|
23
|
+
specify "cache_benchmark should yield and time any action" do
|
24
|
+
ActsAsCached::Benchmarking.cache_runtime.should.equal 0.0
|
25
|
+
|
26
|
+
level = Class.new { |k| def k.method_missing(*args) true end }
|
27
|
+
Story.stubs(:logger).returns(level)
|
28
|
+
|
29
|
+
Story.cache_benchmark("Seriously, nothing.", true) {
|
30
|
+
sleep 0.01
|
31
|
+
"Nothing."
|
32
|
+
}.should.equal "Nothing."
|
33
|
+
|
34
|
+
ActsAsCached::Benchmarking.cache_runtime.should.be > 0.0
|
35
|
+
end
|
36
|
+
end
|