public
Description: Ruby on Rails
Homepage: http://rubyonrails.org
Clone URL: git://github.com/rails/rails.git
Finished documentation for caching

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@426 
5ecf4fe2-1ee6-0310-87b1-e25e094e27de
dhh (author)
Sat Jan 15 14:16:41 -0800 2005
commit  bc4156e09e558260cc39a238ca4d17b44208ce2a
tree    be209e95a2473e80e4f3733fc993681c450ff3cf
parent  e59f1b52497ce3a74bb1f396c06141524f0b6b85
...
1
2
 
 
 
3
4
5
...
1
2
3
4
5
6
7
8
0
@@ -1,5 +1,8 @@
0
 *SVN*
0
 
0
+* Added an extensive caching module that offers three levels of granularity (page, action, fragment) and a variety of stores.
0
+ Read more in ActionController::Caching.
0
+
0
 * Added the option of passing a block to ActiveRecordHelper#form in order to add more to the auto-generated form #469 [dom@sisna.com]
0
 
0
     form("entry", :action => "sign") do |form|
...
1
2
3
4
 
 
 
 
 
 
5
6
 
7
8
9
...
41
42
43
 
 
 
 
 
44
45
46
...
52
53
54
 
 
 
 
 
 
 
 
 
 
55
56
57
...
59
60
61
62
63
64
65
66
67
 
 
68
69
70
...
72
73
74
75
76
77
78
79
 
 
 
 
 
 
 
80
81
82
83
 
 
84
85
86
...
92
93
94
95
96
97
98
99
100
101
102
103
 
 
 
104
105
106
107
108
109
110
111
 
 
 
 
112
113
114
...
135
136
137
138
 
139
140
141
...
198
199
200
201
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
203
204
...
246
247
248
249
 
250
251
 
252
253
 
254
255
256
 
257
258
259
260
261
262
263
 
264
265
266
267
 
268
269
270
271
 
272
273
274
275
276
277
 
278
279
280
281
282
283
 
284
285
286
...
317
318
319
320
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
 
352
353
354
...
1
2
3
 
4
5
6
7
8
9
10
 
11
12
13
14
...
46
47
48
49
50
51
52
53
54
55
56
...
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
...
79
80
81
 
 
 
 
 
 
82
83
84
85
86
...
88
89
90
 
 
 
 
 
91
92
93
94
95
96
97
98
 
99
100
101
102
103
104
105
...
111
112
113
 
 
 
 
 
 
 
 
 
114
115
116
117
118
119
120
121
 
 
 
122
123
124
125
126
127
128
...
149
150
151
 
152
153
154
155
...
212
213
214
 
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
...
284
285
286
 
287
288
 
289
290
 
291
292
293
 
294
295
296
297
298
299
300
 
301
302
303
304
 
305
306
307
308
 
309
310
311
312
313
314
 
315
316
317
318
319
320
 
321
322
323
324
...
355
356
357
 
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
389
390
391
392
0
@@ -1,9 +1,14 @@
0
 require 'fileutils'
0
 
0
 module ActionController #:nodoc:
0
- # To turn off all caching and sweeping, set Base.perform_caching = false.
0
+ # Caching is a cheap way of speeding up slow applications by keeping the result of calculations, renderings, and database calls
0
+ # around for subsequent requests. Action Controller affords you three approaches in varying levels of granularity: Page, Action, Fragment.
0
+ #
0
+ # You can read more about each approach and the sweeping assistance by clicking the modules below.
0
+ #
0
+ # Note: To turn off all caching and sweeping, set Base.perform_caching = false.
0
   module Caching
0
- def self.append_features(base)
0
+ def self.append_features(base) #:nodoc:
0
       super
0
       base.send(:include, Pages, Actions, Fragments, Sweeping)
0
       base.class_eval do
0
@@ -41,6 +46,11 @@ module ActionController #:nodoc:
0
     #
0
     # Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be
0
     # expired.
0
+ #
0
+ # == Setting the cache directory
0
+ #
0
+ # The cache directory should be the document root for the web server and is set using Base.page_cache_directory = "/document/root".
0
+ # For Rails, this directory has already been set to RAILS_ROOT + "/public".
0
     module Pages
0
       def self.append_features(base) #:nodoc:
0
         super
0
@@ -52,6 +62,16 @@ module ActionController #:nodoc:
0
       end
0
 
0
       module ClassMethods
0
+ # Expires the page that was cached with the +path+ as a key. Example:
0
+ # expire_page "/lists/show"
0
+ def expire_page(path)
0
+ return unless perform_caching
0
+ File.delete(page_cache_path(path)) if File.exists?(page_cache_path(path))
0
+ logger.info "Expired page: #{path}" unless logger.nil?
0
+ end
0
+
0
+ # Manually cache the +content+ in the key determined by +path+. Example:
0
+ # cache_page "I'm the cached content", "/lists/show"
0
         def cache_page(content, path)
