From a03e2b356c66ddc8809fa2b23a2a7d652f173b8b Mon Sep 17 00:00:00 2001 From: Pratik Naik Date: Tue, 21 Oct 2008 18:33:40 +0100 Subject: Merge with docrails. Also add a rake task to generate guides in your rails application : rake doc:guides The rake task will generate guides inside doc/guides directory of your application. Open index.html to browse. --- railties/doc/guides/html/caching_with_rails.html | 577 +++++++++++++++++++++++ 1 file changed, 577 insertions(+) create mode 100644 railties/doc/guides/html/caching_with_rails.html (limited to 'railties/doc/guides/html/caching_with_rails.html') diff --git a/railties/doc/guides/html/caching_with_rails.html b/railties/doc/guides/html/caching_with_rails.html new file mode 100644 index 0000000000..df30c46c35 --- /dev/null +++ b/railties/doc/guides/html/caching_with_rails.html @@ -0,0 +1,577 @@ + + + + + Caching with Rails: An overview + + + + + + + + + +
+ + + +
+

Caching with Rails: An overview

+
+
+

Everyone caches. This guide will teach you what you need to know about +avoiding that expensive round-trip to your database and returning what you +need to return to those hungry web clients in the shortest time possible.

+
+
+

1. Basic Caching

+
+

This is an introduction to the three types of caching techniques that Rails +provides by default without the use of any third party plugins.

+

To get started make sure Base.perform_caching is set to true for your +environment.

+
+
+
Base.perform_caching = true
+
+

1.1. Page Caching

+

Page caching is a Rails mechanism which allows the request for a generated +page to be fulfilled by the webserver, without ever having to go through the +Rails stack at all. Obviously, this is super fast. Unfortunately, it can't be +applied to every situation (such as pages that need authentication) and since +the webserver is literally just serving a file from the filesystem, cache +expiration is an issue that needs to be dealt with.

+

So, how do you enable this super-fast cache behavior? Simple, let's say you +have a controller called ProductController and a list action that lists all +the products

+
+
+
class ProductController < ActionController
+
+  cache_page :list
+
+  def list; end
+
+end
+
+

The first time anyone requestsion products/list, Rails will generate a file +called list.html and the webserver will then look for that file before it +passes the next request for products/list to your Rails application.

+

By default, the page cache directory is set to Rails.public_path (which is +usually set to RAILS_ROOT + "/public") and this can be configured by changing +the configuration setting Base.cache_public_directory

+

The page caching mechanism will automatically add a .html exxtension to +requests for pages that do not have an extension to make it easy for the +webserver to find those pages and this can be configured by changing the +configuration setting Base.page_cache_extension

+

In order to expire this page when a new product is added we could extend our +example controler like this:

+
+
+
class ProductController < ActionController
+
+  cache_page :list
+
+  def list; end
+
+  def create
+    expire_page :action => :list
+  end
+
+end
+
+

If you want a more complicated expiration scheme, you can use cache sweepers +to expire cached objects when things change. This is covered in the section on Sweepers.

+

1.2. Action Caching

+

One of the issues with page caching is that you cannot use it for pages that +require to restrict access somehow. This is where Action Caching comes in. +Action Caching works like Page Caching except for the fact that the incoming +web request does go from the webserver to the Rails stack and Action Pack so +that before_filters can be run on it before the cache is served, so that +authentication and other restrictions can be used while still serving the +result of the output from a cached copy.

+

Clearing the cache works in the exact same way as with Page Caching.

+

Let's say you only wanted authenticated users to edit or create a Product +object, but still cache those pages:

+
+
+
class ProductController < ActionController
+
+  before_filter :authenticate, :only => [ :edit, :create ]
+  cache_page :list
+  caches_action :edit
+
+  def list; end
+
+  def create
+    expire_page :action => :list
+    expire_action :action => :edit
+  end
+
+  def edit; end
+
+end
+
+

And you can also use :if (or :unless) to pass a Proc that specifies when the +action should be cached. Also, you can use :layout ⇒ false to cache without +layout so that dynamic information in the layout such as logged in user info +or the number of items in the cart can be left uncached. This feature is +available as of Rails 2.2.

+

[More: more examples? Walk-through of action caching from request to response? + Description of Rake tasks to clear cached files? Show example of + subdomain caching? Talk about :cache_path, :if and assing blocks/Procs + to expire_action?]

+

1.3. Fragment Caching

+

Life would be perfect if we could get away with caching the entire contents of +a page or action and serving it out to the world. Unfortunately, dynamic web +applications usually build pages with a variety of components not all of which +have the same caching characteristics. In order to address such a dynamically +created page where different parts of the page need to be cached and expired +differently Rails provides a mechanism called Fragment caching.

+

Fragment caching allows a fragment of view logic to be wrapped in a cache +block and served out of the cache store when the next request comes in.

+

As an example, if you wanted to show all the orders placed on your website in +real time and didn't want to cache that part of the page, but did want to +cache the part of the page which lists all products available, you could use +this piece of code:

+
+
+
<% Order.find_recent.each do |o| %>
+  <%= o.buyer.name %> bought <% o.product.name %>
+<% end %>
+
+<% cache do %>
+  All available products:
+  <% Product.find(:all).each do |p| %>
+    <%= link_to p.name, product_url(p) %>
+  <% end %>
+<% end %>
+
+

