merb-cache 0.9.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,195 @@
1
+ class Merb::Cache::MemcacheStore
2
+ # Provides the memcache cache store for merb-cache
3
+
4
+ def initialize
5
+ @config = Merb::Controller._cache.config
6
+ prepare
7
+ end
8
+
9
+ class NotReady < Exception #:nodoc:
10
+ def initialize
11
+ super("Memcache server is not ready")
12
+ end
13
+ end
14
+
15
+ class NotDefined < Exception #:nodoc:
16
+ def initialize
17
+ super("Memcache is not defined (require it in init.rb)")
18
+ end
19
+ end
20
+
21
+ # This method is there to ensure minimal requirements are met
22
+ # (directories are accessible, table exists, connected to server, ...)
23
+ def prepare
24
+ namespace = @config[:namespace] || 'merb-cache'
25
+ host = @config[:host] || '127.0.0.1:11211'
26
+ @memcache = MemCache.new(host, {:namespace => namespace})
27
+ @tracking_key = "_#{namespace}_keys" unless @config[:no_tracking]
28
+ raise NotReady unless @memcache.active?
29
+ true
30
+ rescue NameError
31
+ raise NotDefined
32
+ end
33
+
34
+ # Checks whether a cache entry exists
35
+ #
36
+ # ==== Parameter
37
+ # key<String>:: The key identifying the cache entry
38
+ #
39
+ # ==== Returns
40
+ # true if the cache entry exists, false otherwise
41
+ def cached?(key)
42
+ not @memcache.get(key).nil?
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
+ _data = @memcache.get(key)
66
+ if _data.nil?
67
+ _expire = from_now ? from_now.minutes.from_now.to_i : 0
68
+ _data = _controller.send(:capture, &block)
69
+ @memcache.set(key, _data, _expire)
70
+ end
71
+ _controller.send(:concat, _data, block.binding)
72
+ true
73
+ end
74
+
75
+ # Store data to memcache using the specified key
76
+ #
77
+ # ==== Parameters
78
+ # key<Sting>:: The key identifying the cache entry
79
+ # data<String>:: The data to be put in cache
80
+ # from_now<~minutes>::
81
+ # The number of minutes (from now) the cache should persist
82
+ def cache_set(key, data, from_now = nil)
83
+ _expire = from_now ? from_now.minutes.from_now.to_i : 0
84
+ @memcache.set(key, data, _expire)
85
+ cache_start_tracking(key)
86
+ Merb.logger.info("cache: set (#{key})")
87
+ true
88
+ end
89
+
90
+ # Fetch data from memcache using the specified key
91
+ # The entry is deleted if it has expired
92
+ #
93
+ # ==== Parameter
94
+ # key<Sting>:: The key identifying the cache entry
95
+ #
96
+ # ==== Returns
97
+ # data<String, NilClass>::
98
+ # nil is returned whether the entry expired or was not found
99
+ def cache_get(key)
100
+ data = @memcache.get(key)
101
+ Merb.logger.info("cache: #{data.nil? ? "miss" : "hit"} (#{key})")
102
+ data
103
+ end
104
+
105
+ # Expire the cache entry identified by the given key
106
+ #
107
+ # ==== Parameter
108
+ # key<Sting>:: The key identifying the cache entry
109
+ def expire(key)
110
+ @memcache.delete(key)
111
+ cache_stop_tracking(key)
112
+ Merb.logger.info("cache: expired (#{key})")
113
+ true
114
+ end
115
+
116
+ # Expire the cache entries matching the given key
117
+ #
118
+ # ==== Parameter
119
+ # key<Sting>:: The key matching the cache entries
120
+ #
121
+ # ==== Additional info
122
+ # In memcache this requires to keep track of all keys (on by default).
123
+ # If you don't need this, set :no_tracking => true in the config.
124
+ def expire_match(key)
125
+ if @tracking_key
126
+ for _key in get_tracked_keys
127
+ expire(_key) if /#{key}/ =~ _key
128
+ end
129
+ else
130
+ Merb.logger.info("cache: expire_match is not supported with memcache (set :no_tracking => false in your config")
131
+ end
132
+ true
133
+ end
134
+
135
+ # Expire all the cache entries
136
+ def expire_all
137
+ @memcache.flush_all
138
+ stop_tracking_keys
139
+ Merb.logger.info("cache: expired all")
140
+ true
141
+ end
142
+
143
+ # Gives info on the current cache store
144
+ #
145
+ # ==== Returns
146
+ # The type of the current cache store
147
+ def cache_store_type
148
+ "memcache"
149
+ end
150
+
151
+ private
152
+
153
+ # Store the tracked keys in memcache (used by expire_match)
154
+ #
155
+ # ==== Parameter
156
+ # keys<Array[String]>:: The keys to keep track of
157
+ def set_tracked_keys(keys)
158
+ @memcache.set(@tracking_key, keys)
159
+ end
160
+
161
+ # Retrieve tracked keys from memcache
162
+ #
163
+ # ==== Returns
164
+ # keys<Array[String]>:: The tracked keys
165
+ def get_tracked_keys
166
+ @memcache.get(@tracking_key) || []
167
+ end
168
+
169
+ # Remove all tracked keys
170
+ def stop_tracking_keys
171
+ @memcache.delete(@tracking_key) if @tracking_key
172
+ end
173
+
174
+ # Add a key in the array of tracked keys (used by expire_match)
175
+ #
176
+ # ==== Parameter
177
+ # key<String>:: the key to add
178
+ def cache_start_tracking(key)
179
+ return unless @tracking_key
180
+ keys = get_tracked_keys
181
+ keys.push(key)
182
+ set_tracked_keys(keys)
183
+ end
184
+
185
+ # Remove a key from the array of tracked keys (used by expire_match)
186
+ #
187
+ # ==== Parameter
188
+ # key<String>:: the key to remove
189
+ def cache_stop_tracking(key)
190
+ return unless @tracking_key
191
+ keys = get_tracked_keys
192
+ keys.delete(key)
193
+ set_tracked_keys(keys)
194
+ end
195
+ end
@@ -0,0 +1,168 @@
1
+ class Merb::Cache::MemoryStore
2
+ # Provides the memory cache store for merb-cache
3
+
4
+ def initialize
5
+ @config = Merb::Controller._cache.config
6
+ @cache = {}
7
+ @mutex = Mutex.new
8
+ prepare
9
+ end
10
+
11
+ # This method is there to ensure minimal requirements are met
12
+ # (directories are accessible, table exists, connected to server, ...)
13
+ def prepare
14
+ true
15
+ end
16
+
17
+ # Checks whether a cache entry exists
18
+ #
19
+ # ==== Parameter
20
+ # key<String>:: The key identifying the cache entry
21
+ #
22
+ # ==== Returns
23
+ # true if the cache entry exists, false otherwise
24
+ def cached?(key)
25
+ if @cache.key?(key)
26
+ _data, _expire = *cache_read(key)
27
+ return true if _expire.nil? || Time.now < _expire
28
+ expire(key)
29
+ end
30
+ false
31
+ end
32
+
33
+ # Capture or restore the data in cache.
34
+ # If the cache entry expired or does not exist, the data are taken
35
+ # from the execution of the block, marshalled and stored in cache.
36
+ # Otherwise, the data are loaded from the cache and returned unmarshalled
37
+ #
38
+ # ==== Parameters
39
+ # _controller<Merb::Controller>:: The instance of the current controller
40
+ # key<String>:: The key identifying the cache entry
41
+ # from_now<~minutes>::
42
+ # The number of minutes (from now) the cache should persist
43
+ # &block:: The template to be used or not
44
+ #
45
+ # ==== Additional information
46
+ # When fetching data (the cache entry exists and has not expired)
47
+ # The data are loaded from the cache and returned unmarshalled.
48
+ # Otherwise:
49
+ # The controller is used to capture the rendered template (from the block).
50
+ # It uses the capture_#{engine} and concat_#{engine} methods to do so.
51
+ # The captured data are then marshalled and stored.
52
+ def cache(_controller, key, from_now = nil, &block)
53
+ if @cache.key?(key)
54
+ _data, _expire = *cache_read(key)
55
+ _cache_hit = _expire.nil? || Time.now < _expire
56
+ end
57
+ unless _cache_hit
58
+ _expire = from_now ? from_now.minutes.from_now : nil
59
+ _data = _controller.send(:capture, &block)
60
+ cache_write(key, [_data, _expire])
61
+ end
62
+ _controller.send(:concat, _data, block.binding)
63
+ true
64
+ end
65
+
66
+ # Store data to the file using the specified key
67
+ #
68
+ # ==== Parameters
69
+ # key<Sting>:: The key identifying the cache entry
70
+ # data<String>:: The data to be put in cache
71
+ # from_now<~minutes>::
72
+ # The number of minutes (from now) the cache should persist
73
+ def cache_set(key, data, from_now = nil)
74
+ _expire = from_now ? from_now.minutes.from_now : nil
75
+ cache_write(key, [data, _expire])
76
+ Merb.logger.info("cache: set (#{key})")
77
+ true
78
+ end
79
+
80
+ # Fetch data from the file using the specified key
81
+ # The entry is deleted if it has expired
82
+ #
83
+ # ==== Parameter
84
+ # key<Sting>:: The key identifying the cache entry
85
+ #
86
+ # ==== Returns
87
+ # data<String, NilClass>::
88
+ # nil is returned whether the entry expired or was not found
89
+ def cache_get(key)
90
+ if @cache.key?(key)
91
+ _data, _expire = *cache_read(key)
92
+ if _expire.nil? || Time.now < _expire
93
+ Merb.logger.info("cache: hit (#{key})")
94
+ return _data
95
+ end
96
+ @mutex.synchronize do @cache.delete(key) end
97
+ end
98
+ Merb.logger.info("cache: miss (#{key})")
99
+ nil
100
+ end
101
+
102
+ # Expire the cache entry identified by the given key
103
+ #
104
+ # ==== Parameter
105
+ # key<Sting>:: The key identifying the cache entry
106
+ def expire(key)
107
+ @mutex.synchronize do
108
+ @cache.delete(key)
109
+ end
110
+ Merb.logger.info("cache: expired (#{key})")
111
+ true
112
+ end
113
+
114
+ # Expire the cache entries matching the given key
115
+ #
116
+ # ==== Parameter
117
+ # key<Sting>:: The key matching the cache entries
118
+ def expire_match(key)
119
+ @mutex.synchronize do
120
+ @cache.delete_if do |k,v| k.match(/#{key}/) end
121
+ end
122
+ Merb.logger.info("cache: expired matching (#{key})")
123
+ true
124
+ end
125
+
126
+ # Expire all the cache entries
127
+ def expire_all
128
+ @mutex.synchronize do
129
+ @cache.clear
130
+ end
131
+ Merb.logger.info("cache: expired all")
132
+ true
133
+ end
134
+
135
+ # Gives info on the current cache store
136
+ #
137
+ # ==== Returns
138
+ # The type of the current cache store
139
+ def cache_store_type
140
+ "memory"
141
+ end
142
+
143
+ private
144
+
145
+ # Read data from the memory hash table using mutex
146
+ #
147
+ # ==== Parameters
148
+ # cache_file<String>:: The key identifying the cache entry
149
+ #
150
+ # ==== Returns
151
+ # _data<String>:: The data fetched from the cache
152
+ def cache_read(key)
153
+ @mutex.synchronize do
154
+ @cache[key]
155
+ end
156
+ end
157
+
158
+ # Write data to the memory hash table using mutex
159
+ #
160
+ # ==== Parameters
161
+ # cache_file<String>:: The key identifying the cache entry
162
+ # data<String>:: The data to be put in cache
163
+ def cache_write(key, data)
164
+ @mutex.synchronize do
165
+ @cache[key] = data
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,164 @@
1
+ require "merb-cache/cache-action"
2
+ require "merb-cache/cache-page"
3
+ require "merb-cache/cache-fragment"
4
+
5
+ class Merb::Cache
6
+ attr_reader :config, :store
7
+
8
+ class StoreNotFound < Exception #:nodoc:
9
+ def initialize(cache_store)
10
+ super("cache_store (#{cache_store}) not found (not implemented?)")
11
+ end
12
+ end
13
+
14
+ DEFAULT_CONFIG = {
15
+ :cache_html_directory => Merb.dir_for(:public) / "cache",
16
+
17
+ #:store => "database",
18
+ #:table_name => "merb_cache",
19
+
20
+ #:disable => "development", # disable merb-cache in development
21
+ #:disable => true, # disable merb-cache in all environments
22
+
23
+ :store => "file",
24
+ :cache_directory => Merb.root_path("tmp/cache"),
25
+
26
+ #:store => "memcache",
27
+ #:host => "127.0.0.1:11211",
28
+ #:namespace => "merb_cache",
29
+ #:track_keys => true,
30
+
31
+ #:store => "memory",
32
+ # store could be: file, memcache, memory, database, dummy, ...
33
+ }
34
+
35
+ # Called in the after_app_loads loop and instantiate the right backend
36
+ #
37
+ # ==== Raises
38
+ # Store#NotFound::
39
+ # If the cache_store mentionned in the config is unknown
40
+ def start
41
+ @config = DEFAULT_CONFIG.merge(Merb::Plugins.config[:merb_cache] || {})
42
+ if @config[:disable] == true || Merb.environment == @config[:disable]
43
+ config[:store] = "dummy"
44
+ end
45
+ @config[:cache_html_directory] ||= Merb.dir_for(:public) / "cache"
46
+ require "merb-cache/cache-store/#{@config[:store]}"
47
+ @store = Merb::Cache.const_get("#{@config[:store].capitalize}Store").new
48
+ Merb.logger.info("Using #{@config[:store]} cache")
49
+ rescue LoadError
50
+ raise Merb::Cache::StoreNotFound, @config[:store].inspect
51
+ end
52
+
53
+ # Compute a cache key and yield it to the given block
54
+ # It is used by the #expire_page, #expire_action and #expire methods.
55
+ #
56
+ # ==== Parameters
57
+ # options<String, Hash>:: The key or the Hash that will be used to build the key
58
+ # controller<String>:: The name of the controller
59
+ # controller_based<Boolean>:: only used by action and page caching
60
+ #
61
+ # ==== Options (options)
62
+ # :key<String>:: The complete or partial key that will be computed.
63
+ # :action<String>:: The action name that will be used to compute the key
64
+ # :controller<String>:: The controller name that will be part of the key
65
+ # :params<Array[String]>::
66
+ # The params will be joined together (with '/') and added to the key
67
+ # :match<Boolean, String>::
68
+ # true, it will try to match multiple cache entries
69
+ # string, shortcut for {:key => "mykey", :match => true}
70
+ #
71
+ # ==== Examples
72
+ # expire(:key => "root_key", :params => [session[:me], params[:id]])
73
+ # expire(:match => "root_key")
74
+ # expire_action(:action => 'list')
75
+ # expire_page(:action => 'show', :controller => 'news')
76
+ #
77
+ # ==== Returns
78
+ # The result of the given block
79
+ #
80
+ def expire_key_for(options, controller, controller_based = false)
81
+ key = ""
82
+ if options.is_a? Hash
83
+ case
84
+ when key = options[:key]
85
+ when action = options[:action]
86
+ controller = options[:controller] || controller
87
+ key = "/#{controller}/#{action}"
88
+ when match = options[:match]
89
+ key = match
90
+ end
91
+ if _params = options[:params]
92
+ key += "/" + _params.join("/")
93
+ end
94
+ yield key, !options[:match].nil?
95
+ else
96
+ yield controller_based ? "/#{controller}/#{options}" : options, false
97
+ end
98
+ end
99
+
100
+ # Compute a cache key based on the given parameters
101
+ # Only used by the #cached_page?, #cached_action?, #cached?, #cache,
102
+ # #cache_get and #cache_set methods
103
+ #
104
+ # ==== Parameters
105
+ # options<String, Hash>:: The key or the Hash that will be used to build the key
106
+ # controller<String>:: The name of the controller
107
+ # controller_based<Boolean>:: only used by action and page caching
108
+ #
109
+ # ==== Options (options)
110
+ # :key<String>:: The complete or partial key that will be computed.
111
+ # :action<String>:: The action name that will be used to compute the key
112
+ # :controller<String>:: The controller name that will be part of the key
113
+ # :params<Array[String]>::
114
+ # The params will be joined together (with '/') and added to the key
115
+ #
116
+ # ==== Examples
117
+ # cache_set("my_key", @data)
118
+ # cache_get(:key => "root_key", :params => [session[:me], params[:id]])
119
+ #
120
+ # ==== Returns
121
+ # The computed key
122
+ def key_for(options, controller, controller_based = false)
123
+ key = ""
124
+ if options.is_a? Hash
125
+ case
126
+ when key = options[:key]
127
+ when action = options[:action]
128
+ controller = options[:controller] || controller
129
+ key = "/#{controller}/#{action}"
130
+ end
131
+ if _params = options[:params]
132
+ key += "/" + _params.join("/")
133
+ end
134
+ else
135
+ key = controller_based ? "/#{controller}/#{options}" : options
136
+ end
137
+ key
138
+ end
139
+
140
+ module ControllerInstanceMethods
141
+ # Mixed in Merb::Controller and provides expire_all for action and fragment caching.
142
+ def expire_all
143
+ Merb::Controller._cache.store.expire_all
144
+ end
145
+ end
146
+ end
147
+
148
+ module Merb #:nodoc:
149
+ class Controller #:nodoc:
150
+ cattr_reader :_cache
151
+ @@_cache = Merb::Cache.new
152
+ # extends Merb::Controller with new instance methods
153
+ include Merb::Cache::ControllerInstanceMethods
154
+ class << self #:nodoc:
155
+ # extends Merb::Controller with new class methods
156
+ include Merb::Cache::ControllerClassMethods
157
+ end
158
+ end
159
+ end
160
+
161
+ Merb::BootLoader.after_app_loads do
162
+ # the cache starts after the application is loaded
163
+ Merb::Controller._cache.start
164
+ end
@@ -0,0 +1,6 @@
1
+ namespace :merb_cache do
2
+ desc "Do something for merb-cache"
3
+ task :default do
4
+ puts "merb-cache doesn't do anything"
5
+ end
6
+ end
data/lib/merb-cache.rb ADDED
@@ -0,0 +1,10 @@
1
+ if defined?(Merb::Plugins)
2
+ Merb::Plugins.add_rakefiles "merb-cache/merbtasks"
3
+ unless 1.respond_to? :minutes
4
+ class Numeric #:nodoc:
5
+ def minutes; self * 60; end
6
+ def from_now(now = Time.now); now + self; end
7
+ end
8
+ end
9
+ require "merb-cache/merb-cache"
10
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: merb-cache
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.2
5
+ platform: ruby
6
+ authors:
7
+ - Alex Boussinet
8
+ autorequire: merb-cache
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-03-24 00:00:00 -05:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: merb-core
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.9.2
23
+ version:
24
+ description: Merb plugin that provides caching (page, action, fragment, object)
25
+ email: alex.boussinet@gmail.com
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files:
31
+ - README
32
+ - LICENSE
33
+ - TODO
34
+ files:
35
+ - LICENSE
36
+ - README
37
+ - Rakefile
38
+ - TODO
39
+ - lib/merb-cache
40
+ - lib/merb-cache/cache-action.rb
41
+ - lib/merb-cache/cache-fragment.rb
42
+ - lib/merb-cache/cache-page.rb
43
+ - lib/merb-cache/cache-store
44
+ - lib/merb-cache/cache-store/database-activerecord.rb
45
+ - lib/merb-cache/cache-store/database-datamapper.rb
46
+ - lib/merb-cache/cache-store/database-sequel.rb
47
+ - lib/merb-cache/cache-store/database.rb
48
+ - lib/merb-cache/cache-store/dummy.rb
49
+ - lib/merb-cache/cache-store/file.rb
50
+ - lib/merb-cache/cache-store/memcache.rb
51
+ - lib/merb-cache/cache-store/memory.rb
52
+ - lib/merb-cache/merb-cache.rb
53
+ - lib/merb-cache/merbtasks.rb
54
+ - lib/merb-cache.rb
55
+ has_rdoc: true
56
+ homepage: http://www.merbivore.com
57
+ post_install_message:
58
+ rdoc_options: []
59
+
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ version:
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: "0"
73
+ version:
74
+ requirements: []
75
+
76
+ rubyforge_project:
77
+ rubygems_version: 1.0.1
78
+ signing_key:
79
+ specification_version: 2
80
+ summary: Merb plugin that provides caching (page, action, fragment, object)
81
+ test_files: []
82
+