cache_fu 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,13 @@
1
+ require 'cache_fu'
2
+ require 'rails'
3
+
4
+ module ActsAsCached
5
+ class Railtie < Rails::Railtie
6
+ initializer 'cache_fu:extends' do
7
+ end
8
+
9
+ rake_tasks do
10
+ load 'tasks/memcached.rb'
11
+ end
12
+ end
13
+ 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