winton-background_cache 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/README.markdown +66 -0
- data/Rakefile +31 -0
- data/background_cache.gemspec +32 -0
- data/changelog.markdown +4 -0
- data/init.rb +5 -0
- data/lib/background_cache.rb +2 -0
- data/lib/background_cache/config.rb +118 -0
- data/lib/background_cache/controller.rb +42 -0
- data/lib/background_cache/helper.rb +18 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +6 -0
- data/tasks/background_cache.rake +29 -0
- metadata +65 -0
data/MIT-LICENSE
ADDED
@@ -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.
|
data/README.markdown
ADDED
@@ -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
|
data/Rakefile
ADDED
@@ -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
|
data/changelog.markdown
ADDED
data/init.rb
ADDED
@@ -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
|
data/spec/spec.opts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/spec/spec_helper.rb
ADDED
@@ -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
|
+
|