merb-cache 0.9.7 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
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