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.
Files changed (55) hide show
  1. data/LICENSE +2 -2
  2. data/README +207 -143
  3. data/Rakefile +55 -10
  4. data/TODO +0 -2
  5. data/lib/merb-cache/cache.rb +84 -0
  6. data/lib/merb-cache/core_ext/enumerable.rb +9 -0
  7. data/lib/merb-cache/core_ext/hash.rb +20 -0
  8. data/lib/merb-cache/merb_ext/controller.rb +167 -0
  9. data/lib/merb-cache/stores/fundamental/abstract_store.rb +101 -0
  10. data/lib/merb-cache/stores/fundamental/file_store.rb +112 -0
  11. data/lib/merb-cache/stores/fundamental/memcached_store.rb +112 -0
  12. data/lib/merb-cache/stores/strategy/abstract_strategy_store.rb +123 -0
  13. data/lib/merb-cache/stores/strategy/action_store.rb +56 -0
  14. data/lib/merb-cache/stores/strategy/adhoc_store.rb +69 -0
  15. data/lib/merb-cache/stores/strategy/gzip_store.rb +63 -0
  16. data/lib/merb-cache/stores/strategy/page_store.rb +64 -0
  17. data/lib/merb-cache/stores/strategy/sha1_store.rb +62 -0
  18. data/lib/merb-cache.rb +8 -7
  19. data/spec/merb-cache/cache_spec.rb +88 -0
  20. data/spec/merb-cache/core_ext/enumerable_spec.rb +22 -0
  21. data/spec/merb-cache/core_ext/hash_spec.rb +20 -0
  22. data/spec/merb-cache/merb_ext/controller_spec.rb +284 -0
  23. data/spec/merb-cache/stores/fundamental/abstract_store_spec.rb +166 -0
  24. data/spec/merb-cache/stores/fundamental/file_store_spec.rb +186 -0
  25. data/spec/merb-cache/stores/fundamental/memcached_store_spec.rb +243 -0
  26. data/spec/merb-cache/stores/strategy/abstract_strategy_store_spec.rb +78 -0
  27. data/spec/merb-cache/stores/strategy/action_store_spec.rb +189 -0
  28. data/spec/merb-cache/stores/strategy/adhoc_store_spec.rb +225 -0
  29. data/spec/merb-cache/stores/strategy/gzip_store_spec.rb +51 -0
  30. data/spec/merb-cache/stores/strategy/page_store_spec.rb +111 -0
  31. data/spec/merb-cache/stores/strategy/sha1_store_spec.rb +75 -0
  32. data/spec/spec_helper.rb +69 -72
  33. metadata +42 -31
  34. data/lib/merb-cache/cache-action.rb +0 -144
  35. data/lib/merb-cache/cache-fragment.rb +0 -95
  36. data/lib/merb-cache/cache-page.rb +0 -203
  37. data/lib/merb-cache/cache-store/database-activerecord.rb +0 -88
  38. data/lib/merb-cache/cache-store/database-datamapper.rb +0 -79
  39. data/lib/merb-cache/cache-store/database-sequel.rb +0 -78
  40. data/lib/merb-cache/cache-store/database.rb +0 -144
  41. data/lib/merb-cache/cache-store/dummy.rb +0 -106
  42. data/lib/merb-cache/cache-store/file.rb +0 -194
  43. data/lib/merb-cache/cache-store/memcache.rb +0 -199
  44. data/lib/merb-cache/cache-store/memory.rb +0 -168
  45. data/lib/merb-cache/merb-cache.rb +0 -165
  46. data/lib/merb-cache/merbtasks.rb +0 -6
  47. data/spec/config/database.yml +0 -14
  48. data/spec/controller.rb +0 -101
  49. data/spec/log/merb_test.log +0 -433
  50. data/spec/merb-cache-action_spec.rb +0 -162
  51. data/spec/merb-cache-fragment_spec.rb +0 -100
  52. data/spec/merb-cache-page_spec.rb +0 -150
  53. data/spec/merb-cache_spec.rb +0 -15
  54. data/spec/views/cache_controller/action1.html.erb +0 -4
  55. 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