Section 9.3. Output Caching


9.3. Output Caching

Output caching refers to storing the output of Rails' views so that the next request requires less overhead to recreate. Rails provides output caching at three levels of granularity, each useful for different purposes:


Page caching

Writes the complete response body to a static file in the public directory, so that subsequent requests are served directly by the web server


Action caching

Caches the complete response body, but still processes each request through Rails


Fragment caching

Stores subpage level snippets of output

Not everything is a candidate for cachinghighly dynamic applications that deal with ever-changing data may not benefit much or at all. But many pages will get a dramatic speedup from caching, especially high-traffic pages that summarize a large amount of data and don't need up-to-the-second freshness.

By default, output caching isn't performed in the development environment. To enable it for debugging, edit config/environments/development.rb and enable caching with this setting:

config.action_controller.perform_caching = true

9.3.1. Page Caching

The first type of output caching, and the bluntest, is page caching. Page caches store the output of an entire action at once, and subsequent requests to the page bypass filtersin fact, they bypass Rails entirely.

Page caching relies on how the web server in front of Rails (such as Apache or lighttpd) is configured. Typically, when the web server receives a request for a URL in a Rails application, it will first check the public directory for a match. If none is found, the request is passed on to the Rails dispatcher. Page caching cleverly takes advantage of that fact by actually writing static HTML files to the application's public directory. So the first time a request for a page-cached URL comes in (say, /articles/1), Rails is invoked and the page is dynamically generated and written to the file system, at public/articles/1.html. The next time the same URL is requested, the web server will respond with the static file; the request never passes through the Rails stack, so it won't even register in Rails' log files. Servers like Apache and lighttpd are tuned to be very fast at delivering static files, so page caching can have a huge effect on overall site performance.

Often, page caching indirectly improves performance on the rest of an applicationeven the noncached parts. Even if you can only use page caching for a few of the most popular URLs in the applicationsay, the home page and RSS feedsyou'll significantly reduce load on the application server and database, freeing them up to handle the noncached requests faster.

Page caching is enabled with a class method in the controller, caches_page, which takes a list of the actions you want cached. For example:

class ArticlesController < ApplicationController   caches_page :show   def show     @article = Article.find params[:id]   end end

In this example, the first request to the show action (via a URL like /articles/1) will process the action as usual, entailing the usual overhead of session management, a database lookup, rendering the view, etc. After sending the response back to the client, the output will then be cached to a static file (in this case public/articles/1.html). From then on, as long as the cache exists, the page will be served just like any other static filein other words, fast.

For public, content-heavy, personalization-light resources, page caching can have an immense effect on performance. But the greatest strength of page cachingthat it bypasses Railsis also its biggest gotcha. Namely, because it doesn't invoke before filters, page caching isn't suitable for any content that needs to be protected by login or personalized. So every time you enable page caching for a page, ask yourself two questions. First: is the page completely public? And second: is the page free from any personalization?

If the answer to both of those questions is affirmative, it's probably a great candidate for page caching. If not, move on to the next-best thing: action caching or fragment caching.

9.3.2. Action Caching

Action caching works much like page caching, in that it stores the entire response body of an action. There's one important difference: every request is still handled by Rails, and although the actions themselves aren't processed, before_filters are. That means that action caches, unlike page caches, can be protected by authentication.

Like page caching, action caching is enabled with a class method in the controller, this time caches_action. For example:

class ArticlesController < ApplicationController   before_filter :require_signin, :only => :edit   caches_action :edit   def edit     @article = Article.find params[:id]   end   private     def require_signin       return true unless session[:user_id].nil?       redirect_to signin_url       return false     end end

Action caches aren't stored in the public directory; rather, the keys for action caches are derived from the current URL path, so a request to the edit action here would be cached with a key like localhost:3000/articles/edit/1 (the host and port are included in the cache key so that different subdomains can have independent caches). When /articles/edit/1 is requested the first time, Rails won't have a cache yet, so it will execute the action, deliver the response, and save it to the cache. The next time the route is requested, Rails will skip the action altogether and just deliver the response.

Although the action method is never called, action caching will process any filters before delivering the response (such as require_signin, in this case). That's a good thing, because it means you can benefit from caching even on pages that require authentication.

Information about the results of caching is sent to the environment's log file, so it's helpful to watch that during development. Here's an example that demonstrates how dramatic the speedup from action caching can be, even in the development environment:

# First request Processing ArticlesController#admin [GET]   Parameters: {"action"=>"admin", "controller"=>"articles"} Cached fragment: localhost:3000/articles/admin (0.00654) Completed in 0.43186 (2 reqs/sec) # Subsequent requests Processing ArticlesController#admin [GET]   Parameters: {"action"=>"admin", "controller"=>"articles"} Fragment read: localhost:3000/articles/admin (0.00048) Completed in 0.02311 (43 reqs/sec)

