Recipe 12.5. Mixing Static and Dynamic Content with Fragment Caching


Problem

One of the pages of your application contains several sections that are generated dynamically. You want to control performance by caching certain sections while leaving others dynamic.

Solution

Rails provides fragment caching to let you control which sections of a page are to be cached and which are to remain truly dynamic. You can even cache several sections of a page individually and have different criteria for how each section's cache is expired.

To specify the type of fragment store you want Rails to use:

config/environment.rb:

ActionController::Base.fragment_cache_store =                             :file_store, %W( #{RAILS_ROOT}/public/frags )

This tells Rails to store individual fragments in the public/frags directory.

Fragment caching make the most sense when you have an expensive query that's used to produce some rendered output. To demonstrate fragment caching, let the following get_time class method of the Invoice model play the part of a custom query that may take some time to execute:

app/models/invoice.rb:

class Invoice < ActiveRecord::Base   def self.get_time     find_by_sql("select now() as time;")[0].time   end  end

The following view template displays three different versions of the output of Invoice#get_time, which is made available to the show.rhtml template via the @report instance variable:

app/views/reports/show.rhtml:

<h1>Reports</h1> <%= link_to "show", :action => "show" %> | <%= link_to "expire_one", :action => "expire_one" %> | <%= link_to "expire_all", :action => "expire_all" %>  <br /><hr /> <%= @report %><br /> <% cache(:action => "show", :id => "report_one") do %>   <%= @report %><br /> <% end %> <% cache(:action => "show", :id => "report_two") do %>   <%= @report %><br /> <% end %>

The first occurrence of @report is displayed without any caching. The second two occurrences are each wrapped in a block and passed to the cache view helper. The cache helper stores each fragment in a file identified by the url_for style option hash that you pass it. In this example, the two fragments created are distinguished by their unique values of the id key.

The Reports Controller defines the following actions that demonstrate how you can expire each fragment on a page individually:

app/controllers/reports_controller.rb:

class ReportsController < ApplicationController   def show     @report = Invoice.get_time   end   def expire_one     @report = Invoice.get_time     expire_fragment(:action => "show", :id => "report_one")     redirect_to :action => "show"   end    def expire_all     @report = Invoice.get_time     expire_fragment(%r{show/.*})     redirect_to :action => "show"   end  end

The show action populates the @report instance variable and renders the show.rhtml template. The first time the template is rendered, each call to the cache helper generates a cached version of the block it surrounds.

The expire_one action demonstrates how you can expire a specific fragment by referencing it with the same url_for options hash that was used to create the cache fragment. The expire_all action shows how to remove all fragments that match a regular expression.

Discussion

Fragment caching is slower than page caching, but you trade some performance for the control of caching specific portions of a page while leaving others dynamic.

The solution stores two cache files in the cache directory on the file system specified by #{RAILS_ROOT}/public/frags. The command shows these files:

$ ls public/frags/localhost.3000/reports/show report_one.cache  report_two.cache

Notice the subdirectory that is created is named after the host and port number of the server. This can help distinguish fragments that may differ only by the subdomain name (e.g., rob.tupleshop.com/reports/show, tim.tupleshop.com/reports/show would create two distinct cache files).

Like Rails session data storage, you have several options to store cached fragments. The solution demonstrates storing fragments on your filesystem in the directory specified. Four storage options are listed; select one based on the specifics of your deployment setup or whichever proves to be fastest:


FileStore

Keeps the fragments on disk in the cache_path, which works well for all types of environments and shares the fragments for all the web server processes running off the same application directory.

Fragments are stored on your file system in the specified cache_path.

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


MemoryStore

Fragments are stored in your system's memory. This is the default if no store is specified explicitly. This store won't work with Rails running under straight CGI, although if you're using CGI, you're probably not worried about performance. You should monitor how much memory each of your server processes is consuming. Running out of RAM will quickly kill performance.

ActionController::Base.fragment_cache_store = :memory_store


DRbStore

Fragments are stored in the memory of a separate, shared DRb (distributed Ruby) process. This store makes one cache available to all processes but requires that you run and manage a separate DRb process.

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


MemCacheStore

Fragments are stored via the distributed memory object caching system, memcached. Requires the installation of a Ruby memcache client library.

ActionController::Base.fragment_cache_store =  :mem_cache_store, "localhost"

See Also

  • Section 12.6"




Rails Cookbook
Rails Cookbook (Cookbooks (OReilly))
ISBN: 0596527314
EAN: 2147483647
Year: 2007
Pages: 250
Authors: Rob Orsini

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