winton-background_cache 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Winton Welsh
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,66 @@
1
+ BackgroundCache
2
+ ===============
3
+
4
+ Use a rake task to expire fragments, with or without a layout. Uses Rails and cache_fu.
5
+
6
+ Dynamic Configuration
7
+ ---------------------
8
+
9
+ Create *lib/background\_cache\_config.rb*:
10
+
11
+ <pre>
12
+ BackgroundCache::Config.new do |config|
13
+
14
+ # Configure a background cache in one call
15
+ Tag::League.find(:all).each do |tag|
16
+ config.cache(
17
+ # Route params
18
+ :controller => 'sections',
19
+ :action => 'teams',
20
+ :tag => tag.permalink,
21
+ # Background cache options
22
+ :every => 1.hour,
23
+ :layout => false,
24
+ :only => "sections_teams_#{tag.permalink}"
25
+ )
26
+ end
27
+
28
+ # Group configure using block methods
29
+ config.every(1.hour).layout(false).only("sections_teams_#{tag.permalink}") do
30
+ Tag::League.find(:all).each do |tag|
31
+ config.cache(
32
+ :controller => 'sections',
33
+ :action => 'teams',
34
+ :tag => tag.permalink
35
+ )
36
+ end
37
+ end
38
+
39
+ # Or use a mix of the two
40
+ end
41
+ </pre>
42
+
43
+ The :only and :except options can be fragment ids or arrays of fragment ids.
44
+
45
+ If no fragment is specified, all of the action's caches will regenerate.
46
+
47
+ This configuration reloads every time the rake task runs. New records get background cached.
48
+
49
+ Rake task
50
+ ---------
51
+
52
+ Add <code>rake background_cache</code> to cron. Set the job's duration the same as your shortest cache.
53
+
54
+ What does the rake task do?
55
+
56
+ * Adds a security key to memcache that is shared by the app and rake task
57
+ * Sends a request to the app to reload its BackgroundCache config
58
+ * If time for a cache to expire, the task sends an expire request to the action
59
+ * BackgroundCache detects the request within the app and modifies the layout or expiry as configured
60
+
61
+ Memcached is employed to track the expire time of each background cache. As a side benefit, if memcached restarts, the rake task knows to generate all caches.
62
+
63
+ Todo
64
+ ----
65
+
66
+ * Specs
@@ -0,0 +1,31 @@
1
+ require 'rake'
2
+
3
+ task :default => 'background_cache.gemspec'
4
+
5
+ file 'background_cache.gemspec' => FileList['{lib,spec}/**','Rakefile'] do |f|
6
+ # read spec file and split out manifest section
7
+ spec = File.read(f.name)
8
+ parts = spec.split(" # = MANIFEST =\n")
9
+ fail 'bad spec' if parts.length != 3
10
+ # determine file list from git ls-files
11
+ files = `git ls-files`.
12
+ split("\n").
13
+ sort.
14
+ reject{ |file| file =~ /^\./ }.
15
+ reject { |file| file =~ /^doc/ }.
16
+ map{ |file| " #{file}" }.
17
+ join("\n")
18
+ # piece file back together and write...
19
+ parts[1] = " s.files = %w[\n#{files}\n ]\n"
20
+ spec = parts.join(" # = MANIFEST =\n")
21
+ File.open(f.name, 'w') { |io| io.write(spec) }
22
+ puts "Updated #{f.name}"
23
+ end
24
+
25
+ # sudo rake install
26
+ task :install do
27
+ `sudo gem uninstall background_cache -x`
28
+ `gem build background_cache.gemspec`
29
+ `sudo gem install background_cache*.gem`
30
+ `rm background_cache*.gem`
31
+ end
@@ -0,0 +1,32 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'background_cache'
3
+ s.version = '0.1.1'
4
+ s.date = '2008-03-11'
5
+
6
+ s.summary = "Generate caches before your users do (with Rails and cache_fu)"
7
+ s.description = "Generate caches before your users do (with Rails and cache_fu)"
8
+
9
+ s.author = 'Winton Welsh'
10
+ s.email = 'mail@wintoni.us'
11
+ s.homepage = 'http://github.com/winton/background_cache'
12
+
13
+ s.has_rdoc = false
14
+
15
+ # = MANIFEST =
16
+ s.files = %w[
17
+ MIT-LICENSE
18
+ README.markdown
19
+ Rakefile
20
+ background_cache.gemspec
21
+ changelog.markdown
22
+ init.rb
23
+ lib/background_cache.rb
24
+ lib/background_cache/config.rb
25
+ lib/background_cache/controller.rb
26
+ lib/background_cache/helper.rb
27
+ spec/spec.opts
28
+ spec/spec_helper.rb
29
+ tasks/background_cache.rake
30
+ ]
31
+ # = MANIFEST =
32
+ end
@@ -0,0 +1,4 @@
1
+ Version 0.1.0
2
+ =============
3
+
4
+ * Initial Creation
data/init.rb ADDED
@@ -0,0 +1,5 @@
1
+ $:.unshift File.dirname(__FILE__) + "/lib"
2
+ require 'background_cache'
3
+
4
+ ActionController::Base.send(:include, BackgroundCache::Controller)
5
+ ActionController::Base.helper(BackgroundCache::Helper)
@@ -0,0 +1,2 @@
1
+ require File.dirname(__FILE__) + "/background_cache/config.rb"
2
+ require File.dirname(__FILE__) + "/background_cache/controller.rb"
@@ -0,0 +1,118 @@
1
+ require 'digest/sha2'
2
+
3
+ module BackgroundCache
4
+ class Config
5
+ def initialize(&block)
6
+ @@caches = []
7
+ yield self
8
+ end
9
+ def cache(options)
10
+ # Convert keys to strings for param matching
11
+ options.dup.each do |key, value|
12
+ options[key.to_s] = value
13
+ options.delete(key)
14
+ end
15
+ # Method-style config
16
+ options = @options.merge(options)
17
+ # Store the cache options
18
+ @@caches.push({
19
+ :except => options.delete('except'),
20
+ :every => options.delete('every') || 1.hour,
21
+ :layout => options.delete('layout'),
22
+ :only => options.delete('only'),
23
+ :params => options
24
+ })
25
+ end
26
+ def except(value, &block)
27
+ set_option(:except, value, &block)
28
+ end
29
+ def every(value, &block)
30
+ set_option(:every, value, &block)
31
+ end
32
+ def layout(value, &block)
33
+ set_option(:layout, value, &block)
34
+ end
35
+ def only(value, &block)
36
+ set_option(:only, value, &block)
37
+ end
38
+ def set_option(key, value, &block)
39
+ @options ||= {}
40
+ @options[key.to_s] = value
41
+ if block
42
+ yield
43
+ @options = {}
44
+ end
45
+ self
46
+ end
47
+ # Find cache config from params
48
+ def self.from_params(params)
49
+ from_params_and_fragment(params)
50
+ end
51
+ def self.from_params_and_fragment(params, fragment=nil)
52
+ unless @@caches.empty?
53
+ @@caches.select { |item|
54
+ # Basic params match (action, controller, etc)
55
+ item[:params] == params &&
56
+ (
57
+ # No fragment specified
58
+ fragment.nil? ||
59
+ (
60
+ (
61
+ # :only not defined
62
+ !item[:only] ||
63
+ # :only matches fragment
64
+ item[:only] == fragment ||
65
+ (
66
+ # :only is an array
67
+ items[:only].respond_to?(:index) &&
68
+ # :only includes matching fragment
69
+ items[:only].include?(fragment)
70
+ )
71
+ ) &&
72
+ (
73
+ # :except not defined
74
+ !item[:except] ||
75
+ # :except not explicitly named
76
+ item[:except] != fragment ||
77
+ (
78
+ # :except is an array
79
+ items[:except].respond_to?(:index) &&
80
+ # :except does not include matching fragment
81
+ !items[:except].include?(fragment)
82
+ )
83
+ )
84
+ )
85
+ )
86
+ }[0]
87
+ end
88
+ end
89
+ def self.caches
90
+ @@caches
91
+ end
92
+ def self.key
93
+ Digest::SHA256.hexdigest("--#{Time.now}--#{rand}--")
94
+ end
95
+ def self.load!
96
+ load RAILS_ROOT + "/lib/background_cache_config.rb"
97
+ end
98
+ def self.unload!
99
+ @@caches = []
100
+ end
101
+ # Unique cache id for storing last expired time
102
+ def self.unique_cache_id(cache)
103
+ id = []
104
+ join = lambda do |k, v|
105
+ id << (k.nil? || v.nil? ?
106
+ nil : [ k, v ].collect { |kv| kv.to_s.gsub(/\W/, '_') }.join('-')
107
+ )
108
+ end
109
+ cache[:params].each do |key, value|
110
+ join.call(key, value)
111
+ end
112
+ cache.each do |key, value|
113
+ join.call(key, value) unless key == :params
114
+ end
115
+ 'background_cache/' + id.compact.join('/')
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,42 @@
1
+ module BackgroundCache
2
+ module Controller
3
+ def self.included(base)
4
+ base.around_filter BackgroundCacheFilter.new
5
+ end
6
+
7
+ private
8
+ class BackgroundCacheFilter
9
+ def before(controller)
10
+ # Secure filters
11
+ key = CACHE['background_cache/key']
12
+ # Load the background cache config (stay dynamic)
13
+ if controller.params[:background_cache_load] == key
14
+ BackgroundCache::Config.load!
15
+ # Unload the background cache config
16
+ elsif controller.params[:background_cache_unload] == key
17
+ BackgroundCache::Config.unload!
18
+ # Reload the cache for an entire page, action, or fragment
19
+ elsif controller.params[:background_cache] == key
20
+ @cache = BackgroundCache::Config.from_params(params)
21
+ # Store current layout, then disable it
22
+ if @cache && @cache[:layout] == false
23
+ @layout = controller.active_layout
24
+ controller.class.layout(false)
25
+ end
26
+ end
27
+ controller.params.delete("background_cache")
28
+ controller.params.delete("background_cache_load")
29
+ controller.params.delete("background_cache_unload")
30
+ true
31
+ end
32
+ def after(controller)
33
+ if @cache
34
+ # Restore layout
35
+ if @cache[:layout] == false
36
+ controller.class.layout(@layout)
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,18 @@
1
+ module BackgroundCache
2
+ module Helper
3
+ def cache_with_background_cache(name = {}, options = nil, &block)
4
+ cache = BackgroundCache::Config.from_params_and_fragment(params, name)
5
+ if cache
6
+ # http://api.rubyonrails.org/classes/ActionView/Helpers/CacheHelper.html
7
+ # http://api.rubyonrails.org/classes/ActionController/Caching/Fragments.html
8
+ # ActionController::Caching::Fragments#fragment_for (undocumented)
9
+ pos = output_buffer.length
10
+ block.call
11
+ @controller.write_fragment(name, buffer[pos..-1], options)
12
+ else
13
+ cache_without_background_cache(name, options, &block)
14
+ end
15
+ end
16
+ alias_method_chain :cache, :background_cache
17
+ end
18
+ end
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,6 @@
1
+ $TESTING=true
2
+ $:.push File.join(File.dirname(__FILE__), '..', 'lib')
3
+
4
+ Spec::Runner.configure do |config|
5
+
6
+ end
@@ -0,0 +1,29 @@
1
+ desc "Background cache cron job"
2
+ task :background_cache => :environment do
3
+ # Secure filters
4
+ key = CACHE['background_cache/key'] = BackgroundCache::Config.key
5
+ # Used to make requests
6
+ session = ActionController::Integration::Session.new
7
+ session.host = nil
8
+ # Load the application background cache config (stay dynamic)
9
+ session.get("/?background_cache_load=#{key}")
10
+ # Retrieve caches from config
11
+ load RAILS_ROOT + "/lib/background_cache_config.rb"
12
+ caches = BackgroundCache::Config.caches
13
+ caches.each do |cache|
14
+ # Unique cache id for storing last expired time
15
+ id = BackgroundCache::Config.unique_cache_id(cache)
16
+ # Find out when this cache was last expired
17
+ expired_at = CACHE[id]
18
+ # If last expired doesn't exist or is older than :every
19
+ if !expired_at || Time.now - expired_at > cache[:every]
20
+ # Request action with ?background_cache
21
+ session.get(session.url_for(cache[:params]) + "?background_cache=#{key}")
22
+ # Update last expired time
23
+ CACHE[id] = Time.now
24
+ end
25
+ puts id
26
+ end
27
+ # Unload the application background cache config
28
+ session.get("/?background_cache_unload=#{key}")
29
+ end
metadata ADDED
@@ -0,0 +1,65 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: winton-background_cache
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Winton Welsh
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-03-11 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: Generate caches before your users do (with Rails and cache_fu)
17
+ email: mail@wintoni.us
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - MIT-LICENSE
26
+ - README.markdown
27
+ - Rakefile
28
+ - background_cache.gemspec
29
+ - changelog.markdown
30
+ - init.rb
31
+ - lib/background_cache.rb
32
+ - lib/background_cache/config.rb
33
+ - lib/background_cache/controller.rb
34
+ - lib/background_cache/helper.rb
35
+ - spec/spec.opts
36
+ - spec/spec_helper.rb
37
+ - tasks/background_cache.rake
38
+ has_rdoc: false
39
+ homepage: http://github.com/winton/background_cache
40
+ post_install_message:
41
+ rdoc_options: []
42
+
43
+ require_paths:
44
+ - lib
45
+ required_ruby_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ required_rubygems_version: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ version:
57
+ requirements: []
58
+
59
+ rubyforge_project:
60
+ rubygems_version: 1.2.0
61
+ signing_key:
62
+ specification_version: 2
63
+ summary: Generate caches before your users do (with Rails and cache_fu)
64
+ test_files: []
65
+