0
           return unless perform_caching
0
           FileUtils.makedirs(File.dirname(page_cache_path(path)))
0
@@ -59,12 +79,8 @@ module ActionController #:nodoc:
0
           logger.info "Cached page: #{path}" unless logger.nil?
0
         end
0
 
0
- def expire_page(path)
0
- return unless perform_caching
0
- File.delete(page_cache_path(path)) if File.exists?(page_cache_path(path))
0
- logger.info "Expired page: #{path}" unless logger.nil?
0
- end
0
-
0
+ # Caches the +actions+ using the page-caching approach that'll store the cache in a path within the page_cache_directory that
0
+ # matches the triggering url.
0
         def caches_page(*actions)
0
           return unless perform_caching
0
           actions.each do |action|
0
@@ -72,15 +88,18 @@ module ActionController #:nodoc:
0
           end
0
         end
0
         
0
- def page_cache_path(path)
0
- if path[-1,1] == '/'
0
- page_cache_directory + path + '/index'
0
- else
0
- page_cache_directory + path
0
+ private
0
+ def page_cache_path(path)
0
+ if path[-1,1] == '/'
0
+ page_cache_directory + path + '/index'
0
+ else
0
+ page_cache_directory + path
0
+ end
0
           end
0
- end
0
       end
0
 
0
+ # Expires the page that was cached with the +options+ as a key. Example:
0
+ # expire_page :controller => "lists", :action => "show"
0
       def expire_page(options = {})
0
         return unless perform_caching
0
         if options[:action].is_a?(Array)
0
@@ -92,23 +111,18 @@ module ActionController #:nodoc:
0
         end
0
       end
0
 
0
- # Expires more than one page at the time. Example:
0
- # expire_pages(
0
- # { :controller => "lists", :action => "public", :id => list_id },
0
- # { :controller => "lists", :action => "show", :id => list_id }
0
- # )
0
- def expire_pages(*options)
0
- options.each { |option| expire_page(option) }
0
- end
0
-
0
+ # Manually cache the +content+ in the key determined by +options+. If no content is provided, the contents of @response.body is used
0
+ # If no options are provided, the current +options+ for this action is used. Example:
0
+ # cache_page "I'm the cached content", :controller => "lists", :action => "show"
0
       def cache_page(content = nil, options = {})
0
         return unless perform_caching && caching_allowed
0
         self.class.cache_page(content || @response.body, url_for(options.merge({ :only_path => true })))
0
       end
0
 
0
- def caching_allowed
0
- !@request.method.post? and (@request.parameters.reject {|k, v| ['id', 'action', 'controller'].include?(k)}).empty?
0
- end
0
+ private
0
+ def caching_allowed
0
+ !@request.method.post? && (@request.parameters.reject {|k, v| ['id', 'action', 'controller'].include?(k)}).empty?
0
+ end
0
     end
0
 
0
     # Action caching is similar to page caching by the fact that the entire output of the response is cached, but unlike page caching,
0
@@ -135,7 +149,7 @@ module ActionController #:nodoc:
0
         base.send(:attr_accessor, :rendered_action_cache)
0
       end
0
 
0
- module ClassMethods
0
+ module ClassMethods #:nodoc:
0
         def caches_action(*actions)
0
           return unless perform_caching
0
           around_filter(ActionCacheFilter.new(*actions))
0
@@ -198,7 +212,31 @@ module ActionController #:nodoc:
0
     #
0
     # == Fragment stores
0
     #
0
- # TO BE WRITTEN...
0
+ # In order to use the fragment caching, you need to designate where the caches should be stored. This is done by assigning a fragment store
0
+ # of which there are four different kinds:
0
+ #
0
+ # * FileStore: Keeps the fragments on disk in the +cache_path+, which works well for all types of environments and share the fragments for
0
+ # all the web server processes running off the same application directory.
0
+ # * MemoryStore: Keeps the fragments in memory, which is fine for WEBrick and for FCGI (if you don't care that each FCGI process holds its
0
+ # own fragment store). It's not suitable for CGI as the process is thrown away at the end of each request. It can potentially also take
0
+ # up a lot of memory since each process keeps all the caches in memory.
0
+ # * DRbStore: Keeps the fragments in the memory of a separate, shared DRb process. This works for all environments and only keeps one cache
0
+ # around for all processes, but requires that you run and manage a separate DRb process.
0
+ # * MemCachedStore: Works like DRbStore, but uses Danga's MemCached instead.
0
+ #
0
+ # Configuration examples (MemoryStore is the default):
0
+ #
0
+ # ActionController::Base.fragment_cache_store =
0
+ # ActionController::Caching::Fragments::MemoryStore.new
0
+ #
0
+ # ActionController::Base.fragment_cache_store =
0
+ # ActionController::Caching::Fragments::FileStore.new("/path/to/cache/directory")
0
+ #
0
+ # ActionController::Base.fragment_cache_store =
0
+ # ActionController::Caching::Fragments::DRbStore.new("druby://localhost:9192")
0
+ #
0
+ # ActionController::Base.fragment_cache_store =
0
+ # ActionController::Caching::Fragments::FileStore.new("localhost")
0
     module Fragments
