winton-background_cache 0.1.1

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.
@@ -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
+