bchiu-merb_cache_more 0.9.4

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