The cache block in our example will bind to the action that called it and is +written out to the same place as the Action Cache, which means that if you +want to cache multiple fragments per action, you should provide an action_path to the cache call:

+
+
+
<% cache(:action => 'recent', :action_suffix => 'all_products') do %>
+  All available products:
+
+

and you can expire it using the expire_fragment method, like so:

+
+
+
expire_fragment(:controller => 'producst', :action => 'recent', :action_suffix => 'all_products)
+
+

1.4. Sweepers

+

Cache sweeping is a mechanism which allows you to get around having a ton of +expire_{page,action,fragment} calls in your code by moving all the work +required to expire cached content into a ActionController::Caching::Sweeper +class that is an Observer and looks for changes to an object via callbacks, +and when a change occurs it expires the caches associated with that object n +an around or after filter.

+

Continuing with our Product controller example, we could rewrite it with a +sweeper such as the following:

+
+
+
class StoreSweeper < ActionController::Caching::Sweeper
+  observe Product # This sweeper is going to keep an eye on the Post model
+
+  # If our sweeper detects that a Post was created call this
+  def after_create(product)
+          expire_cache_for(product)
+  end
+
+  # If our sweeper detects that a Post was updated call this
+  def after_update(product)
+          expire_cache_for(product)
+  end
+
+  # If our sweeper detects that a Post was deleted call this
+  def after_destroy(product)
+          expire_cache_for(product)
+  end
+
+  private
+  def expire_cache_for(record)
+    # Expire the list page now that we added a new product
+    expire_page(:controller => '#{record}', :action => 'list')
+
+    # Expire a fragment
+    expire_fragment(:controller => '#{record}', :action => 'recent', :action_suffix => 'all_products')
+  end
+end
+
+

Then we add it to our controller to tell it to call the sweeper when certain +actions are called. So, if we wanted to expire the cached content for the +list and edit actions when the create action was called, we could do the +following:

+
+
+
class ProductController < ActionController
+
+  before_filter :authenticate, :only => [ :edit, :create ]
+  cache_page :list
+  caches_action :edit
+  cache_sweeper :store_sweeper, :only => [ :create ]
+
+  def list; end
+
+  def create
+    expire_page :action => :list
+    expire_action :action => :edit
+  end
+
+  def edit; end
+
+end
+
+

1.5. SQL Caching

+

Query caching is a Rails feature that caches the result set returned by each +query so that if Rails encounters the same query again for that request, it +will used the cached result set as opposed to running the query against the +database again.

+

For example:

+
+
+
class ProductController < ActionController
+
+  before_filter :authenticate, :only => [ :edit, :create ]
+  cache_page :list
+  caches_action :edit
+  cache_sweeper :store_sweeper, :only => [ :create ]
+
+  def list
+    # Run a find query
+    Product.find(:all)
+
+    ...
+
+    # Run the same query again
+    Product.find(:all)
+  end
+
+  def create
+    expire_page :action => :list
+    expire_action :action => :edit
+  end
+
+  def edit; end
+
+end
+
+

In the list action above, the result set returned by the first +Product.find(:all) will be cached and will be used to avoid querying the +database again the second time that finder is called.

+

Query caches are created at the start of an action and destroyed at the end of +that action and thus persist only for the duration of the action.

+

1.6. Cache stores

+

Rails provides different stores for the cached data for action and fragment +caches. Page caches are always stored on disk.

+

The cache stores provided include:

+

1) Memory store: Cached data is stored in the memory allocated to the Rails + process, which is fine for WEBrick and for FCGI (if you + don't care that each FCGI process holds its 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 up a lot of memory since each process keeps all the + caches in memory.

+
+
+
ActionController::Base.cache_store = :memory_store
+
+

2) File store: Cached data is stored on the disk, this is the default store + and the default path for this store is: /tmp/cache. Works + well for all types of environments and allows all processes + running from the same application directory to access the + cached content.

+
+
+
ActionController::Base.cache_store = :file_store, "/path/to/cache/directory"
+
+

3) DRb store: Cached data is stored in a separate shared DRb process that all + servers communicate with. This works for all environments and + only keeps one cache around for all processes, but requires + that you run and manage a separate DRb process.

+
+
+
ActionController::Base.cache_store = :drb_store, "druby://localhost:9192"
+
+

4) MemCached store: Works like DRbStore, but uses Danga's MemCache instead. + Requires the ruby-memcache library: + gem install ruby-memcache.

+
+
+
ActionController::Base.cache_store = :mem_cache_store, "localhost"
+
+

5) Custom store: You can define your own cache store (new in Rails 2.1)

+
+
+
ActionController::Base.cache_store = MyOwnStore.new("parameter")
+
+
+

2. Advanced Caching

+
+

Along with the built-in mechanisms outlined above, a number of excellent +plugins exist to help with finer grained control over caching. These include +Chris Wanstrath's excellent cache_fu plugin (more info here: +http://errtheblog.com/posts/57-kickin-ass-w-cachefu) and Evan Weaver's +interlock plugin (more info here: +http://blog.evanweaver.com/articles/2007/12/13/better-rails-caching/). Both +of these plugins play nice with memcached and are a must-see for anyone +seriously considering optimizing their caching needs.

+
+ +
+
+ + -- cgit v1.2.3