bchiu-merb_cache_more 0.9.4

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.
@@ -0,0 +1,95 @@
1
+ module Merb::Cache::ControllerInstanceMethods
2
+ # Mixed in Merb::Controller. Provides methods related to fragment caching
3
+
4
+ # Checks whether a cache entry exists
5
+ #
6
+ # ==== Parameter
7
+ # options<String,Hash>:: The options that will be passed to #key_for
8
+ #
9
+ # ==== Returns
10
+ # true if the cache entry exists, false otherwise
11
+ #
12
+ # ==== Example
13
+ # cached_action?("my_key")
14
+ def cached?(options)
15
+ key = Merb::Controller._cache.key_for(options, controller_name)
16
+ Merb::Controller._cache.store.cached?(key)
17
+ end
18
+
19
+ # ==== Example
20
+ # In your view:
21
+ # <%- cache("my_key") do -%>
22
+ # <%= partial :test, :collection => @test %>
23
+ # <%- end -%>
24
+ def cache(options, from_now = nil, &block)
25
+ key = Merb::Controller._cache.key_for(options, controller_name)
26
+ Merb::Controller._cache.store.cache(self, key, from_now, &block)
27
+ end
28
+
29
+ # Fetch data from cache
30
+ #
31
+ # ==== Parameter
32
+ # options<String,Hash>:: The options that will be passed to #key_for
33
+ #
34
+ # ==== Returns
35
+ # data<Object,NilClass>::
36
+ # nil is returned if the cache entry is not found
37
+ #
38
+ # ==== Example
39
+ # if cache_data = cache_get("my_key")
40
+ # @var1, @var2 = *cache_data
41
+ # else
42
+ # @var1 = MyModel.big_query1
43
+ # @var2 = MyModel.big_query2
44
+ # cache_set("my_key", nil, [@var1, @var2])
45
+ # end
46
+ def cache_get(options)
47
+ key = Merb::Controller._cache.key_for(options, controller_name)
48
+ Merb::Controller._cache.store.cache_get(key)
49
+ end
50
+
51
+ # Store data to cache
52
+ #
53
+ # ==== Parameter
54
+ # options<String,Hash>:: The options that will be passed to #key_for
55
+ # object<Object>:: The object(s) to put in cache
56
+ # from_now<~minutes>::
57
+ # The number of minutes (from now) the cache should persist
58
+ #
59
+ # ==== Returns
60
+ # data<Object,NilClass>::
61
+ # nil is returned if the cache entry is not found
62
+ #
63
+ # ==== Example
64
+ # if cache_data = cache_get("my_key")
65
+ # @var1, @var2 = *cache_data
66
+ # else
67
+ # @var1 = MyModel.big_query1
68
+ # @var2 = MyModel.big_query2
69
+ # cache_set("my_key", nil, [@var1, @var2])
70
+ # end
71
+ def cache_set(options, object, from_now = nil)
72
+ key = Merb::Controller._cache.key_for(options, controller_name)
73
+ Merb::Controller._cache.store.cache_set(key, object, from_now)
74
+ end
75
+
76
+ # Expires the entry identified by the key computed after the parameters
77
+ #
78
+ # ==== Parameter
79
+ # options<String,Hash>:: The options that will be passed to #key_for
80
+ #
81
+ # ==== Examples
82
+ # expire("my_key")
83
+ # expire(:key => "my_", :match => true)
84
+ # expire(:key => "my_key", :params => [session[:me], params[:ref]])
85
+ def expire(options)
86
+ Merb::Controller._cache.expire_key_for(options, controller_name) do |key, match|
87
+ if match
88
+ Merb::Controller._cache.store.expire_match(key)
89
+ else
90
+ Merb::Controller._cache.store.expire(key)
91
+ end
92
+ end
93
+ true
94
+ end
95
+ end
@@ -0,0 +1,248 @@
1
+ class Merb::Cache
2
+ cattr_accessor :cached_pages
3
+ self.cached_pages = {}
4
+ end
5
+
6
+ module Merb::Cache::ControllerClassMethods
7
+ # Mixed in Merb::Controller. Provides class methods related to page caching
8
+ # Page caching is mostly action caching with file backend using its own output directory of .html files
9
+
10
+ # Register the action for page caching
11
+ #
12
+ # ==== Parameters
13
+ # action<Symbol>:: The name of the action to register
14
+ # from_now<~minutes>::
15
+ # The number of minutes (from now) the cache should persist
16
+ # options<Hash>::
17
+ # Key formats :format => :snake|:tree|:hash|:query or nil for default
18
+ # Custom format :format => ":param1/:param2-and_:param3"
19
+ # Params :params => [params to include] or false to disable
20
+ #
21
+ # ==== Examples
22
+ # cache_page :mostly_static
23
+ # cache_page :barely_dynamic, 10
24
+ # cache_page :barely_dynamic, 10, {:format => :hash, :params => [:id, :name]}
25
+ # cache_page :barely_dynamic, :format => ":param1/:param2-and_:param3[key]"
26
+ def cache_page(action, from_now = nil, opts = {})
27
+ from_now, opts = nil, from_now if Hash === from_now
28
+
29
+ cache_opts = {:format => opts[:format], :params => opts[:params]}
30
+ opts.delete(:format); opts.delete(:params); opts.delete(:exclude)
31
+
32
+ before("cache_#{action}_before", opts.merge(:only => action, :with => [cache_opts]))
33
+ after("cache_#{action}_after", opts.merge(:only => action, :with => [cache_opts]))
34
+ alias_method "cache_#{action}_before", :cache_page_before
35
+ alias_method "cache_#{action}_after", :cache_page_after
36
+
37
+ _pages = Merb::Cache.cached_pages[controller_name] ||= {}
38
+ _pages[action] = [from_now, 0]
39
+ end
40
+
41
+ # Register actions for page caching (before and after filters)
42
+ #
43
+ # ==== Parameter
44
+ # actions<Symbol,Array[Symbol,~minutes,Hash]>:: See #cache_page
45
+ #
46
+ # ==== Example
47
+ # cache_pages :mostly_static, :barely_dynamic
48
+ # cache_pages :mostly_static, [:barely_dynamic, 10]
49
+ # cache_pages :barely_dynamic, [:barely_dynamic, 10, :format => :hash]
50
+ # cache_pages :barely_dynamic, [:barely_dynamic, 10, :params => [:id, :name]]
51
+ # cache_pages :all, 10, :exclude => [:show], :format => :snake, :params => [:id, :name]
52
+ def cache_pages(*pages)
53
+ config = Merb::Plugins.config[:merb_cache]
54
+
55
+ if pages[0] == :all
56
+ from_now = Hash === pages[1] ? config[:cache_page_ttl] : pages[1]
57
+ opts = Hash === pages[1] ? pages[1] : pages[2] || {}
58
+ excludes = opts[:exclude] || []
59
+ pages = self.instance_methods(false).map {|action|
60
+ [action.to_sym, from_now, opts] unless excludes.include?(action.to_sym)
61
+ }.compact
62
+ end
63
+
64
+ pages.each do |page_opts|
65
+ if Array === page_opts
66
+ action = page_opts[0]
67
+ from_now = Hash === page_opts[1] ? config[:cache_page_ttl] : page_opts[1]
68
+ opts = Hash === page_opts[1] ? page_opts[1] : page_opts[2]
69
+ else
70
+ action = page_opts
71
+ from_now = config[:cache_page_ttl]
72
+ opts = {}
73
+ end
74
+ cache_page(action, from_now, opts||{})
75
+ end
76
+ true
77
+ end
78
+ end
79
+
80
+ module Merb::Cache::ControllerInstanceMethods
81
+ # Mixed in Merb::Controller. Provides methods related to page caching
82
+
83
+ DEFAULT_PAGE_EXTENSION = 'html'
84
+
85
+ # Checks whether a cache entry exists
86
+ #
87
+ # ==== Parameter
88
+ # options<String,Hash>:: The options that will be passed to #key_for
89
+ #
90
+ # ==== Returns
91
+ # true if the cache entry exists, false otherwise
92
+ #
93
+ # ==== Example
94
+ # cached_page?(:action => 'show', :params => [params[:page]])
95
+ # cached_page?(:action => 'show', :extension => 'js')
96
+ def cached_page?(options)
97
+ key = Merb::Controller._cache.key_for(options, controller_name, true)
98
+ extension = options[:extension] || DEFAULT_PAGE_EXTENSION
99
+ File.file?(Merb::Controller._cache.config[:cache_html_directory] / "#{key}.#{extension}")
100
+ end
101
+
102
+ # Expires the page identified by the key computed after the parameters
103
+ #
104
+ # ==== Parameter
105
+ # options<String,Hash>:: The options that will be passed to #expire_key_for
106
+ #
107
+ # ==== Examples (See Merb::Cache#expire_key_for for more options)
108
+ # # will expire path/to/page/cache/news/show/1.html
109
+ # expire_page(:key => url(:news,News.find(1)))
110
+ #
111
+ # # will expire path/to/page/cache/news/show.html
112
+ # expire_page(:action => 'show', :controller => 'news')
113
+ #
114
+ # # will expire path/to/page/cache/news/show*
115
+ # expire_page(:action => 'show', :match => true)
116
+ #
117
+ # # will expire path/to/page/cache/news/show.js
118
+ # expire_page(:action => 'show', :extension => 'js')
119
+ def expire_page(options)
120
+ config_dir = Merb::Controller._cache.config[:cache_html_directory]
121
+ Merb::Controller._cache.expire_key_for(options, controller_name, true) do |key, match|
122
+ if match
123
+ files = Dir.glob(config_dir / "#{key}*")
124
+ else
125
+ extension = options[:extension] || DEFAULT_PAGE_EXTENSION
126
+ files = config_dir / "#{key}.#{extension}"
127
+ end
128
+ FileUtils.rm_rf(files)
129
+ end
130
+ true
131
+ end
132
+
133
+ # Expires all the pages stored in config[:cache_html_directory]
134
+ def expire_all_pages
135
+ FileUtils.rm_rf(Dir.glob(Merb::Controller._cache.config[:cache_html_directory] / "*"))
136
+ end
137
+
138
+ # You can call this method if you need to prevent caching the page
139
+ # after it has been rendered.
140
+ def abort_cache_page
141
+ @capture_page = false
142
+ end
143
+
144
+ private
145
+
146
+ # Called by the before and after filters. Stores or recalls a cache entry.
147
+ # The name used for the cache file is based on request.path
148
+ # If the name ends with "/" then it is removed
149
+ # If the name is "/" then it will be replaced by "index"
150
+ #
151
+ # ==== Parameters
152
+ # data<String>:: the data to put in cache
153
+ #
154
+ # ==== Examples
155
+ # All the file are written to config[:cache_html_directory]
156
+ # If request.path is "/", the name will be "/index.html"
157
+ # If request.path is "/news/show/1", the name will be "/news/show/1.html"
158
+ # If request.path is "/news/show/", the name will be "/news/show.html"
159
+ # If request.path is "/news/styles.css", the name will be "/news/styles.css"
160
+ def _cache_page(data = nil, opts = {})
161
+ return if Merb::Controller._cache.config[:disable_page_caching]
162
+ controller = controller_name
163
+ action = action_name.to_sym
164
+ pages = Merb::Controller._cache.cached_pages[controller]
165
+ return unless pages && pages.key?(action)
166
+
167
+ path = request.path.chomp("/")
168
+ path = "index" if path.empty?
169
+
170
+ _format = opts[:format] || Merb::Controller._cache.config[:cache_key_format]
171
+ _params = opts[:params]==false ? nil : Merb::Request.query_parse(request.query_string, '&', true)
172
+ _params.delete_if {|k,v| !opts[:params].include?(k.to_sym) } if _params && opts[:params]
173
+
174
+ key = Merb::Controller._cache.key_for({:key => path, :params => _params, :format => _format})
175
+
176
+ no_format = params[:format].nil? || params[:format].empty?
177
+ ext = "." + (no_format ? DEFAULT_PAGE_EXTENSION : params[:format])
178
+ ext = nil if File.extname(key) == ext
179
+ cache_file = Merb::Controller._cache.config[:cache_html_directory] / "#{key}#{ext}"
180
+
181
+ if data
182
+ cache_directory = File.dirname(cache_file)
183
+ FileUtils.mkdir_p(cache_directory)
184
+ _expire_in = pages[action][0]
185
+ pages[action][1] = _expire_in.minutes.from_now unless _expire_in.nil?
186
+ cache_write_page(cache_file, data)
187
+ Merb.logger.info("cache: set (#{path})")
188
+ else
189
+ @capture_page = false
190
+ if File.file?(cache_file)
191
+ _data = cache_read_page(cache_file)
192
+ _expire_in, _expire_at = pages[action]
193
+ if _expire_in.nil? || Time.now.to_i < _expire_at.to_i
194
+ Merb.logger.info("cache: hit (#{path})")
195
+ throw(:halt, _data)
196
+ end
197
+ FileUtils.rm_f(cache_file)
198
+ end
199
+ @capture_page = true
200
+ end
201
+ true
202
+ end
203
+
204
+ # Read data from a file using exclusive lock
205
+ #
206
+ # ==== Parameters
207
+ # cache_file<String>:: the full path to the file
208
+ #
209
+ # ==== Returns
210
+ # data<String>:: the data that has been read from the file
211
+ def cache_read_page(cache_file)
212
+ _data = nil
213
+ File.open(cache_file, "r") do |cache_data|
214
+ cache_data.flock(File::LOCK_EX)
215
+ _data = cache_data.read
216
+ cache_data.flock(File::LOCK_UN)
217
+ end
218
+ _data
219
+ end
220
+
221
+ # Write data to a file using exclusive lock
222
+ #
223
+ # ==== Parameters
224
+ # cache_file<String>:: the full path to the file
225
+ # data<String>:: the data that will be written to the file
226
+ def cache_write_page(cache_file, data)
227
+ File.open(cache_file, "w+") do |cache_data|
228
+ cache_data.flock(File::LOCK_EX)
229
+ cache_data.write(data)
230
+ cache_data.flock(File::LOCK_UN)
231
+ end
232
+ true
233
+ end
234
+
235
+ # before filter
236
+ def cache_page_before(opts)
237
+ # recalls a cached entry or set @capture_page to true in order
238
+ # to grab the response in the after filter
239
+ _cache_page(nil, opts)
240
+ end
241
+
242
+ # after filter
243
+ def cache_page_after(opts)
244
+ # takes the body of the response
245
+ # if the cache entry expired, if it doesn't exist or status is 200
246
+ _cache_page(body, opts) if @capture_page && status == 200
247
+ end
248
+ end
@@ -0,0 +1,88 @@
1
+ module Merb::Cache::DatabaseStore::ActiveRecord
2
+ # Module that provides ActiveRecord support for the database backend
3
+
4
+ # The cache model migration
5
+ class CacheMigration < ActiveRecord::Migration
6
+ def self.up
7
+ create_table (Merb::Controller._cache.config[:table_name]), :primary_key => :ckey do |t|
8
+ t.column :ckey, :string
9
+ t.column :data, :text
10
+ t.datetime :expire, :default => nil
11
+ end
12
+ end
13
+
14
+ def self.down
15
+ drop_table Merb::Controller._cache.config[:table_name]
16
+ end
17
+ end
18
+
19
+ # The cache model
20
+ class CacheModel < ActiveRecord::Base
21
+ set_table_name Merb::Controller._cache.config[:table_name]
22
+
23
+ # Fetch data from the database using the specified key
24
+ # The entry is deleted if it has expired
25
+ #
26
+ # ==== Parameter
27
+ # key<Sting>:: The key identifying the cache entry
28
+ #
29
+ # ==== Returns
30
+ # data<String, NilClass>::
31
+ # nil is returned whether the entry expired or was not found
32
+ def self.cache_get(key)
33
+ if entry = self.find(:first, :conditions => ["ckey=?", key])
34
+ return entry.data if entry.expire.nil? || Time.now < entry.expire
35
+ self.expire(key)
36
+ end
37
+ nil
38
+ end
39
+
40
+ # Store data to the database using the specified key
41
+ #
42
+ # ==== Parameters
43
+ # key<Sting>:: The key identifying the cache entry
44
+ # data<String>:: The data to be put in cache
45
+ # expire<~minutes>::
46
+ # The number of minutes (from now) the cache should persist
47
+ # get<Boolean>::
48
+ # used internally to behave like this
49
+ # - when set to true, perform update_or_create on the cache entry
50
+ # - when set to false, force creation of the cache entry
51
+ def self.cache_set(key, data, expire = nil, get = true)
52
+ attributes = {:ckey => key, :data => data, :expire => expire }
53
+ if get
54
+ entry = self.find(:first, :conditions => ["ckey=?",key])
55
+ entry.nil? ? self.create(attributes) : entry.update_attributes(attributes)
56
+ else
57
+ self.create(attributes)
58
+ end
59
+ true
60
+ end
61
+
62
+ # Expire the cache entry identified by the given key
63
+ #
64
+ # ==== Parameter
65
+ # key<Sting>:: The key identifying the cache entry
66
+ def self.expire(key)
67
+ self.delete_all(["ckey=?", key])
68
+ end
69
+
70
+ # Expire the cache entries matching the given key
71
+ #
72
+ # ==== Parameter
73
+ # key<Sting>:: The key matching the cache entries
74
+ def self.expire_match(key)
75
+ self.delete_all(["ckey like ?", key + "%"])
76
+ end
77
+
78
+ # Expire all the cache entries
79
+ def self.expire_all
80
+ self.delete_all
81
+ end
82
+
83
+ # Perform auto-migration in case the table is unknown in the database
84
+ def self.check_table
85
+ CacheMigration.up unless self.table_exists?
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,79 @@
1
+ module Merb::Cache::DatabaseStore::DataMapper
2
+ # Module that provides DataMapper support for the database backend
3
+
4
+ # The cache model
5
+ class CacheModel < DataMapper::Base
6
+ set_table_name Merb::Controller._cache.config[:table_name]
7
+ property :ckey, :string, :length => 255, :lazy => false, :key => true
8
+ property :data, :text, :lazy => false
9
+ property :expire, :datetime, :default => nil
10
+
11
+ # Fetch data from the database using the specified key
12
+ # The entry is deleted if it has expired
13
+ #
14
+ # ==== Parameter
15
+ # key<Sting>:: The key identifying the cache entry
16
+ #
17
+ # ==== Returns
18
+ # data<String, NilClass>::
19
+ # nil is returned whether the entry expired or was not found
20
+ def self.cache_get(key)
21
+ if entry = self.first(key)
22
+ return entry.data if entry.expire.nil? || DateTime.now < entry.expire
23
+ entry.destroy!
24
+ end
25
+ nil
26
+ end
27
+
28
+ # Store data to the database using the specified key
29
+ #
30
+ # ==== Parameters
31
+ # key<Sting>:: The key identifying the cache entry
32
+ # data<String>:: The data to be put in cache
33
+ # expire<~minutes>::
34
+ # The number of minutes (from now) the cache should persist
35
+ # get<Boolean>::
36
+ # used internally to behave like this:
37
+ # - when set to true, perform update_or_create on the cache entry
38
+ # - when set to false, force creation of the cache entry
39
+ def self.cache_set(key, data, expire = nil, get = true)
40
+ attributes = {:ckey => key, :data => data,
41
+ :expire => expire.nil? ? nil : expire.to_s_db }
42
+ if get
43
+ entry = self.first(key)
44
+ entry.nil? ? self.create(attributes) : entry.update_attributes(attributes)
45
+ else
46
+ self.create(attributes)
47
+ end
48
+ true
49
+ end
50
+
51
+ # Expire the cache entry identified by the given key
52
+ #
53
+ # ==== Parameter
54
+ # key<Sting>:: The key identifying the cache entry
55
+ def self.expire(key)
56
+ entry = self.first(key)
57
+ entry.destroy! unless entry.nil?
58
+ end
59
+
60
+ # Expire the cache entries matching the given key
61
+ #
62
+ # ==== Parameter
63
+ # key<Sting>:: The key matching the cache entries
64
+ def self.expire_match(key)
65
+ #FIXME
66
+ database.execute("DELETE FROM #{self.table.name} WHERE ckey LIKE '#{key}%'")
67
+ end
68
+
69
+ # Expire all the cache entries
70
+ def self.expire_all
71
+ self.truncate!
72
+ end
73
+
74
+ # Perform auto-migration in case the table is unknown in the database
75
+ def self.check_table
76
+ self.auto_migrate! unless self.table.exists?
77
+ end
78
+ end
79
+ end