0
       def self.append_features(base) #:nodoc:
0
         super
0
@@ -246,41 +284,41 @@ module ActionController #:nodoc:
0
         logger.info "Expired fragment: #{name}" unless logger.nil?
0
       end
0
     
0
- class MemoryStore
0
+ class MemoryStore #:nodoc:
0
         def initialize
0
- @data = { }
0
+ @data, @mutex = { }, Mutex.new
0
         end
0
-
0
+
0
         def read(name, options = {}) #:nodoc:
0
           begin
0
- @data[name]
0
+ @mutex.synchronize { @data[name] }
0
           rescue
0
             nil
0
           end
0
         end
0
 
0
         def write(name, value, options = {}) #:nodoc:
0
- @data[name] = value
0
+ @mutex.synchronize { @data[name] = value }
0
         end
0
 
0
         def delete(name, options = {}) #:nodoc:
0
- @data.delete(name)
0
+ @mutex.synchronize { @data.delete(name) }
0
         end
0
       end
0
 
0
- class DRbStore < MemoryStore
0
+ class DRbStore < MemoryStore #:nodoc:
0
         def initialize(address = 'druby://localhost:9192')
0
           @data = DRbObject.new(nil, address)
0
         end
0
       end
0
 
0
- class MemCacheStore < MemoryStore
0
+ class MemCacheStore < MemoryStore #:nodoc:
0
         def initialize(address = 'localhost')
0
           @data = MemCache.new(address)
0
         end
0
       end
0
 
0
- class FileStore
0
+ class FileStore #:nodoc:
0
         def initialize(cache_path)
0
           @cache_path = cache_path
0
         end
0
@@ -317,38 +355,38 @@ module ActionController #:nodoc:
0
       end
0
     end
0
 
0
- module Sweeping #:nodoc:
0
+ # Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change.
0
+ # They do this by being half-observers, half-filters and implementing callbacks for both roles. A Sweeper example:
0
+ #
0
+ # class ListSweeper < ActiveRecord::Observer
0
+ # observe List, Item
0
+ #
0
+ # def after_save(record)
0
+ # @list = record.is_a?(List) ? record : record.list
0
+ # end
0
+ #
0
+ # def filter(controller)
0
+ # controller.expire_page(:controller => "lists", :action => %w( show public feed ), :id => @list.id)
0
+ # controller.expire_action(:controller => "lists", :action => "all")
0
+ # @list.shares.each { |share| controller.expire_page(:controller => "lists", :action => "show", :id => share.url_key) }
0
+ # end
0
+ # end
0
+ #
0
+ # The sweeper is assigned on the controllers that wish to have its job performed using the <tt>cache_sweeper</tt> class method:
0
+ #
0
+ # class ListsController < ApplicationController
0
+ # caches_action :index, :show, :public, :feed
0
+ # cache_sweeper :list_sweeper, :only => [ :edit, :destroy, :share ]
0
+ # end
0
+ #
0
+ # In the example above, four actions are cached and three actions are responsible of expiring those caches.
0
+ module Sweeping
0
       def self.append_features(base) #:nodoc:
0
         super
0
         base.extend(ClassMethods)
0
       end
0
 
0
- # Sweepers are the terminators of the caching world and responsible for expiring caches when model objects change.
0
- # They do this by being half-observers, half-filters and implementing callbacks for both roles. A Sweeper example:
0
- #
0
- # class ListSweeper < ActiveRecord::Observer
0
- # observe List, Item
0
- #
0
- # def after_save(record)
0
- # @list = record.is_a?(List) ? record : record.list
0
- # end
0
- #
0
- # def filter(controller)
0
- # controller.expire_page(:controller => "lists", :action => %w( show public feed ), :id => @list.id)
0
- # controller.expire_action(:controller => "lists", :action => "all")
0
- # @list.shares.each { |share| controller.expire_page(:controller => "lists", :action => "show", :id => share.url_key) }
0
- # end
0
- # end
0
- #
0
- # The sweeper is assigned on the controllers that wish to have its job performed using the <tt>cache_sweeper</tt> class method:
0
- #
0
- # class ListsController < ApplicationController
0
- # caches_action :index, :show, :public, :feed
0
- # cache_sweeper :list_sweeper, :only => [ :edit, :destroy, :share ]
0
- # end
0
- #
0
- # In the example above, four actions are cached and three actions are responsible of expiring those caches.
0
- module ClassMethods
0
+ module ClassMethods #:nodoc:
0
         def cache_sweeper(*sweepers)
0
           return unless perform_caching
0
           configuration = sweepers.last.is_a?(Hash) ? sweepers.pop : {}

Comments

    No one has commented yet.