Remember that although action caching will evaluate before_filters, the entire output of the action will still be cached staticallylayout and all. That means that personalized content (e.g., "Signed in as Scott") or time-sensitive content (e.g., "Posted 42 minutes ago") won't play well with action caches. In some cases, fragment caching may be the best way around that problem. In others, Ajax can help by delivering a cached page and using Prototype to update it with dynamic pieces. For example, suppose you'd like to use page caching, but also present relative dates (e.g., "Posted three hours ago"). With a bit of JavaScript, you can both have and eat cake. All we need is a JavaScript counterpart to Rails' time_ago_in_words helper. Here's how it might look, added to Prototype's Element object:

Element.addMethods({   // based on courtenay's implementation at   // http://blog.caboo.se/articles/2005/05/03/cache-this   timeAgoInWords: function(element) {      system_date = Date.parse(element.innerHTML);     with(new Date(  )) {       user_date = Date.UTC(getUTCFullYear(), getUTCMonth(  ),                             getUTCDate(), getUTCHours(  ),                             getUTCMinutes(), getUTCSeconds(  ));     }     element.update(       function(minutes) {         if (minutes.isNaN)  return "";         minutes = Math.abs(minutes);         if (minutes < 1)    return ('less than a minute ago');         if (minutes < 45)   return (minutes + ' minutes ago');         if (minutes < 90)   return ('about an hour ago');         if (minutes < 1080) return (Math.round(minutes / 60) + ' hours ago');         if (minutes < 1440) return ('one day ago');         else return (Math.round(minutes / 1440) + ' days ago')       }((user_date - system_date) / (60 * 1000))     );   } });

This code expects that Rails will output dates in UTC (also known as Greenwich Mean Time). So instead of using the Rails time_ago_in_words helper in the view template, you'd output absolute dates, like this:

<span class='absoluteDate'><%= Time.now.utc %></span>

Then, drop in a little code that will search the document for every element with a certain CSS class, and refresh the dates:

$$('.absoluteDate').invoke('timeAgoInWords');

Now you can enjoy the best of both worldsthe lightning-fast performance of page caching, and the convenience of relative dates and times.

9.3.3. Fragment Caching

Behind the scenes, fragment caching uses the same system as action cachingaction caches are just fragment caches wrapped around an entire action at once. Fragment caches are created with the cache helper. For example, in a view template, like /views/articles/index.rhtml:

<h2>Articles</h2> <% cache do %>   <% Article.find(:all).each do |article| %>     <h3><%= article.title %></h3>     <%= simple_format article.body %>   <% end %> <% end %>

Notice that the cache helper is wrapping most of the templateeverything inside the block will be stored in a fragment cache, so that it's not evaluated if the cache exists. In this example, you might wonder why we aren't using action caching, since we're caching almost the entire template in a fragment. The essential difference is that in this example, the layout is not included in the cache, so it could contain personalized information.

Like action caches, fragment caches are stored according to the current URL path, so the fragment here would be cached with the key localhost:3000/articles. That means that by default, only one fragment is stored per action. If you want to cache multiple fragments per page, specify a suffix for the cache key using the :action_suffix option on the cache helper. For example:

<% Article.find(:all).each do |article| %>   <% cache :action_suffix => article.id do %>     <h3><%= article.title %></h3>     <%= simple_format article.body %>   <% end %> <% end %>

By moving the cache helper inside the loop and specifying the action suffix, multiple independent fragment caches are created (like localhost:3000/articles/1) and each can be expired independently.

9.3.4. Expiring Output Caches

So far, we've looked at how to create output caches in Rails. But that's only half of the puzzle; the other half is expiring those caches when the underlying content has changed.

Each caching method comes with a corresponding expiration method: expire_fragment, expire_action, and expire_page. To expire a stale cache, just pass in a hash of options that correspond to the cache key. For example, to clear the page cache with the key /articles/1, you'd call:

expire_page :controller => "articles", :action => "show", :id => "1"

Expiring action caches and fragment caches works essentially the same way:

expire_action   :controller => "articles", :action => "show", :id => "1" expire_fragment :controller => "articles", :action => "show", :id => "1"

In the context of a controller, cache expiration usually happens when records are added or updated. For example:

class Chapter9Controller < ApplicationController   caches_page :show   caches_action :edit      def create     Article.create params[:article]     expire_fragment :action => "index"     redirect_to articles_url   end   def update     Article.update params[:id], params[:article]     expire_action :action => "edit", :id => params[:id]     expire_page :action => "show", :id => params[:id]     expire_fragment :action => "index", :action_suffix => params[:id]     redirect_to article_url   end end

While these explicit expire_* methods are sufficient for expiring caches in fairly simple circumstances, they can quickly grow unwieldy. Often, one piece of content is reflected on multiple actionse.g., a show action, an index action, a web feed, and the home page. If you try to explicitly expire each cache every time the content is changed, your controllers won't stay DRY for long.

The solution is to use cache sweepers, special observer classes that intercept changes to ActiveRecord models and take care of expiring the necessary caches. Using sweepers consolidates your expiration logic. For information about using sweepers, see the Rails documentation at http://api.rubyonrails.com/classes/ActionController/Caching/Sweeping.html.




Ajax on Rails
Ajax on Rails
ISBN: 0596527446
EAN: 2147483647
Year: 2006
Pages: 103
Authors: Scott Raymond

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net