merb-cache 0.9.2
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 +77 -0
- data/TODO +2 -0
- data/lib/merb-cache/cache-action.rb +132 -0
- data/lib/merb-cache/cache-fragment.rb +95 -0
- data/lib/merb-cache/cache-page.rb +184 -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 +192 -0
- data/lib/merb-cache/cache-store/memcache.rb +195 -0
- data/lib/merb-cache/cache-store/memory.rb +168 -0
- data/lib/merb-cache/merb-cache.rb +164 -0
- data/lib/merb-cache/merbtasks.rb +6 -0
- data/lib/merb-cache.rb +10 -0
- metadata +82 -0
@@ -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
|
@@ -0,0 +1,144 @@
|
|
1
|
+
class Merb::Cache::DatabaseStore
|
2
|
+
# Provides the database cache store for merb-cache
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@config = Merb::Controller._cache.config
|
6
|
+
prepare
|
7
|
+
end
|
8
|
+
|
9
|
+
class OrmNotFound < Exception #:nodoc:
|
10
|
+
def initialize
|
11
|
+
super("No valid ORM found (did you specify use_orm in init.rb?)")
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
# Requires the ORM at startup, raising an OrmNotFound exception if
|
16
|
+
# the backend is not found
|
17
|
+
Merb::Controller._cache.config[:table_name] ||= "merb_cache"
|
18
|
+
if defined?(Merb::Orms::ActiveRecord)
|
19
|
+
require "merb-cache/cache-store/database-activerecord.rb"
|
20
|
+
include Merb::Cache::DatabaseStore::ActiveRecord
|
21
|
+
elsif defined?(Merb::Orms::DataMapper)
|
22
|
+
require "merb-cache/cache-store/database-datamapper.rb"
|
23
|
+
include Merb::Cache::DatabaseStore::DataMapper
|
24
|
+
elsif defined?(Merb::Orms::Sequel)
|
25
|
+
require "merb-cache/cache-store/database-sequel.rb"
|
26
|
+
include Merb::Cache::DatabaseStore::Sequel
|
27
|
+
else
|
28
|
+
raise OrmNotFound
|
29
|
+
end
|
30
|
+
|
31
|
+
# This method is there to ensure minimal requirements are met
|
32
|
+
# (file permissions, table exists, connected to server, ...)
|
33
|
+
def prepare
|
34
|
+
CacheModel.check_table
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
# Checks whether a cache entry exists
|
39
|
+
#
|
40
|
+
# ==== Parameter
|
41
|
+
# key<String>:: The key identifying the cache entry
|
42
|
+
#
|
43
|
+
# ==== Returns
|
44
|
+
# true if the cache entry exists, false otherwise
|
45
|
+
def cached?(key)
|
46
|
+
not CacheModel.cache_get(key).nil?
|
47
|
+
end
|
48
|
+
|
49
|
+
# Capture or restore the data in cache.
|
50
|
+
# If the cache entry expired or does not exist, the data are taken
|
51
|
+
# from the execution of the block, marshalled and stored in cache.
|
52
|
+
# Otherwise, the data are loaded from the cache and returned unmarshalled
|
53
|
+
#
|
54
|
+
# ==== Parameters
|
55
|
+
# _controller<Merb::Controller>:: The instance of the current controller
|
56
|
+
# key<String>:: The key identifying the cache entry
|
57
|
+
# from_now<~minutes>::
|
58
|
+
# The number of minutes (from now) the cache should persist
|
59
|
+
# &block:: The template to be used or not
|
60
|
+
#
|
61
|
+
# ==== Additional information
|
62
|
+
# When fetching data (the cache entry exists and has not expired)
|
63
|
+
# The data are loaded from the cache and returned unmarshalled.
|
64
|
+
# Otherwise:
|
65
|
+
# The controller is used to capture the rendered template (from the block).
|
66
|
+
# It uses the capture_#{engine} and concat_#{engine} methods to do so.
|
67
|
+
# The captured data are then marshalled and stored.
|
68
|
+
def cache(_controller, key, from_now = nil, &block)
|
69
|
+
_data = CacheModel.cache_get(key)
|
70
|
+
if _data.nil?
|
71
|
+
_expire = from_now ? from_now.minutes.from_now : nil
|
72
|
+
_data = _controller.send(:capture, &block)
|
73
|
+
CacheModel.cache_set(key, Marshal.dump(_data), _expire, false)
|
74
|
+
else
|
75
|
+
_data = Marshal.load(_data)
|
76
|
+
end
|
77
|
+
_controller.send(:concat, _data, block.binding)
|
78
|
+
true
|
79
|
+
end
|
80
|
+
|
81
|
+
# Store data to the database using the specified key
|
82
|
+
#
|
83
|
+
# ==== Parameters
|
84
|
+
# key<Sting>:: The key identifying the cache entry
|
85
|
+
# data<String>:: The data to be put in cache
|
86
|
+
# from_now<~minutes>::
|
87
|
+
# The number of minutes (from now) the cache should persist
|
88
|
+
def cache_set(key, data, from_now = nil)
|
89
|
+
_expire = from_now ? from_now.minutes.from_now : nil
|
90
|
+
CacheModel.cache_set(key, Marshal.dump(data), _expire)
|
91
|
+
Merb.logger.info("cache: set (#{key})")
|
92
|
+
true
|
93
|
+
end
|
94
|
+
|
95
|
+
# Fetch data from the database using the specified key
|
96
|
+
# The entry is deleted if it has expired
|
97
|
+
#
|
98
|
+
# ==== Parameter
|
99
|
+
# key<Sting>:: The key identifying the cache entry
|
100
|
+
#
|
101
|
+
# ==== Returns
|
102
|
+
# data<String, NilClass>::
|
103
|
+
# nil is returned whether the entry expired or was not found
|
104
|
+
def cache_get(key)
|
105
|
+
data = CacheModel.cache_get(key)
|
106
|
+
Merb.logger.info("cache: #{data.nil? ? "miss" : "hit"} (#{key})")
|
107
|
+
data.nil? ? nil : Marshal.load(data)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Expire the cache entry identified by the given key
|
111
|
+
#
|
112
|
+
# ==== Parameter
|
113
|
+
# key<Sting>:: The key identifying the cache entry
|
114
|
+
def expire(key)
|
115
|
+
CacheModel.expire(key)
|
116
|
+
Merb.logger.info("cache: expired (#{key})")
|
117
|
+
true
|
118
|
+
end
|
119
|
+
|
120
|
+
# Expire the cache entries matching the given key
|
121
|
+
#
|
122
|
+
# ==== Parameter
|
123
|
+
# key<Sting>:: The key matching the cache entries
|
124
|
+
def expire_match(key)
|
125
|
+
CacheModel.expire_match(key)
|
126
|
+
Merb.logger.info("cache: expired matching (#{key})")
|
127
|
+
true
|
128
|
+
end
|
129
|
+
|
130
|
+
# Expire all the cache entries
|
131
|
+
def expire_all
|
132
|
+
CacheModel.expire_all
|
133
|
+
Merb.logger.info("cache: expired all")
|
134
|
+
true
|
135
|
+
end
|
136
|
+
|
137
|
+
# Gives info on the current cache store
|
138
|
+
#
|
139
|
+
# ==== Returns
|
140
|
+
# The type of the current cache store
|
141
|
+
def cache_store_type
|
142
|
+
"database"
|
143
|
+
end
|
144
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
class Merb::Cache::DummyStore
|
2
|
+
# Provides dummy cache store for merb-cache
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@config = Merb::Controller._cache.config
|
6
|
+
prepare
|
7
|
+
end
|
8
|
+
|
9
|
+
# This method is there to ensure minimal requirements are met
|
10
|
+
# (directories are accessible, table exists, connected to server, ...)
|
11
|
+
def prepare
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
# Checks whether a cache entry exists
|
16
|
+
#
|
17
|
+
# ==== Parameter
|
18
|
+
# key<String>:: The key identifying the cache entry
|
19
|
+
#
|
20
|
+
# ==== Returns
|
21
|
+
# true if the cache entry exists, false otherwise
|
22
|
+
def cached?(key)
|
23
|
+
false
|
24
|
+
end
|
25
|
+
|
26
|
+
# Capture or restore the data in cache.
|
27
|
+
# If the cache entry expired or does not exist, the data are taken
|
28
|
+
# from the execution of the block, marshalled and stored in cache.
|
29
|
+
# Otherwise, the data are loaded from the cache and returned unmarshalled
|
30
|
+
#
|
31
|
+
# ==== Parameters
|
32
|
+
# _controller<Merb::Controller>:: The instance of the current controller
|
33
|
+
# key<String>:: The key identifying the cache entry
|
34
|
+
# from_now<~minutes>::
|
35
|
+
# The number of minutes (from now) the cache should persist
|
36
|
+
# &block:: The template to be used or not
|
37
|
+
#
|
38
|
+
# ==== Additional information
|
39
|
+
# When fetching data (the cache entry exists and has not expired)
|
40
|
+
# The data are loaded from the cache and returned unmarshalled.
|
41
|
+
# Otherwise:
|
42
|
+
# The controller is used to capture the rendered template (from the block).
|
43
|
+
# It uses the capture_#{engine} and concat_#{engine} methods to do so.
|
44
|
+
# The captured data are then marshalled and stored.
|
45
|
+
def cache(_controller, key, from_now = nil, &block)
|
46
|
+
_data = _controller.send(:capture, &block)
|
47
|
+
_controller.send(:concat, _data, block.binding)
|
48
|
+
true
|
49
|
+
end
|
50
|
+
|
51
|
+
# Store data to memcache using the specified key
|
52
|
+
#
|
53
|
+
# ==== Parameters
|
54
|
+
# key<Sting>:: The key identifying the cache entry
|
55
|
+
# data<String>:: The data to be put in cache
|
56
|
+
# from_now<~minutes>::
|
57
|
+
# The number of minutes (from now) the cache should persist
|
58
|
+
def cache_set(key, data, from_now = nil)
|
59
|
+
true
|
60
|
+
end
|
61
|
+
|
62
|
+
# Fetch data from memcache using the specified key
|
63
|
+
# The entry is deleted if it has expired
|
64
|
+
#
|
65
|
+
# ==== Parameter
|
66
|
+
# key<Sting>:: The key identifying the cache entry
|
67
|
+
#
|
68
|
+
# ==== Returns
|
69
|
+
# data<String, NilClass>::
|
70
|
+
# nil is returned whether the entry expired or was not found
|
71
|
+
def cache_get(key)
|
72
|
+
nil
|
73
|
+
end
|
74
|
+
|
75
|
+
# Expire the cache entry identified by the given key
|
76
|
+
#
|
77
|
+
# ==== Parameter
|
78
|
+
# key<Sting>:: The key identifying the cache entry
|
79
|
+
def expire(key)
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
83
|
+
# Expire the cache entries matching the given key
|
84
|
+
#
|
85
|
+
# ==== Parameter
|
86
|
+
# key<Sting>:: The key matching the cache entries
|
87
|
+
#
|
88
|
+
# ==== Warning !
|
89
|
+
# This does not work in memcache.
|
90
|
+
def expire_match(key)
|
91
|
+
true
|
92
|
+
end
|
93
|
+
|
94
|
+
# Expire all the cache entries
|
95
|
+
def expire_all
|
96
|
+
true
|
97
|
+
end
|
98
|
+
|
99
|
+
# Gives info on the current cache store
|
100
|
+
#
|
101
|
+
# ==== Returns
|
102
|
+
# The type of the current cache store
|
103
|
+
def cache_store_type
|
104
|
+
"dummy"
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,192 @@
|
|
1
|
+
class Merb::Cache::FileStore
|
2
|
+
# Provides the file cache store for merb-cache
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@config = Merb::Controller._cache.config
|
6
|
+
@config[:cache_directory] ||= Merb.root_path("tmp/cache")
|
7
|
+
# @config[:cache_action_directory] ||= Merb.dir_for(:public) / "cache"
|
8
|
+
prepare
|
9
|
+
end
|
10
|
+
|
11
|
+
class NotAccessible < Exception #:nodoc:
|
12
|
+
def initialize(message)
|
13
|
+
super("Cache directories are not readable/writeable (#{message})")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# This method is there to ensure minimal requirements are met
|
18
|
+
# (directories are accessible, table exists, connected to server, ...)
|
19
|
+
def prepare
|
20
|
+
unless File.readable?(@config[:cache_directory]) &&
|
21
|
+
File.writable?(@config[:cache_directory])
|
22
|
+
raise NotAccessible, @config[:cache_directory]
|
23
|
+
end
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
# Checks whether a cache entry exists
|
28
|
+
#
|
29
|
+
# ==== Parameter
|
30
|
+
# key<String>:: The key identifying the cache entry
|
31
|
+
#
|
32
|
+
# ==== Returns
|
33
|
+
# true if the cache entry exists, false otherwise
|
34
|
+
def cached?(key)
|
35
|
+
cache_file = @config[:cache_directory] / "#{key}.cache"
|
36
|
+
_data = _expire = nil
|
37
|
+
if File.file?(cache_file)
|
38
|
+
_data, _expire = Marshal.load(cache_read(cache_file))
|
39
|
+
return true if _expire.nil? || Time.now < _expire
|
40
|
+
FileUtils.rm_f(cache_file)
|
41
|
+
end
|
42
|
+
false
|
43
|
+
end
|
44
|
+
|
45
|
+
# Capture or restore the data in cache.
|
46
|
+
# If the cache entry expired or does not exist, the data are taken
|
47
|
+
# from the execution of the block, marshalled and stored in cache.
|
48
|
+
# Otherwise, the data are loaded from the cache and returned unmarshalled
|
49
|
+
#
|
50
|
+
# ==== Parameters
|
51
|
+
# _controller<Merb::Controller>:: The instance of the current controller
|
52
|
+
# key<String>:: The key identifying the cache entry
|
53
|
+
# from_now<~minutes>::
|
54
|
+
# The number of minutes (from now) the cache should persist
|
55
|
+
# &block:: The template to be used or not
|
56
|
+
#
|
57
|
+
# ==== Additional information
|
58
|
+
# When fetching data (the cache entry exists and has not expired)
|
59
|
+
# The data are loaded from the cache and returned unmarshalled.
|
60
|
+
# Otherwise:
|
61
|
+
# The controller is used to capture the rendered template (from the block).
|
62
|
+
# It uses the capture_#{engine} and concat_#{engine} methods to do so.
|
63
|
+
# The captured data are then marshalled and stored.
|
64
|
+
def cache(_controller, key, from_now = nil, &block)
|
65
|
+
cache_file = @config[:cache_directory] / "#{key}.cache"
|
66
|
+
_cache_hit = _data = _expire = nil
|
67
|
+
|
68
|
+
if File.file?(cache_file)
|
69
|
+
_data, _expire = Marshal.load(cache_read(cache_file))
|
70
|
+
_cache_hit = true if _expire.nil? || Time.now < _expire
|
71
|
+
end
|
72
|
+
unless _cache_hit
|
73
|
+
cache_directory = File.dirname(cache_file)
|
74
|
+
FileUtils.mkdir_p(cache_directory)
|
75
|
+
_expire = from_now ? from_now.minutes.from_now : nil
|
76
|
+
_data = _controller.send(:capture, &block)
|
77
|
+
cache_write(cache_file, Marshal.dump([_data, _expire]))
|
78
|
+
end
|
79
|
+
_controller.send(:concat, _data, block.binding)
|
80
|
+
true
|
81
|
+
end
|
82
|
+
|
83
|
+
# Store data to the file using the specified key
|
84
|
+
#
|
85
|
+
# ==== Parameters
|
86
|
+
# key<Sting>:: The key identifying the cache entry
|
87
|
+
# data<String>:: The data to be put in cache
|
88
|
+
# from_now<~minutes>::
|
89
|
+
# The number of minutes (from now) the cache should persist
|
90
|
+
def cache_set(key, data, from_now = nil)
|
91
|
+
cache_file = @config[:cache_directory] / "#{key}.cache"
|
92
|
+
cache_directory = File.dirname(cache_file)
|
93
|
+
FileUtils.mkdir_p(cache_directory)
|
94
|
+
_expire = from_now ? from_now.minutes.from_now : nil
|
95
|
+
cache_write(cache_file, Marshal.dump([data, _expire]))
|
96
|
+
Merb.logger.info("cache: set (#{key})")
|
97
|
+
true
|
98
|
+
end
|
99
|
+
|
100
|
+
# Fetch data from the file using the specified key
|
101
|
+
# The entry is deleted if it has expired
|
102
|
+
#
|
103
|
+
# ==== Parameter
|
104
|
+
# key<Sting>:: The key identifying the cache entry
|
105
|
+
#
|
106
|
+
# ==== Returns
|
107
|
+
# data<String, NilClass>::
|
108
|
+
# nil is returned whether the entry expired or was not found
|
109
|
+
def cache_get(key)
|
110
|
+
cache_file = @config[:cache_directory] / "#{key}.cache"
|
111
|
+
if File.file?(cache_file)
|
112
|
+
_data, _expire = Marshal.load(cache_read(cache_file))
|
113
|
+
if _expire.nil? || Time.now < _expire
|
114
|
+
Merb.logger.info("cache: hit (#{key})")
|
115
|
+
return _data
|
116
|
+
end
|
117
|
+
FileUtils.rm_f(cache_file)
|
118
|
+
end
|
119
|
+
Merb.logger.info("cache: miss (#{key})")
|
120
|
+
nil
|
121
|
+
end
|
122
|
+
|
123
|
+
# Expire the cache entry identified by the given key
|
124
|
+
#
|
125
|
+
# ==== Parameter
|
126
|
+
# key<Sting>:: The key identifying the cache entry
|
127
|
+
def expire(key)
|
128
|
+
FileUtils.rm_f(@config[:cache_directory] / "#{key}.cache")
|
129
|
+
Merb.logger.info("cache: expired (#{key})")
|
130
|
+
true
|
131
|
+
end
|
132
|
+
|
133
|
+
# Expire the cache entries matching the given key
|
134
|
+
#
|
135
|
+
# ==== Parameter
|
136
|
+
# key<Sting>:: The key matching the cache entries
|
137
|
+
def expire_match(key)
|
138
|
+
#files = Dir.glob(@config[:cache_directory] / "#{key}*.cache")
|
139
|
+
files = Dir.glob(@config[:cache_directory] / "#{key}*")
|
140
|
+
FileUtils.rm_rf(files)
|
141
|
+
Merb.logger.info("cache: expired matching (#{key})")
|
142
|
+
true
|
143
|
+
end
|
144
|
+
|
145
|
+
# Expire all the cache entries
|
146
|
+
def expire_all
|
147
|
+
FileUtils.rm_rf(Dir.glob("#{@config[:cache_directory]}/*"))
|
148
|
+
Merb.logger.info("cache: expired all")
|
149
|
+
true
|
150
|
+
end
|
151
|
+
|
152
|
+
# Gives info on the current cache store
|
153
|
+
#
|
154
|
+
# ==== Returns
|
155
|
+
# The type of the current cache store
|
156
|
+
def cache_store_type
|
157
|
+
"file"
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
# Read data from the file using exclusive lock
|
163
|
+
#
|
164
|
+
# ==== Parameters
|
165
|
+
# cache_file<String>:: The full path to the file
|
166
|
+
#
|
167
|
+
# ==== Returns
|
168
|
+
# _data<String>:: The data read from the file
|
169
|
+
def cache_read(cache_file)
|
170
|
+
_data = nil
|
171
|
+
File.open(cache_file, "r") do |cache_data|
|
172
|
+
cache_data.flock(File::LOCK_EX)
|
173
|
+
_data = cache_data.read
|
174
|
+
cache_data.flock(File::LOCK_UN)
|
175
|
+
end
|
176
|
+
_data
|
177
|
+
end
|
178
|
+
|
179
|
+
# Write data to the file using exclusive lock
|
180
|
+
#
|
181
|
+
# ==== Parameters
|
182
|
+
# cache_file<String>:: The full path to the file
|
183
|
+
# data<String>:: The data to be put in cache
|
184
|
+
def cache_write(cache_file, data)
|
185
|
+
File.open(cache_file, "w+") do |cache_data|
|
186
|
+
cache_data.flock(File::LOCK_EX)
|
187
|
+
cache_data.write(data)
|
188
|
+
cache_data.flock(File::LOCK_UN)
|
189
|
+
end
|
190
|
+
true
|
191
|
+
end
|
192
|
+
end
|