merb-cache 0.9.7 → 0.9.8
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 +2 -2
- data/README +207 -143
- data/Rakefile +55 -10
- data/TODO +0 -2
- data/lib/merb-cache/cache.rb +84 -0
- data/lib/merb-cache/core_ext/enumerable.rb +9 -0
- data/lib/merb-cache/core_ext/hash.rb +20 -0
- data/lib/merb-cache/merb_ext/controller.rb +167 -0
- data/lib/merb-cache/stores/fundamental/abstract_store.rb +101 -0
- data/lib/merb-cache/stores/fundamental/file_store.rb +112 -0
- data/lib/merb-cache/stores/fundamental/memcached_store.rb +112 -0
- data/lib/merb-cache/stores/strategy/abstract_strategy_store.rb +123 -0
- data/lib/merb-cache/stores/strategy/action_store.rb +56 -0
- data/lib/merb-cache/stores/strategy/adhoc_store.rb +69 -0
- data/lib/merb-cache/stores/strategy/gzip_store.rb +63 -0
- data/lib/merb-cache/stores/strategy/page_store.rb +64 -0
- data/lib/merb-cache/stores/strategy/sha1_store.rb +62 -0
- data/lib/merb-cache.rb +8 -7
- data/spec/merb-cache/cache_spec.rb +88 -0
- data/spec/merb-cache/core_ext/enumerable_spec.rb +22 -0
- data/spec/merb-cache/core_ext/hash_spec.rb +20 -0
- data/spec/merb-cache/merb_ext/controller_spec.rb +284 -0
- data/spec/merb-cache/stores/fundamental/abstract_store_spec.rb +166 -0
- data/spec/merb-cache/stores/fundamental/file_store_spec.rb +186 -0
- data/spec/merb-cache/stores/fundamental/memcached_store_spec.rb +243 -0
- data/spec/merb-cache/stores/strategy/abstract_strategy_store_spec.rb +78 -0
- data/spec/merb-cache/stores/strategy/action_store_spec.rb +189 -0
- data/spec/merb-cache/stores/strategy/adhoc_store_spec.rb +225 -0
- data/spec/merb-cache/stores/strategy/gzip_store_spec.rb +51 -0
- data/spec/merb-cache/stores/strategy/page_store_spec.rb +111 -0
- data/spec/merb-cache/stores/strategy/sha1_store_spec.rb +75 -0
- data/spec/spec_helper.rb +69 -72
- metadata +42 -31
- data/lib/merb-cache/cache-action.rb +0 -144
- data/lib/merb-cache/cache-fragment.rb +0 -95
- data/lib/merb-cache/cache-page.rb +0 -203
- data/lib/merb-cache/cache-store/database-activerecord.rb +0 -88
- data/lib/merb-cache/cache-store/database-datamapper.rb +0 -79
- data/lib/merb-cache/cache-store/database-sequel.rb +0 -78
- data/lib/merb-cache/cache-store/database.rb +0 -144
- data/lib/merb-cache/cache-store/dummy.rb +0 -106
- data/lib/merb-cache/cache-store/file.rb +0 -194
- data/lib/merb-cache/cache-store/memcache.rb +0 -199
- data/lib/merb-cache/cache-store/memory.rb +0 -168
- data/lib/merb-cache/merb-cache.rb +0 -165
- data/lib/merb-cache/merbtasks.rb +0 -6
- data/spec/config/database.yml +0 -14
- data/spec/controller.rb +0 -101
- data/spec/log/merb_test.log +0 -433
- data/spec/merb-cache-action_spec.rb +0 -162
- data/spec/merb-cache-fragment_spec.rb +0 -100
- data/spec/merb-cache-page_spec.rb +0 -150
- data/spec/merb-cache_spec.rb +0 -15
- data/spec/views/cache_controller/action1.html.erb +0 -4
- data/spec/views/cache_controller/action2.html.haml +0 -4
@@ -0,0 +1,167 @@
|
|
1
|
+
module Merb::Cache::CacheMixin
|
2
|
+
def self.included(base)
|
3
|
+
base.extend(ClassMethods)
|
4
|
+
end
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def cache!(conditions = {})
|
8
|
+
before(:_cache_before, conditions.only(:if, :unless).merge(:with => conditions))
|
9
|
+
after(:_cache_after, conditions.only(:if, :unless).merge(:with => conditions))
|
10
|
+
end
|
11
|
+
|
12
|
+
def cache(*actions)
|
13
|
+
if actions.last.is_a? Hash
|
14
|
+
cache_action(*actions)
|
15
|
+
else
|
16
|
+
actions.each {|a| cache_action(*a)}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def cache_action(action, conditions = {})
|
21
|
+
before("_cache_#{action}_before", conditions.only(:if, :unless).merge(:with => [conditions], :only => action))
|
22
|
+
after("_cache_#{action}_after", conditions.only(:if, :unless).merge(:with => [conditions], :only => action))
|
23
|
+
alias_method "_cache_#{action}_before", :_cache_before
|
24
|
+
alias_method "_cache_#{action}_after", :_cache_after
|
25
|
+
end
|
26
|
+
|
27
|
+
def eager_cache(trigger_action, target = trigger_action, conditions = {}, &blk)
|
28
|
+
target, conditions = trigger_action, target if target.is_a? Hash
|
29
|
+
|
30
|
+
if target.is_a? Array
|
31
|
+
target_controller, target_action = *target
|
32
|
+
else
|
33
|
+
target_controller, target_action = self, target
|
34
|
+
end
|
35
|
+
|
36
|
+
after("_eager_cache_#{trigger_action}_to_#{target_controller.name.snake_case}__#{target_action}_after", conditions.only(:if, :unless).merge(:with => [target_controller, target_action, conditions, blk], :only => trigger_action))
|
37
|
+
alias_method "_eager_cache_#{trigger_action}_to_#{target_controller.name.snake_case}__#{target_action}_after", :_eager_cache_after
|
38
|
+
end
|
39
|
+
|
40
|
+
def eager_dispatch(action, env = {}, blk = nil)
|
41
|
+
kontroller = new(Merb::Request.new(env))
|
42
|
+
kontroller.force_cache!
|
43
|
+
|
44
|
+
blk.call(kontroller) unless blk.nil?
|
45
|
+
|
46
|
+
kontroller._dispatch(action)
|
47
|
+
|
48
|
+
kontroller
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def fetch_partial(template, opts={}, conditions = {})
|
53
|
+
template_id = template.to_s
|
54
|
+
if template_id =~ %r{^/}
|
55
|
+
template_path = File.dirname(template_id) / "_#{File.basename(template_id)}"
|
56
|
+
else
|
57
|
+
kontroller = (m = template_id.match(/.*(?=\/)/)) ? m[0] : controller_name
|
58
|
+
template_id = "_#{File.basename(template_id)}"
|
59
|
+
end
|
60
|
+
|
61
|
+
unused, template_key = _template_for(template_id, opts.delete(:format) || content_type, kontroller, template_path)
|
62
|
+
|
63
|
+
fetch_proc = lambda { partial(template, opts) }
|
64
|
+
|
65
|
+
concat(Merb::Cache[_lookup_store(conditions)].fetch(template_key, opts, conditions, &fetch_proc), fetch_proc.binding)
|
66
|
+
end
|
67
|
+
|
68
|
+
def fetch_fragment(opts = {}, conditions = {}, &proc)
|
69
|
+
|
70
|
+
if opts[:cache_key].blank?
|
71
|
+
file, line = proc.to_s.scan(%r{^#<Proc:0x\w+@(.+):(\d+)>$}).first
|
72
|
+
fragment_key = "#{file}[#{line}]"
|
73
|
+
else
|
74
|
+
fragment_key = opts.delete(:cache_key)
|
75
|
+
end
|
76
|
+
|
77
|
+
concat(Merb::Cache[_lookup_store(conditions)].fetch(fragment_key, opts, conditions) { capture(&proc) }, proc.binding)
|
78
|
+
end
|
79
|
+
|
80
|
+
def _cache_before(conditions = {})
|
81
|
+
unless @_force_cache
|
82
|
+
if @_skip_cache.nil? && data = Merb::Cache[_lookup_store(conditions)].read(self, _parameters_and_conditions(conditions).first)
|
83
|
+
throw(:halt, data)
|
84
|
+
@_cache_hit = true
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def _cache_after(conditions = {})
|
90
|
+
if @_skip_cache.nil? && Merb::Cache[_lookup_store(conditions)].write(self, nil, *_parameters_and_conditions(conditions))
|
91
|
+
@_cache_write = true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def _eager_cache_after(klass, action, conditions = {}, blk = nil)
|
96
|
+
if @_skip_cache.nil?
|
97
|
+
run_later do
|
98
|
+
controller = klass.eager_dispatch(action, request.env, blk)
|
99
|
+
|
100
|
+
Merb::Cache[controller._lookup_store(conditions)].write(controller, nil, *controller._parameters_and_conditions(conditions))
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def eager_cache(action, conditions = {}, env = request.env.dup, &blk)
|
106
|
+
unless @_skip_cache
|
107
|
+
if action.is_a?(Array)
|
108
|
+
klass, action = *action
|
109
|
+
else
|
110
|
+
klass = self.class
|
111
|
+
end
|
112
|
+
|
113
|
+
run_later do
|
114
|
+
controller = klass.eager_dispatch(action, env, blk)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def _set_skip_cache
|
120
|
+
@_skip_cache = true
|
121
|
+
end
|
122
|
+
|
123
|
+
def skip_cache!
|
124
|
+
_set_skip_cache
|
125
|
+
end
|
126
|
+
|
127
|
+
def force_cache!
|
128
|
+
@_force_cache = true
|
129
|
+
end
|
130
|
+
|
131
|
+
def _lookup_store(conditions = {})
|
132
|
+
conditions[:store] || conditions[:stores] || default_cache_store
|
133
|
+
end
|
134
|
+
|
135
|
+
# Overwrite this in your controller to change the default store for a given controller
|
136
|
+
def default_cache_store
|
137
|
+
Merb::Cache.default_store_name
|
138
|
+
end
|
139
|
+
|
140
|
+
#ugly, please make me purdy'er
|
141
|
+
def _parameters_and_conditions(conditions)
|
142
|
+
parameters = {}
|
143
|
+
|
144
|
+
if self.class.respond_to? :action_argument_list
|
145
|
+
arguments, defaults = self.class.action_argument_list[action_name]
|
146
|
+
arguments.inject(parameters) do |parameters, arg|
|
147
|
+
if defaults.include?(arg.first)
|
148
|
+
parameters[arg.first] = self.params[arg.first] || arg.last
|
149
|
+
else
|
150
|
+
parameters[arg.first] = self.params[arg.first]
|
151
|
+
end
|
152
|
+
parameters
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
case conditions[:params]
|
157
|
+
when Symbol
|
158
|
+
parameters[conditions[:params]] = self.params[conditions[:params]]
|
159
|
+
when Array
|
160
|
+
conditions[:params].each do |param|
|
161
|
+
parameters[param] = self.params[param]
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
return parameters, conditions.except(:params, :store, :stores)
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
class Merb::Cache::AbstractStore
|
2
|
+
|
3
|
+
def initialize(config = {}); end
|
4
|
+
|
5
|
+
# determines if the store is able to persist data identified by the key & parameters
|
6
|
+
# with the given conditions.
|
7
|
+
#
|
8
|
+
# @param [#to_s] key the key used to identify an entry
|
9
|
+
# @param [Hash] parameters optional parameters used to identify an entry
|
10
|
+
# @param [Hash] conditions optional conditions that place constraints or detail instructions for storing an entry
|
11
|
+
# @return [TrueClass] the ability of the store to write an entry based on the key, parameters, and conditions
|
12
|
+
# @raise [NotImplementedError] API method has not been implemented
|
13
|
+
def writable?(key, parameters = {}, conditions = {})
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
|
17
|
+
# gets the data from the store identified by the key & parameters.
|
18
|
+
# return nil if the entry does not exist.
|
19
|
+
#
|
20
|
+
# @param [#to_s] key the key used to identify an entry
|
21
|
+
# @param [Hash] parameters optional parameters used to identify an entry
|
22
|
+
# @return [Object, NilClass] the match entry, or nil if no entry exists matching the key and parameters
|
23
|
+
# @raise [NotImplementedError] API method has not been implemented
|
24
|
+
def read(key, parameters = {})
|
25
|
+
raise NotImplementedError
|
26
|
+
end
|
27
|
+
|
28
|
+
# persists the data so that it can be retrieved by the key & parameters.
|
29
|
+
# returns nil if it is unable to persist the data.
|
30
|
+
# returns true if successful.
|
31
|
+
#
|
32
|
+
# @param [#to_s] key the key used to identify an entry
|
33
|
+
# @param data the object to persist as an entry
|
34
|
+
# @param [Hash] parameters optional parameters used to identify an entry
|
35
|
+
# @param [Hash] conditions optional conditions that place constraints or detail instructions for storing an entry
|
36
|
+
# @return [TrueClass, NilClass] true if the entry was successfully written, otherwise nil
|
37
|
+
# @raise [NotImplementedError] API method has not been implemented
|
38
|
+
def write(key, data = nil, parameters = {}, conditions = {})
|
39
|
+
raise NotImplementedError
|
40
|
+
end
|
41
|
+
|
42
|
+
# @param [#to_s] key the key used to identify an entry
|
43
|
+
# @param data the object to persist as an entry
|
44
|
+
# @param [Hash] parameters optional parameters used to identify an entry
|
45
|
+
# @param [Hash] conditions optional conditions that place constraints or detail instructions for storing an entry
|
46
|
+
# @return [TrueClass, NilClass] true if the entry was successfully written, otherwise nil
|
47
|
+
# @raise [NotImplementedError] API method has not been implemented
|
48
|
+
def write_all(key, data = nil, parameters = {}, conditions = {})
|
49
|
+
write(key, data, parameters, conditions)
|
50
|
+
end
|
51
|
+
|
52
|
+
# tries to read the data from the store. If that fails, it calls
|
53
|
+
# the block parameter and persists the result.
|
54
|
+
#
|
55
|
+
# @param [#to_s] key the key used to identify an entry
|
56
|
+
# @param [Hash] parameters optional parameters used to identify an entry
|
57
|
+
# @param [Hash] conditions optional conditions that place constraints or detail instructions for storing an entry
|
58
|
+
# @return [Object, NilClass] the match entry or the result of the block call, or nil if the entry is not successfully written
|
59
|
+
# @raise [NotImplementedError] API method has not been implemented
|
60
|
+
def fetch(key, parameters = {}, conditions = {}, &blk)
|
61
|
+
raise NotImplementedError
|
62
|
+
end
|
63
|
+
|
64
|
+
# returns true/false/nil based on if data identified by the key & parameters
|
65
|
+
# is persisted in the store.
|
66
|
+
#
|
67
|
+
# @param [#to_s] key the key used to identify an entry
|
68
|
+
# @param [Hash] parameters optional parameters used to identify an entry
|
69
|
+
# @return [TrueClass] true if the key and parameters match an entry in the store, false otherwise
|
70
|
+
# @raise [NotImplementedError] API method has not been implemented
|
71
|
+
def exists?(key, parameters = {})
|
72
|
+
raise NotImplementedError
|
73
|
+
end
|
74
|
+
|
75
|
+
# deletes the entry for the key & parameter from the store.
|
76
|
+
#
|
77
|
+
# @param [#to_s] key the key used to identify an entry
|
78
|
+
# @param [Hash] parameters optional parameters used to identify an entry
|
79
|
+
# @raise [TrueClass] true if the an entry matching the key and parameters is successfully deleted, false otherwise
|
80
|
+
# @raise [NotImplementedError] API method has not been implemented
|
81
|
+
def delete(key, parameters = {})
|
82
|
+
raise NotImplementedError
|
83
|
+
end
|
84
|
+
|
85
|
+
# deletes all entries for the key & parameters for the store.
|
86
|
+
#
|
87
|
+
# @return [TrueClass] true if all entries in the store are erased, false otherwise
|
88
|
+
# @raise [NotImplementedError] API method has not been implemented
|
89
|
+
def delete_all
|
90
|
+
raise NotImplementedError
|
91
|
+
end
|
92
|
+
|
93
|
+
# dangerous version of delete_all. Used by strategy stores, which may delete entries not associated with the strategy
|
94
|
+
# store making the call.
|
95
|
+
#
|
96
|
+
# @return [TrueClass] true if all entries in the store are erased, false otherwise
|
97
|
+
# @raise [NotImplementedError] API method has not been implemented
|
98
|
+
def delete_all!
|
99
|
+
delete_all
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Merb::Cache
|
2
|
+
# Cache store that uses files. Usually this is good for fragment
|
3
|
+
# and page caching but not object caching.
|
4
|
+
#
|
5
|
+
# By default cached files are stored in tmp/cache under Merb.root directory.
|
6
|
+
# To use other location pass :dir option to constructor.
|
7
|
+
#
|
8
|
+
# File caching does not support expiration time.
|
9
|
+
class FileStore < AbstractStore
|
10
|
+
attr_accessor :dir
|
11
|
+
|
12
|
+
# Creates directory for cached files unless it exists.
|
13
|
+
def initialize(config = {})
|
14
|
+
@dir = config[:dir] || Merb.root_path(:tmp / :cache)
|
15
|
+
|
16
|
+
create_path(@dir)
|
17
|
+
end
|
18
|
+
|
19
|
+
# File caching does not support expiration time.
|
20
|
+
def writable?(key, parameters = {}, conditions = {})
|
21
|
+
case key
|
22
|
+
when String, Numeric, Symbol
|
23
|
+
!conditions.has_key?(:expire_in)
|
24
|
+
else nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Reads cached template from disk if it exists.
|
29
|
+
def read(key, parameters = {})
|
30
|
+
if exists?(key, parameters)
|
31
|
+
read_file(pathify(key, parameters))
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Writes cached template to disk, creating cache directory
|
36
|
+
# if it does not exist.
|
37
|
+
def write(key, data = nil, parameters = {}, conditions = {})
|
38
|
+
if writable?(key, parameters, conditions)
|
39
|
+
if File.file?(path = pathify(key, parameters))
|
40
|
+
write_file(path, data)
|
41
|
+
else
|
42
|
+
create_path(path) && write_file(path, data)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Fetches cached data by key if it exists. If it does not,
|
48
|
+
# uses passed block to get new cached value and writes it
|
49
|
+
# using given key.
|
50
|
+
def fetch(key, parameters = {}, conditions = {}, &blk)
|
51
|
+
read(key, parameters) || (writable?(key, parameters, conditions) && write(key, value = blk.call, parameters, conditions) && value)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Checks if cached template with given key exists.
|
55
|
+
def exists?(key, parameters = {})
|
56
|
+
File.file?(pathify(key, parameters))
|
57
|
+
end
|
58
|
+
|
59
|
+
# Deletes cached template by key using FileUtils#rm.
|
60
|
+
def delete(key, parameters = {})
|
61
|
+
if File.file?(path = pathify(key, parameters))
|
62
|
+
FileUtils.rm(path)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def delete_all
|
67
|
+
raise NotSupportedError
|
68
|
+
end
|
69
|
+
|
70
|
+
def pathify(key, parameters = {})
|
71
|
+
if key.to_s =~ /^\//
|
72
|
+
path = "#{@dir}#{key}"
|
73
|
+
else
|
74
|
+
path = "#{@dir}/#{key}"
|
75
|
+
end
|
76
|
+
|
77
|
+
path << "--#{parameters.to_sha2}" unless parameters.empty?
|
78
|
+
path
|
79
|
+
end
|
80
|
+
|
81
|
+
protected
|
82
|
+
|
83
|
+
def create_path(path)
|
84
|
+
FileUtils.mkdir_p(File.dirname(path))
|
85
|
+
end
|
86
|
+
|
87
|
+
# Reads file content. Access to the file
|
88
|
+
# uses file locking.
|
89
|
+
def read_file(path)
|
90
|
+
data = nil
|
91
|
+
File.open(path, "r") do |file|
|
92
|
+
file.flock(File::LOCK_EX)
|
93
|
+
data = file.read
|
94
|
+
file.flock(File::LOCK_UN)
|
95
|
+
end
|
96
|
+
|
97
|
+
data
|
98
|
+
end
|
99
|
+
|
100
|
+
# Writes file content. Access to the file
|
101
|
+
# uses file locking.
|
102
|
+
def write_file(path, data)
|
103
|
+
File.open(path, "w+") do |file|
|
104
|
+
file.flock(File::LOCK_EX)
|
105
|
+
file.write(data)
|
106
|
+
file.flock(File::LOCK_UN)
|
107
|
+
end
|
108
|
+
|
109
|
+
true
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
require 'memcached'
|
2
|
+
|
3
|
+
module Merb::Cache
|
4
|
+
# Memcached store uses one or several Memcached
|
5
|
+
# servers for caching. It's flexible and can be used
|
6
|
+
# for fragment caching, action caching, page caching
|
7
|
+
# or object caching.
|
8
|
+
class MemcachedStore < AbstractStore
|
9
|
+
attr_accessor :namespace, :servers, :memcached
|
10
|
+
|
11
|
+
def initialize(config = {})
|
12
|
+
@namespace = config[:namespace]
|
13
|
+
@servers = config[:servers] || ["127.0.0.1:11211"]
|
14
|
+
|
15
|
+
connect(config)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Memcached store consideres all keys and parameters
|
19
|
+
# writable.
|
20
|
+
def writable?(key, parameters = {}, conditions = {})
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
# Reads key from the cache.
|
25
|
+
def read(key, parameters = {})
|
26
|
+
begin
|
27
|
+
@memcached.get(normalize(key, parameters))
|
28
|
+
rescue Memcached::NotFound, Memcached::Stored
|
29
|
+
nil
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Writes data to the cache using key, parameters and conditions.
|
34
|
+
def write(key, data = nil, parameters = {}, conditions = {})
|
35
|
+
if writable?(key, parameters, conditions)
|
36
|
+
begin
|
37
|
+
@memcached.set(normalize(key, parameters), data, expire_time(conditions))
|
38
|
+
true
|
39
|
+
rescue
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Fetches cached data by key if it exists. If it does not,
|
46
|
+
# uses passed block to get new cached value and writes it
|
47
|
+
# using given key.
|
48
|
+
def fetch(key, parameters = {}, conditions = {}, &blk)
|
49
|
+
read(key, parameters) || (writable?(key, parameters, conditions) && write(key, value = blk.call, parameters, conditions) && value)
|
50
|
+
end
|
51
|
+
|
52
|
+
# returns true/false/nil based on if data identified by the key & parameters
|
53
|
+
# is persisted in the store.
|
54
|
+
#
|
55
|
+
# With Memcached 1.2 protocol the only way to
|
56
|
+
# find if key exists in the cache is to read it.
|
57
|
+
# It is very fast and shouldn't be a concern.
|
58
|
+
def exists?(key, parameters = {})
|
59
|
+
begin
|
60
|
+
@memcached.get(normalize(key, parameters)) && true
|
61
|
+
rescue Memcached::Stored
|
62
|
+
true
|
63
|
+
rescue Memcached::NotFound
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Deletes entry from cached by key.
|
69
|
+
def delete(key, parameters = {})
|
70
|
+
begin
|
71
|
+
@memcached.delete(normalize(key, parameters))
|
72
|
+
rescue Memcached::NotFound
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Flushes the cache.
|
78
|
+
def delete_all
|
79
|
+
@memcached.flush
|
80
|
+
end
|
81
|
+
|
82
|
+
def clone
|
83
|
+
twin = super
|
84
|
+
twin.memcached = @memcached.clone
|
85
|
+
twin
|
86
|
+
end
|
87
|
+
|
88
|
+
# Establishes connection to Memcached.
|
89
|
+
#
|
90
|
+
# Use :buffer_requests option to use bufferring,
|
91
|
+
# :no_block to use non-blocking async I/O.
|
92
|
+
def connect(config = {})
|
93
|
+
@memcached = ::Memcached.new(@servers, config.only(:buffer_requests, :no_block).merge(:namespace => @namespace))
|
94
|
+
end
|
95
|
+
|
96
|
+
# Returns cache key calculated from base key
|
97
|
+
# and SHA2 hex from parameters.
|
98
|
+
def normalize(key, parameters = {})
|
99
|
+
parameters.empty? ? "#{key}" : "#{key}--#{parameters.to_sha2}"
|
100
|
+
end
|
101
|
+
|
102
|
+
# Returns expiration timestamp if :expire_in key is
|
103
|
+
# given.
|
104
|
+
def expire_time(conditions = {})
|
105
|
+
if t = conditions[:expire_in]
|
106
|
+
Time.now + t
|
107
|
+
else
|
108
|
+
0
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# -*- coding: undecided -*-
|
2
|
+
module Merb::Cache
|
3
|
+
# A strategy store wraps one or
|
4
|
+
# more fundamental stores, acting as a middle man between caching
|
5
|
+
# requests.
|
6
|
+
#
|
7
|
+
# For example, if you need to save memory on your Memcache server,
|
8
|
+
# you could wrap your MemcachedStore with a GzipStore. This would
|
9
|
+
# automatically compress the cached data when put into the cache, and
|
10
|
+
# decompress it on the way out. You can even wrap strategy caches
|
11
|
+
# with other strategy caches. If your key was comprised of sensitive
|
12
|
+
# information, like a SSN, you might want to encrypt the key before
|
13
|
+
# storage. Wrapping your GzipStore in a SHA1Store would take
|
14
|
+
# care of that for you.
|
15
|
+
#
|
16
|
+
# The AbstractStore class defines 9 methods as the API:
|
17
|
+
#
|
18
|
+
# writable?(key, parameters = {}, conditions = {})
|
19
|
+
# exists?(key, parameters = {})
|
20
|
+
# read(key, parameters = {})
|
21
|
+
# write(key, data = nil, parameters = {}, conditions = {})
|
22
|
+
# write_all(key, data = nil, parameters = {}, conditions = {})
|
23
|
+
# fetch(key, parameters = {}, conditions = {}, &blk)
|
24
|
+
# delete(key, parameters = {})
|
25
|
+
# delete_all
|
26
|
+
# delete_all!
|
27
|
+
#
|
28
|
+
# AbstractStrategyStore implements all of these with the exception
|
29
|
+
# of delete_all. If a strategy store can guarantee that calling
|
30
|
+
# delete_all on it’s wrapped store(s) will only delete entries
|
31
|
+
# populated by the strategy store, it may define the safe
|
32
|
+
# version of delete_all. However, this is usually not the
|
33
|
+
# case, hence delete_all is not part of the
|
34
|
+
# public API for AbstractStrategyStore.
|
35
|
+
class AbstractStrategyStore < AbstractStore
|
36
|
+
# START: interface for creating strategy stores. This should/might change.
|
37
|
+
def self.contextualize(*stores)
|
38
|
+
Class.new(self) do
|
39
|
+
cattr_accessor :contextualized_stores
|
40
|
+
|
41
|
+
self.contextualized_stores = stores
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class << self
|
46
|
+
alias_method :[], :contextualize
|
47
|
+
end
|
48
|
+
|
49
|
+
attr_accessor :stores
|
50
|
+
|
51
|
+
def initialize(config = {})
|
52
|
+
@stores = contextualized_stores.map do |cs|
|
53
|
+
case cs
|
54
|
+
when Symbol
|
55
|
+
Merb::Cache[cs]
|
56
|
+
when Class
|
57
|
+
cs.new(config)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
# END: interface for creating strategy stores.
|
63
|
+
|
64
|
+
attr_accessor :stores
|
65
|
+
|
66
|
+
# determines if the store is able to persist data identified by the key & parameters
|
67
|
+
# with the given conditions.
|
68
|
+
def writable?(key, parameters = {}, conditions = {})
|
69
|
+
raise NotImplementedError
|
70
|
+
end
|
71
|
+
|
72
|
+
# gets the data from the store identified by the key & parameters.
|
73
|
+
# return nil if the entry does not exist.
|
74
|
+
def read(key, parameters = {})
|
75
|
+
raise NotImplementedError
|
76
|
+
end
|
77
|
+
|
78
|
+
# persists the data so that it can be retrieved by the key & parameters.
|
79
|
+
# returns nil if it is unable to persist the data.
|
80
|
+
# returns true if successful.
|
81
|
+
def write(key, data = nil, parameters = {}, conditions = {})
|
82
|
+
raise NotImplementedError
|
83
|
+
end
|
84
|
+
|
85
|
+
# persists the data to all context stores.
|
86
|
+
# returns nil if none of the stores were able to persist the data.
|
87
|
+
# returns true if at least one write was successful.
|
88
|
+
def write_all(key, data = nil, parameters = {}, conditions = {})
|
89
|
+
raise NotImplementedError
|
90
|
+
end
|
91
|
+
|
92
|
+
# tries to read the data from the store. If that fails, it calls
|
93
|
+
# the block parameter and persists the result.
|
94
|
+
def fetch(key, parameters = {}, conditions = {}, &blk)
|
95
|
+
raise NotImplementedError
|
96
|
+
end
|
97
|
+
|
98
|
+
# returns true/false/nil based on if data identified by the key & parameters
|
99
|
+
# is persisted in the store.
|
100
|
+
def exists?(key, parameters = {})
|
101
|
+
raise NotImplementedError
|
102
|
+
end
|
103
|
+
|
104
|
+
# deletes the entry for the key & parameter from the store.
|
105
|
+
def delete(key, parameters = {})
|
106
|
+
raise NotImplementedError
|
107
|
+
end
|
108
|
+
|
109
|
+
# deletes all entries for the key & parameters for the store.
|
110
|
+
# considered dangerous because strategy stores which call delete_all!
|
111
|
+
# on their context stores could delete other store's entrees.
|
112
|
+
def delete_all!
|
113
|
+
raise NotImplementedError
|
114
|
+
end
|
115
|
+
|
116
|
+
def clone
|
117
|
+
twin = super
|
118
|
+
twin.stores = self.stores.map {|s| s.clone}
|
119
|
+
twin
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Merb::Cache
|
2
|
+
# Store well suited for action caching.
|
3
|
+
class ActionStore < AbstractStrategyStore
|
4
|
+
def writable?(dispatch, parameters = {}, conditions = {})
|
5
|
+
case dispatch
|
6
|
+
when Merb::Controller
|
7
|
+
@stores.any?{|s| s.writable?(normalize(dispatch), parameters, conditions)}
|
8
|
+
else false
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def read(dispatch, parameters = {})
|
13
|
+
if writable?(dispatch, parameters)
|
14
|
+
@stores.capture_first {|s| s.read(normalize(dispatch), parameters)}
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def write(dispatch, data = nil, parameters = {}, conditions = {})
|
19
|
+
if writable?(dispatch, parameters, conditions)
|
20
|
+
@stores.capture_first {|s| s.write(normalize(dispatch), data || dispatch.body, parameters, conditions)}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def write_all(dispatch, data = nil, parameters = {}, conditions = {})
|
25
|
+
if writable?(dispatch, parameters, conditions)
|
26
|
+
@stores.map {|s| s.write_all(normalize(dispatch), data || dispatch.body, parameters, conditions)}.all?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def fetch(dispatch, parameters = {}, conditions = {}, &blk)
|
31
|
+
if writable?(dispatch, parameters, conditions)
|
32
|
+
read(dispatch, parameters) || @stores.capture_first {|s| s.fetch(normalize(dispatch), data || dispatch.body, parameters, conditions, &blk)}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def exists?(dispatch, parameters = {})
|
37
|
+
if writable?(dispatch, parameters)
|
38
|
+
@stores.capture_first {|s| s.exists?(normalize(dispatch), parameters)}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def delete(dispatch, parameters = {})
|
43
|
+
if writable?(dispatch, parameters)
|
44
|
+
@stores.map {|s| s.delete(normalize(dispatch), parameters)}.any?
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def delete_all!
|
49
|
+
@stores.map {|s| s.delete_all!}.all?
|
50
|
+
end
|
51
|
+
|
52
|
+
def normalize(dispatch)
|
53
|
+
"#{dispatch.class.name}##{dispatch.action_name}" unless dispatch.class.name.blank? || dispatch.action_name.blank?
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|