thorero-cache 0.9.4
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.
- data/LICENSE +20 -0
- data/README +160 -0
- data/Rakefile +59 -0
- data/TODO +2 -0
- data/lib/merb-cache.rb +10 -0
- data/lib/merb-cache/cache-action.rb +135 -0
- data/lib/merb-cache/cache-fragment.rb +95 -0
- data/lib/merb-cache/cache-page.rb +203 -0
- data/lib/merb-cache/cache-store/database-activerecord.rb +88 -0
- data/lib/merb-cache/cache-store/database-datamapper.rb +79 -0
- data/lib/merb-cache/cache-store/database-sequel.rb +78 -0
- data/lib/merb-cache/cache-store/database.rb +144 -0
- data/lib/merb-cache/cache-store/dummy.rb +106 -0
- data/lib/merb-cache/cache-store/file.rb +194 -0
- data/lib/merb-cache/cache-store/memcache.rb +199 -0
- data/lib/merb-cache/cache-store/memory.rb +168 -0
- data/lib/merb-cache/merb-cache.rb +165 -0
- data/lib/merb-cache/merbtasks.rb +6 -0
- data/spec/config/database.yml +14 -0
- data/spec/controller.rb +87 -0
- data/spec/log/merb_test.log +433 -0
- data/spec/merb-cache-action_spec.rb +137 -0
- data/spec/merb-cache-fragment_spec.rb +100 -0
- data/spec/merb-cache-page_spec.rb +150 -0
- data/spec/merb-cache_spec.rb +15 -0
- data/spec/spec_helper.rb +84 -0
- data/spec/views/cache_controller/action1.html.erb +4 -0
- data/spec/views/cache_controller/action2.html.haml +4 -0
- metadata +110 -0
@@ -0,0 +1,203 @@
|
|
1
|
+
class Merb::Cache
|
2
|
+
cattr_accessor :cached_pages
|
3
|
+
self.cached_pages = {}
|
4
|
+
end
|
5
|
+
|
6
|
+
module Merb::Cache::ControllerClassMethods
|
7
|
+
# Mixed in Merb::Controller. Provides class methods related to page caching
|
8
|
+
# Page caching is mostly action caching with file backend using its own output directory of .html files
|
9
|
+
|
10
|
+
# Register the action for page caching
|
11
|
+
#
|
12
|
+
# ==== Parameters
|
13
|
+
# action<Symbol>:: The name of the action to register
|
14
|
+
# from_now<~minutes>::
|
15
|
+
# The number of minutes (from now) the cache should persist
|
16
|
+
#
|
17
|
+
# ==== Examples
|
18
|
+
# cache_page :mostly_static
|
19
|
+
# cache_page :barely_dynamic, 10
|
20
|
+
def cache_page(action, from_now = nil)
|
21
|
+
cache_pages([action, from_now])
|
22
|
+
end
|
23
|
+
|
24
|
+
# Register actions for page caching (before and after filters)
|
25
|
+
#
|
26
|
+
# ==== Parameter
|
27
|
+
# pages<Symbol,Array[Symbol,~minutes]>:: See #cache_page
|
28
|
+
#
|
29
|
+
# ==== Example
|
30
|
+
# cache_pages :mostly_static, [:barely_dynamic, 10]
|
31
|
+
def cache_pages(*pages)
|
32
|
+
if pages.any? && !Merb::Cache.cached_pages.key?(controller_name)
|
33
|
+
before(:cache_page_before)
|
34
|
+
after(:cache_page_after)
|
35
|
+
end
|
36
|
+
pages.each do |action, from_now|
|
37
|
+
_pages = Merb::Cache.cached_pages[controller_name] ||= {}
|
38
|
+
_pages[action] = [from_now, 0]
|
39
|
+
end
|
40
|
+
true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module Merb::Cache::ControllerInstanceMethods
|
45
|
+
# Mixed in Merb::Controller. Provides methods related to page caching
|
46
|
+
|
47
|
+
DEFAULT_PAGE_EXTENSION = 'html'
|
48
|
+
|
49
|
+
# Checks whether a cache entry exists
|
50
|
+
#
|
51
|
+
# ==== Parameter
|
52
|
+
# options<String,Hash>:: The options that will be passed to #key_for
|
53
|
+
#
|
54
|
+
# ==== Returns
|
55
|
+
# true if the cache entry exists, false otherwise
|
56
|
+
#
|
57
|
+
# ==== Example
|
58
|
+
# cached_page?(:action => 'show', :params => [params[:page]])
|
59
|
+
# cached_page?(:action => 'show', :extension => 'js')
|
60
|
+
def cached_page?(options)
|
61
|
+
key = Merb::Controller._cache.key_for(options, controller_name, true)
|
62
|
+
extension = options[:extension] || DEFAULT_PAGE_EXTENSION
|
63
|
+
File.file?(Merb::Controller._cache.config[:cache_html_directory] / "#{key}.#{extension}")
|
64
|
+
end
|
65
|
+
|
66
|
+
# Expires the page identified by the key computed after the parameters
|
67
|
+
#
|
68
|
+
# ==== Parameter
|
69
|
+
# options<String,Hash>:: The options that will be passed to #expire_key_for
|
70
|
+
#
|
71
|
+
# ==== Examples (See Merb::Cache#expire_key_for for more options)
|
72
|
+
# # will expire path/to/page/cache/news/show/1.html
|
73
|
+
# expire_page(:key => url(:news,News.find(1)))
|
74
|
+
#
|
75
|
+
# # will expire path/to/page/cache/news/show.html
|
76
|
+
# expire_page(:action => 'show', :controller => 'news')
|
77
|
+
#
|
78
|
+
# # will expire path/to/page/cache/news/show*
|
79
|
+
# expire_page(:action => 'show', :match => true)
|
80
|
+
#
|
81
|
+
# # will expire path/to/page/cache/news/show.js
|
82
|
+
# expire_page(:action => 'show', :extension => 'js')
|
83
|
+
def expire_page(options)
|
84
|
+
config_dir = Merb::Controller._cache.config[:cache_html_directory]
|
85
|
+
Merb::Controller._cache.expire_key_for(options, controller_name, true) do |key, match|
|
86
|
+
if match
|
87
|
+
files = Dir.glob(config_dir / "#{key}*")
|
88
|
+
else
|
89
|
+
extension = options[:extension] || DEFAULT_PAGE_EXTENSION
|
90
|
+
files = config_dir / "#{key}.#{extension}"
|
91
|
+
end
|
92
|
+
FileUtils.rm_rf(files)
|
93
|
+
end
|
94
|
+
true
|
95
|
+
end
|
96
|
+
|
97
|
+
# Expires all the pages stored in config[:cache_html_directory]
|
98
|
+
def expire_all_pages
|
99
|
+
FileUtils.rm_rf(Dir.glob(Merb::Controller._cache.config[:cache_html_directory] / "*"))
|
100
|
+
end
|
101
|
+
|
102
|
+
# You can call this method if you need to prevent caching the page
|
103
|
+
# after it has been rendered.
|
104
|
+
def abort_cache_page
|
105
|
+
@capture_page = false
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
# Called by the before and after filters. Stores or recalls a cache entry.
|
111
|
+
# The name used for the cache file is based on request.path
|
112
|
+
# If the name ends with "/" then it is removed
|
113
|
+
# If the name is "/" then it will be replaced by "index"
|
114
|
+
#
|
115
|
+
# ==== Parameters
|
116
|
+
# data<String>:: the data to put in cache
|
117
|
+
#
|
118
|
+
# ==== Examples
|
119
|
+
# All the file are written to config[:cache_html_directory]
|
120
|
+
# If request.path is "/", the name will be "/index.html"
|
121
|
+
# If request.path is "/news/show/1", the name will be "/news/show/1.html"
|
122
|
+
# If request.path is "/news/show/", the name will be "/news/show.html"
|
123
|
+
# If request.path is "/news/styles.css", the name will be "/news/styles.css"
|
124
|
+
def _cache_page(data = nil)
|
125
|
+
return if Merb::Controller._cache.config[:disable_page_caching]
|
126
|
+
controller = controller_name
|
127
|
+
action = action_name.to_sym
|
128
|
+
pages = Merb::Controller._cache.cached_pages[controller]
|
129
|
+
return unless pages && pages.key?(action)
|
130
|
+
path = request.path.chomp("/")
|
131
|
+
path = "index" if path.empty?
|
132
|
+
no_format = params[:format].nil? || params[:format].empty?
|
133
|
+
ext = "." + (no_format ? DEFAULT_PAGE_EXTENSION : params[:format])
|
134
|
+
ext = nil if File.extname(path) == ext
|
135
|
+
cache_file = Merb::Controller._cache.config[:cache_html_directory] / "#{path}#{ext}"
|
136
|
+
if data
|
137
|
+
cache_directory = File.dirname(cache_file)
|
138
|
+
FileUtils.mkdir_p(cache_directory)
|
139
|
+
_expire_in = pages[action][0]
|
140
|
+
pages[action][1] = _expire_in.minutes.from_now unless _expire_in.nil?
|
141
|
+
cache_write_page(cache_file, data)
|
142
|
+
Merb.logger.info("cache: set (#{path})")
|
143
|
+
else
|
144
|
+
@capture_page = false
|
145
|
+
if File.file?(cache_file)
|
146
|
+
_data = cache_read_page(cache_file)
|
147
|
+
_expire_in, _expire_at = pages[action]
|
148
|
+
if _expire_in.nil? || Time.now.to_i < _expire_at.to_i
|
149
|
+
Merb.logger.info("cache: hit (#{path})")
|
150
|
+
throw(:halt, _data)
|
151
|
+
end
|
152
|
+
FileUtils.rm_f(cache_file)
|
153
|
+
end
|
154
|
+
@capture_page = true
|
155
|
+
end
|
156
|
+
true
|
157
|
+
end
|
158
|
+
|
159
|
+
# Read data from a file using exclusive lock
|
160
|
+
#
|
161
|
+
# ==== Parameters
|
162
|
+
# cache_file<String>:: the full path to the file
|
163
|
+
#
|
164
|
+
# ==== Returns
|
165
|
+
# data<String>:: the data that has been read from the file
|
166
|
+
def cache_read_page(cache_file)
|
167
|
+
_data = nil
|
168
|
+
File.open(cache_file, "r") do |cache_data|
|
169
|
+
cache_data.flock(File::LOCK_EX)
|
170
|
+
_data = cache_data.read
|
171
|
+
cache_data.flock(File::LOCK_UN)
|
172
|
+
end
|
173
|
+
_data
|
174
|
+
end
|
175
|
+
|
176
|
+
# Write data to a file using exclusive lock
|
177
|
+
#
|
178
|
+
# ==== Parameters
|
179
|
+
# cache_file<String>:: the full path to the file
|
180
|
+
# data<String>:: the data that will be written to the file
|
181
|
+
def cache_write_page(cache_file, data)
|
182
|
+
File.open(cache_file, "w+") do |cache_data|
|
183
|
+
cache_data.flock(File::LOCK_EX)
|
184
|
+
cache_data.write(data)
|
185
|
+
cache_data.flock(File::LOCK_UN)
|
186
|
+
end
|
187
|
+
true
|
188
|
+
end
|
189
|
+
|
190
|
+
# before filter
|
191
|
+
def cache_page_before
|
192
|
+
# recalls a cached entry or set @capture_page to true in order
|
193
|
+
# to grab the response in the after filter
|
194
|
+
_cache_page
|
195
|
+
end
|
196
|
+
|
197
|
+
# after filter
|
198
|
+
def cache_page_after
|
199
|
+
# takes the body of the response
|
200
|
+
# if the cache entry expired, if it doesn't exist or status is 200
|
201
|
+
_cache_page(body) if @capture_page && status == 200
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Merb::Cache::DatabaseStore::ActiveRecord
|
2
|
+
# Module that provides ActiveRecord support for the database backend
|
3
|
+
|
4
|
+
# The cache model migration
|
5
|
+
class CacheMigration < ActiveRecord::Migration
|
6
|
+
def self.up
|
7
|
+
create_table (Merb::Controller._cache.config[:table_name]), :primary_key => :ckey do |t|
|
8
|
+
t.column :ckey, :string
|
9
|
+
t.column :data, :text
|
10
|
+
t.datetime :expire, :default => nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.down
|
15
|
+
drop_table Merb::Controller._cache.config[:table_name]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# The cache model
|
20
|
+
class CacheModel < ActiveRecord::Base
|
21
|
+
set_table_name Merb::Controller._cache.config[:table_name]
|
22
|
+
|
23
|
+
# Fetch data from the database using the specified key
|
24
|
+
# The entry is deleted if it has expired
|
25
|
+
#
|
26
|
+
# ==== Parameter
|
27
|
+
# key<Sting>:: The key identifying the cache entry
|
28
|
+
#
|
29
|
+
# ==== Returns
|
30
|
+
# data<String, NilClass>::
|
31
|
+
# nil is returned whether the entry expired or was not found
|
32
|
+
def self.cache_get(key)
|
33
|
+
if entry = self.find(:first, :conditions => ["ckey=?", key])
|
34
|
+
return entry.data if entry.expire.nil? || Time.now < entry.expire
|
35
|
+
self.expire(key)
|
36
|
+
end
|
37
|
+
nil
|
38
|
+
end
|
39
|
+
|
40
|
+
# Store data to the database using the specified key
|
41
|
+
#
|
42
|
+
# ==== Parameters
|
43
|
+
# key<Sting>:: The key identifying the cache entry
|
44
|
+
# data<String>:: The data to be put in cache
|
45
|
+
# expire<~minutes>::
|
46
|
+
# The number of minutes (from now) the cache should persist
|
47
|
+
# get<Boolean>::
|
48
|
+
# used internally to behave like this
|
49
|
+
# - when set to true, perform update_or_create on the cache entry
|
50
|
+
# - when set to false, force creation of the cache entry
|
51
|
+
def self.cache_set(key, data, expire = nil, get = true)
|
52
|
+
attributes = {:ckey => key, :data => data, :expire => expire }
|
53
|
+
if get
|
54
|
+
entry = self.find(:first, :conditions => ["ckey=?",key])
|
55
|
+
entry.nil? ? self.create(attributes) : entry.update_attributes(attributes)
|
56
|
+
else
|
57
|
+
self.create(attributes)
|
58
|
+
end
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
# Expire the cache entry identified by the given key
|
63
|
+
#
|
64
|
+
# ==== Parameter
|
65
|
+
# key<Sting>:: The key identifying the cache entry
|
66
|
+
def self.expire(key)
|
67
|
+
self.delete_all(["ckey=?", key])
|
68
|
+
end
|
69
|
+
|
70
|
+
# Expire the cache entries matching the given key
|
71
|
+
#
|
72
|
+
# ==== Parameter
|
73
|
+
# key<Sting>:: The key matching the cache entries
|
74
|
+
def self.expire_match(key)
|
75
|
+
self.delete_all(["ckey like ?", key + "%"])
|
76
|
+
end
|
77
|
+
|
78
|
+
# Expire all the cache entries
|
79
|
+
def self.expire_all
|
80
|
+
self.delete_all
|
81
|
+
end
|
82
|
+
|
83
|
+
# Perform auto-migration in case the table is unknown in the database
|
84
|
+
def self.check_table
|
85
|
+
CacheMigration.up unless self.table_exists?
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Merb::Cache::DatabaseStore::DataMapper
|
2
|
+
# Module that provides DataMapper support for the database backend
|
3
|
+
|
4
|
+
# The cache model
|
5
|
+
class CacheModel < DataMapper::Base
|
6
|
+
set_table_name Merb::Controller._cache.config[:table_name]
|
7
|
+
property :ckey, :string, :length => 255, :lazy => false, :key => true
|
8
|
+
property :data, :text, :lazy => false
|
9
|
+
property :expire, :datetime, :default => nil
|
10
|
+
|
11
|
+
# Fetch data from the database using the specified key
|
12
|
+
# The entry is deleted if it has expired
|
13
|
+
#
|
14
|
+
# ==== Parameter
|
15
|
+
# key<Sting>:: The key identifying the cache entry
|
16
|
+
#
|
17
|
+
# ==== Returns
|
18
|
+
# data<String, NilClass>::
|
19
|
+
# nil is returned whether the entry expired or was not found
|
20
|
+
def self.cache_get(key)
|
21
|
+
if entry = self.first(key)
|
22
|
+
return entry.data if entry.expire.nil? || DateTime.now < entry.expire
|
23
|
+
entry.destroy!
|
24
|
+
end
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
# Store data to the database using the specified key
|
29
|
+
#
|
30
|
+
# ==== Parameters
|
31
|
+
# key<Sting>:: The key identifying the cache entry
|
32
|
+
# data<String>:: The data to be put in cache
|
33
|
+
# expire<~minutes>::
|
34
|
+
# The number of minutes (from now) the cache should persist
|
35
|
+
# get<Boolean>::
|
36
|
+
# used internally to behave like this:
|
37
|
+
# - when set to true, perform update_or_create on the cache entry
|
38
|
+
# - when set to false, force creation of the cache entry
|
39
|
+
def self.cache_set(key, data, expire = nil, get = true)
|
40
|
+
attributes = {:ckey => key, :data => data,
|
41
|
+
:expire => expire.nil? ? nil : expire.to_s_db }
|
42
|
+
if get
|
43
|
+
entry = self.first(key)
|
44
|
+
entry.nil? ? self.create(attributes) : entry.update_attributes(attributes)
|
45
|
+
else
|
46
|
+
self.create(attributes)
|
47
|
+
end
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
# Expire the cache entry identified by the given key
|
52
|
+
#
|
53
|
+
# ==== Parameter
|
54
|
+
# key<Sting>:: The key identifying the cache entry
|
55
|
+
def self.expire(key)
|
56
|
+
entry = self.first(key)
|
57
|
+
entry.destroy! unless entry.nil?
|
58
|
+
end
|
59
|
+
|
60
|
+
# Expire the cache entries matching the given key
|
61
|
+
#
|
62
|
+
# ==== Parameter
|
63
|
+
# key<Sting>:: The key matching the cache entries
|
64
|
+
def self.expire_match(key)
|
65
|
+
#FIXME
|
66
|
+
database.execute("DELETE FROM #{self.table.name} WHERE ckey LIKE '#{key}%'")
|
67
|
+
end
|
68
|
+
|
69
|
+
# Expire all the cache entries
|
70
|
+
def self.expire_all
|
71
|
+
self.truncate!
|
72
|
+
end
|
73
|
+
|
74
|
+
# Perform auto-migration in case the table is unknown in the database
|
75
|
+
def self.check_table
|
76
|
+
self.auto_migrate! unless self.table.exists?
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Merb::Cache::DatabaseStore::Sequel
|
2
|
+
# Module that provides Sequel support for the database backend
|
3
|
+
|
4
|
+
# The cache model
|
5
|
+
class CacheModel < Sequel::Model(Merb::Controller._cache.config[:table_name].to_sym)
|
6
|
+
set_schema do
|
7
|
+
primary_key :id
|
8
|
+
varchar :ckey, :index => true
|
9
|
+
varchar :data
|
10
|
+
timestamp :expire, :default => nil
|
11
|
+
end
|
12
|
+
|
13
|
+
# Fetch data from the database using the specified key
|
14
|
+
# The entry is deleted if it has expired
|
15
|
+
#
|
16
|
+
# ==== Parameter
|
17
|
+
# key<Sting>:: The key identifying the cache entry
|
18
|
+
#
|
19
|
+
# ==== Returns
|
20
|
+
# data<String, NilClass>::
|
21
|
+
# nil is returned whether the entry expired or was not found
|
22
|
+
def self.cache_get(key)
|
23
|
+
if entry = self.filter(:ckey => key).single_record(:limit => 1)
|
24
|
+
return entry.data if entry.expire.nil? || Time.now < entry.expire
|
25
|
+
self.expire(key)
|
26
|
+
end
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
|
30
|
+
# Store data to the database using the specified key
|
31
|
+
#
|
32
|
+
# ==== Parameters
|
33
|
+
# key<Sting>:: The key identifying the cache entry
|
34
|
+
# data<String>:: The data to be put in cache
|
35
|
+
# expire<~minutes>::
|
36
|
+
# The number of minutes (from now) the cache should persist
|
37
|
+
# get<Boolean>::
|
38
|
+
# used internally to behave like this
|
39
|
+
# - when set to true, perform update_or_create on the cache entry
|
40
|
+
# - when set to false, force creation of the cache entry
|
41
|
+
def self.cache_set(key, data, expire = nil, get = true)
|
42
|
+
attributes = {:ckey => key, :data => data, :expire => expire }
|
43
|
+
if get
|
44
|
+
entry = self.filter(:ckey => key).single_record(:limit => 1)
|
45
|
+
entry.nil? ? self.create(attributes) : entry.set(attributes)
|
46
|
+
else
|
47
|
+
self.create(attributes)
|
48
|
+
end
|
49
|
+
true
|
50
|
+
end
|
51
|
+
|
52
|
+
# Expire the cache entry identified by the given key
|
53
|
+
#
|
54
|
+
# ==== Parameter
|
55
|
+
# key<Sting>:: The key identifying the cache entry
|
56
|
+
def self.expire(key)
|
57
|
+
self.filter(:ckey => key).delete
|
58
|
+
end
|
59
|
+
|
60
|
+
# Expire the cache entries matching the given key
|
61
|
+
#
|
62
|
+
# ==== Parameter
|
63
|
+
# key<Sting>:: The key matching the cache entries
|
64
|
+
def self.expire_match(key)
|
65
|
+
self.filter{:ckey.like key + "%"}.delete
|
66
|
+
end
|
67
|
+
|
68
|
+
# Expire all the cache entries
|
69
|
+
def self.expire_all
|
70
|
+
self.delete_all
|
71
|
+
end
|
72
|
+
|
73
|
+
# Perform auto-migration in case the table is unknown in the database
|
74
|
+
def self.check_table
|
75
|
+
self.create_table unless self.table_exists?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|