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