Chapter 10. ActionView

 

The very powerful and the very stupid have one thing in common. Instead of altering their views to fit the facts, they alter the facts to fit their views...which can be very uncomfortable if you happen to be one of the facts that needs altering.

 
 --Doctor Who

Controllers are the skeleton and musculature of your Rails application. In which case, models form the heart and mind, and your view templates (based on ActionView, the third major component of Rails) are your application’s skin—the part that is visible to the outside world.

ActionView is the Rails API for putting together the visual component of your application, namely the HTML and associated content that will be rendered in a web browser whenever someone uses your Rails application. Actually, in this brave new world of REST resources, ActionView is involved in generating any sort of output you want to pour out of your app.

ActionView contains a full-featured templating system based on a Ruby library named ERb. It takes data prepared by the controller layer and interleaves it with view code to create a presentation layer for the end user.

In this chapter, we cover the fundamentals of the ActionView framework, from the basics of templating, to effective use of partials, to the significant performance boosts possible via caching.

ERb Basics

Inside standard Rails view template files, you’re writing code in a kind of Ruby dialect, namely Embedded Ruby, or ERb, which is not unique to Rails. ERb is a standard library of the Ruby distribution.

An ERb document typically contains static HTML together with Ruby code that will be executed dynamically when the template is rendered. As I’m sure you know if you’ve done any Rails programming at all, to insert Ruby code into an ERb document, you place the code inside a pair of delimiters.

There are two different types of template delimiters available, which serve different purposes, and work identically to their counterparts in JSP and ASP technologies, which you might be familiar with already:

<% %> and <%= %>

The code between the delimiters will be executed in either case. The difference is that the return value of the first expression is discarded, while that of the second is inserted into the template’s output.

A very common bug during everyday Rails coding is to accidentally use the non-outputting delimiter where you actually needed one that produces output. You’ll tear your hair out trying to figure out why a certain value is not showing up on your screen, and yet no errors are being generated.

ERb Practice

You can get a feel for ERb outside of Rails since the ERb interpreter is a standard part of Ruby, and you can write and process ERb templates for practice using that interpreter. You simply use the command-line command erb.

For example, put the following template code in a file (demo.erb, perhaps):

Let's list all the methods of a Ruby string.

First we need a string.

<% str = "I am a string!" %>

We've got one: here it is:

  <%= str %>

Now, let's see its methods:

<% str.methods.sort[0...10].each_with_index do |m,i| %>
  <%= i+1 %>. <%= m %>
<% end %>

Now, run the file through erb:

$ erb demo.erb

You’ll see the following output:

Let's list all the methods of a Ruby string.

First we need a string.



We've got one: here it is:

  I am a string!

Now, let's see its methods -- or maybe just a few, so they don't
scroll off the screen:


  1. %

  2. *

  3. +

  4. <

  5. <<

  6. <=

  7. <=>

  8. ==

  9. ===

  10. =~

As you can see, all of the Ruby code inside the delimiters has been executed, including the assignment to str and the iteration with each. Only the code inside the equal-sign delimiters, however, has contributed to the output from the execution of the template.

You may also notice a lot of blank lines in the output. The presence of delimited code in the template has no special effect, one way or the other, on the line count. A line is a line, so the following line comes through as a blank line:

<% end %>

Tightening Up ERb Output

Rails gives you a way to remove at least some of the extra blank lines, by using modified delimiters:

<%- str.methods.sort[0...10].each_with_index do |m,i| -%>
  <%= i+1 %>. <%= m %>
<%- end -%>

Note the minus signs in the delimiters; they will suppress leading whitespace and extra newlines in the output of the template. Used judiciously, they can pretty up your template output significantly. It isn’t something that the end user cares about, but it might help you out whenever you’re examining the HTML source code of your application.

Commenting Out ERb Delimiters

Ruby’s commenting symbol # will work to cancel an ERb delimiter. Just insert it right after the percent sign of the opening delimiter tag.

<%#= str %>

The contents of the commented ERb tag will be ignored. You don’t want to leave commented-out code littering your template, but it’s useful for temporarily disabling something.

Conditional Output

One of the most common idioms you’ll use when coding Rails views is to conditionally output content to the view. The most elementary way to control conditional output is to use if/else statements in conjunction with <% %> as follows:

<% if @show_subtitle %>
  <h2><%= @subtitle %></h2>
<% end %>

A lot of times you can use inline if conditions and shorten your code, since the <%= tag doesn’t care if you feed it a nil value. Just add a postfix if condition to the statement:

<h2><%= @subtitle if @show_subtitle %></h2>

Of course, there’s a potential problem with the preceding example. The first, more verbose, conditional output will eliminate the <h2> tags entirely, but the second example does not.

There are a couple of ways to deal with the problem and keep it a one-liner.

First, there’s the butt-ugly solution that I’ve occasionally seen in some Rails applications, which is the only reason why I’m mentioning it here!

<%= "<h2>#{@subtitle}</h2>" if @show_subtitle %>

Aaargh! Ugly! The more elegant solution involves Rails’ content_tag helper method.

<%= content_tag('h2', @subtitle) if @show_subtitle %>

Helper methods, both the ones included in Rails and the ones that you’ll write on your own, are your main tool for building elegant view templates. Helpers are covered extensively in Chapter 11, “All About Helpers.”

RHTML? RXML? RJS?

As of Rails 2.0 the standard naming practice is to suffix ERb template files with .erb, but in earlier versions of Rails, we used .rhtml.

There are two other standard formats and suffixes for templates:

  • .builder (formerly .rxml) signals Rails to execute the template with Jim Weirich’s Builder::XmlMarkup library, used to easily create XML-based output. Use of Builder is covered in Chapter 15, “XML and ActiveResource.”

  • .rjs (unchanged) triggers Rails’ built-in JavaScript generation facilities, covered in Chapter 12, “Ajax on Rails.”

Note that at the end of this chapter, we do a quick survey of third-party templating languages available that integrate nicely with Rails, and why you might want to try them. For now, let’s continue with a review of layout and template usage.

Layouts and Templates

Rails has easy conventions for template usage, related to the location of templates with the Rails project directories.

The app/views directory, shown in Figure 10.1, contains subdirectories matching the name of each controller in the application. Within each controller’s view subdirectory, you place a template named to match its corresponding action.

A typical app/views directory

Figure 10.1. A typical app/views directory

The special app/views/layout directory holds layout templates, intended to be reusable containers for your views. Again, naming conventions are used to determine which templates to render, except this time it is the name of the controller that is used for matching.

In the case of layouts, the inheritance hierarchy of controllers comes into play. Most Rails applications have an application.rhtml file in their layout directory. It shares its name with the ApplicationController, which is typically extended by all the other controllers in an application; therefore it is picked up as the default layout for all views.

It is picked up, unless of course, a more specific layout template is in place, but most of the time it makes sense to use just one application-wide template, such as the simple one shown in Listing 10.1.

Example 10.1. A Simple, General-Purpose application.rhtml Layout Template

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">

<html>
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>My Rails Application</title>
  <%= stylesheet_link_tag 'scaffold', :media => "all" %>
  <%= javascript_include_tag :defaults %>
 </head>
 <body>
  <%= yield :layout %>
 </body>
</html>

The stylesheet_link_tag and javascript_include_tag methods are helpers that automatically insert standard LINK and SCRIPT tags into your document, for CSS and JavaScript files, respectively. The only other interesting part of that template is the call to yield :layout, which we discuss next.

Yielding Content

The Ruby language’s built-in yield keyword is put to good use in making layout and action templates collaborate. Notice the use of yield in the middle of the layout template:

<body>
  <%= yield :layout %>
</body>

In this case, the :layout symbol is a special message to the rendering system. It marks where to insert the output of the action’s rendered output, which is usually the template corresponding to that action.

You can add extra places in your layout where you want to be able to yield content, by including additional yield invocations—just make sure to use a unique identifier. A good example is a layout that has left and right sidebar content (simplified, of course):

<body>
  <div class="left sidebar">
    <%= yield :left %>
  </div>
  <div id="main_content">
    <%= yield :layout %>
  </div>
  <div class="right sidebar">
    <%= yield :right %>
  </div>
</body>

The center DIV element receives the main template content generated. But how do you give Rails content for the left and right sidebars? Easy—just use the content_for method in your template code in the following way:

<% content_for(:left) do %>
<h2>Navigation</h2>
<ul>
  <li>...
</ul>
<% end %>

<% content_for(:right) do %>
<h2>Help</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit,
   sed do eiusmod tempor incididunt ut labore et dolore magna
   aliqua. Ut enim ad minim veniam, quis nostrud ...
<% end %>

<h1>Page Heading</h1>
<p>The normal template content that will get yielded
  to :layout</p>
  ...

Besides sidebars and other types of visible content blocks, I suggest you yield for additional content to be added to the HEAD element of your page, as shown in Listing 10.2. It’s a super-useful technique, because Internet Explorer can occasionally get very ill-tempered about SCRIPT tags appearing outside of the HEAD element.

Example 10.2. Yielding Additional Head Content

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">

<html>
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>My Rails Application</title>
  <%= stylesheet_link_tag 'scaffold', :media => "all" %>
  <%= javascript_include_tag :defaults %>
  <%= yield :head %>
 </head>
 <body>
  <%= yield :layout %>
 </body>
</html>

Template Variables

We’ve seen how layouts and yielding content blocks work, but other than that, how does data get from the controller layer to the view? During preparation of the template, instance variables set during execution of the controller action will be copied over as instance variables of the template context.

Instance Variables

Copying of instance variables is the main form of communication from controller to view, and frankly, that behavior is one of the most basic facts about Rails and I’m sure that you know it:

class HelloWorldController < ActionController::Base
  def index
    @msg = "Hello, world!"
  end
end

# template file /app/views/hello_world/index.html.erb
<%= @msg %>

What you might not be aware of is that a lot more than just instance variables from the controller are copied over to the template. It’s not a good idea to depend on some of the following objects directly, and especially not to use them to do data operations. Remember that MVC standard practice is to let the controller layer prepare data for rendering, not the view!

assigns

Want to see everything that comes across the controller-view boundary? Throw <%= debug(assigns) %> into your template and take a look at the output. The assigns attribute is essentially internal to Rails and you should not use it directly in your production code.

base_path

Local filesystem path pointing to the base directory of your application where templates are kept.

controller

The current controller instance is made available via controller, before it goes out of scope at the end of request processing. You can take advantage of the controller’s knowledge of its name (via the controller_name attribute) and the action that was just performed (via the action_name attribute), in order to structure your CSS more effectively, as shown in Listing 10.3.

Example 10.3. Controller and Action Names as Body CSS Classes

<html>
  ...
  <body class="<%= controller.controller_name %>
               <%= controller.action_name %>">
    ...
  </body>
</html>

That would result in a BODY tag looking something like this, depending on the action executed:

<body class="timesheets index">

Hopefully you already know that the C in CSS stands for cascading, which refers to the fact that class names cascade down the tree of elements in your markup code and are available for creation of rules. Our trick in Listing 10.3 is to automatically include the controller and action name as classnames of your BODY element, so that you can use them to customize look and feel of the page very flexibly later on in the development cycle.

Here’s an example of how the technique would be used to vary the background of header elements depending on the controller path:

body.timesheets .header {
  background: url(../images/timesheet-bg.png) no-repeat left top
}

body.expense_reports .header {
  background: url(../images/expense-reports-bg.png) no-repeat left top
}

flash

The flash is a view variable that you’ll definitely use on a regular basis. It has popped up in larger code samples throughout the book so far, whenever you want to send the user a message from the controller layer, but only for the duration of the next request.

A common Rails practice is to use flash[:notice] to hold benign notice messages, and flash[:error] for communication of a more serious nature. Personally, I like to conditionally output both of them in DIV elements, right at the top of my layout, and use CSS to position them, as shown in Listing 10.4.

Example 10.4. Standardized Flash Notice and Error Placement in application.html.erb

<html>
  ...
  <body>
    <%= content_tag 'div', h(flash[:notice]),
         :class => 'notice', :id => 'notice' if flash[:notice] %>
    <%= content_tag 'div', h(flash[:error]),
         :class => 'notice error', :id => 'error' if flash[:error] %>

    <%= yield :layout %>
  </body>
</html>

Using the content_tag helper facilitates making the entire output conditional. Otherwise, I’d need an if block around the HTML markup and the scheme would get pretty messy.

headers

The headers variable holds the values of HTTP headers that accompanied the request being processed. Not much you can do with headers in your view, other than occasionally view them for debugging reasons. Put <%= debug(headers) %> somewhere in your layout and you’ll see output similar to the following in your browser, after refreshing the page of course:

--
Status: 200 OK
cookie:
- - adf69ed8dd86204d1685b6635adae0d9ea8740a0
Cache-Control: no-cache

logger

Have something to record for posterity in the logs while you’re rendering the view? Use the logger method to get the view’s Logger instance, RAILS_DEFAULT_LOGGER unless you’ve changed it.

params

This is the same params hash that is available in your controller, containing the key/value pairs of your request. I’ll occasionally use a value from the params hash directly in the view, particularly when I’m dealing with pages that are subject to filtering or row sorting.

<p>Filter by month:
  <%= select_tag(:month_filter,
        options_for_select(@month_options, params[:month_filter])) %>

It’s very dangerous from a security perspective to put unfiltered parameter data into the output stream of your template. The following section, “Protecting the Integrity of Your View from User-Submitted Content,” covers that topic in depth.

request and response

The HTTP request and response objects are exposed to the view, but other than for debugging purposes, I can’t think of any reason why you would want to use them directly from your template.

session

The session variable is the user’s session hash. There might be situations where it’d be okay to pull values out to affect rendering, but I shudder to think that you might try to set values in the session from the view layer. Use with care, and primarily for debugging, just like request and response.

Protecting the Integrity of Your View from User-Submitted Content

If any data in your application is user-submitted, or in any way originates from a source that you don’t trust completely, then you need to keep in mind the need to escape and sanitize template content. Otherwise, you leave yourself wide open to a variety of malicious hacker attacks.

For example, consider the following template snippet, which copies the value of params[:page_number] into its output quite innocently:

<h1>Search Results</h1>
<h2>Page <%= params[:page_number] %></h2>

Easy way to include the page number, right? Sure. But consider what happens if someone submits a request to that page that embeds a SCRIPT tag and some malicious code as the value of the page_number request parameter? Bingo! The malicious code goes right into your template!

Fortunately, there is a very simple way to prevent this form of attack and the Rails core developers expect you to use it very often, so they named the method with one character: h.

<h1>Search Results</h1>
<h2>Page <%=h(params[:page_number]) %></h2>

The h method escapes HTML content—instead of passing it through as markup, it will convert the less-than and greater-than characters into their respective character entities, and in so doing, cripple malicious injection attacks. Of course, it doesn’t do anything to content that doesn’t have markup.

But what if you are trying to display user-submitted HTML, as is often the case with web applications that feature blog-style commenting facilities? In those cases, you can try using the sanitize method of ActionView::Helpers::TextHelper. It’ll strip out the tags most commonly used in attacks: FORM and SCRIPT, but leave others intact. The sanitize method is covered in depth in Chapter 11.

Partials

A partial is a fragment of template code. The Rails way is to use partials to factor view code into modular chunks that can be assembled in layouts with as little repetition as possible. The syntax for including a partial within a template starts with render :partial => "name". Partial template names must begin with an underscore, which serves to set them apart visually within a given view template directory. However, you leave the underscore out when you refer to them.

Simple Use Cases

The simplest partial use case is simply to extract a portion of template code. Some developers divide their templates into logical parts by using partial extraction. Sometimes it is easier to understand the structure of a screen if the significant parts are factored out of it. For instance, Listing 10.5 is a simple user registration screen that has its parts factored out into partials.

Example 10.5. Simple User Registration Form with Partials

<h1>User Registration</h1>
<%= error_messages_for :user %>
<% form_for :user, :url => users_path do -%>
  <table class="registration">
   <tr>
    <td class="details demographics">
     <%= render :partial => 'details' %>
     <%= render :partial => 'demographics' %>
    </td>
    <td class="location">
     <%= render :partial => 'location' %>
    </td>
   </tr>
   <tr>
    <td colspan="2"><%= render :partial => 'opt_in' %></td>
   </tr>
   <tr>
    <td colspan="2"><%= render :partial => 'terms' %></td>
   </tr>
  </table>
  <p><%= submit_tag 'Register' %></p>
<% end -%>

While we’re at it, let me pop open one of those partials. To conserve space, we’ll take a look at one of the smaller ones, the partial containing the opt-in check boxes of this particular app. The source is in Listing 10.6; notice that its name begins with an underscore.

Example 10.6. The Opt-In Partial in the File app/views/users/_opt_in.html.erb

<fieldset id="opt_in">
  <legend>Spam Opt In</legend>
  <p><%= check_box :user, :send_event_updates %>
     Send me updates about events!<br/>
  <%= check_box :user, :send_site_updates%>
     Notify me about new services</p>
</fieldset>

Personally, I like partials to be entirely contained inside a semantically significant markup container. In the case of the opt-in partial in Listing 10.6, both check box controls are contained inside a single <fieldset> element, which I’ve given an id attribute. Following that rule, more as a loose guideline than anything else, helps me to mentally identify how the contents of this partial are going to fit inside the parent template. If we were dealing with other markup, perhaps outside of a form, I might choose to wrap the partial markup inside a well-identified <div> container, instead of a <fieldset>.

Why not include the <td> markup inside the partial templates? It’s a matter of style—I like to be able to see the complete markup skeleton in one piece. In this case, the skeleton is the table structure that you see in Listing 10.5. If portions of that table were inside the partial templates, it would obfuscate the layout of the page. I do admit that this is one of those areas where personal style and preference should take precedence and I can only advise you as to what has worked for me, personally.

Reuse of Partials

Since the registration form is neatly factored out into its component parts, it is easy to create a simple edit form using some of its partials, as in Listing 10.7.

Example 10.7. Simple User Edit Form Reusing Some of the Same Partials

<h1>Edit User</h1>
<%= error_messages_for :user %>
<% form_for :user, :url => user_path(@user),
                   :html => { :method => :put } do -%>
  <table class="settings">
   <tr>
    <td class="details">
     <%= render :partial => 'details' %>
    </td>
    <td class="demographics">
     <%= render :partial => 'demographics' %>
    </td>
   </tr>
   <tr>
    <td colspan="2" class="opt_in">
      <%= render :partial => 'opt_in' %>
    </td>
   </tr>
  </table>
  <p><%= submit_tag 'Save Settings' %></p>
<% end -%>

If you compare Listings 10.5 and 10.7, you’ll notice that the structure of the table changed a little bit in the Edit form, and it has less content than the registration form. Perhaps the location is handled in greater detail on another screen, and certainly you don’t want to require agreement of terms every time the user changes her settings.

Shared Partials

Until now we’ve been considering the use of partials that reside in the same directory as their parent template. However, you can easily refer to partials that are in other directories, just by prefixing the directory name. You still leave off the underscore, which might feel a little weird.

Let’s add a captcha partial to the bottom of the registration form from Listing 10.5, to help prevent spammers from invading our web application:

  ...
  <tr>
    <td colspan="2"><%= render :partial => 'terms' %></td>
   </tr>
   <tr>
    <td colspan="2"><%= render :partial => 'shared/captcha' %></td>
   </tr>
  </table>
  <p><%= submit_tag 'Register' %></p>
<% end -%>

Since the captcha partial is used in various different parts of the application, it makes sense to let it reside in a shared folder rather than any particular view folder. However, you do have to be a little bit careful when you move existing template code into a shared partial. It’s quite possible to inadvertently craft a partial that depends implicitly on where it’s rendered.

For example, take the case of the Rails-talk mailing list member with a troublesome partial defined in login/_login.rhtml:

<% form_tag do %>
 <label>Username:</label>
 <%= text_field_tag :username, params[:username] %>
 <br />
 <label>Password:</label>
 <%= password_field_tag :password, params[:password] %>
 <br />
 <%= submit_tag "Login" %>
<% end %>

The login form submission worked when he rendered this partial as part of the login controller’s login action (“the login page”), but not when it was included as part of the view for any other section of his website. The problem is that form_tag (covered in the next chapter) normally takes an optional action parameter telling it where to post its information. If you leave out the action, the form will post back to its current URL, which will vary for shared partials, depending on where they’re being used from.

Passing Variables to Partials

Partials inherit the instance variables available to their parent templates. That’s why the form helpers used in the partials of Listings 10.5 and 10.7 work: They rely implicitly on an @user variable to be in scope. I feel it’s fine to use this implicit variable sharing in some cases, particularly when the partials are tightly bound to their parent templates. It would be especially true in cases where the only reason you broke out partials in the first place was to reduce the size and complexity of a particularly large template.

However, once you get into the practice of breaking out partial templates for application-wide reuse, depending on implicitly passed variables gets a lot more dicey. That’s why Rails supports the passing of locally scoped variables to partial templates, as a hash parameter named :locals, as in the following snippet:

render :partial => 'shared/address', :locals => { :form => form }

The names and values of the :locals hash are converted into locally scoped variables (no @ sign) in the partial. Listing 10.8 is a variation on the registration template. This time we’re using the version of form_for that yields a block parameter representing the form to its form helper methods. We’ll pass that form parameter on, too.

Example 10.8. Simple User Registration Template Passing Form as Local Variable

<h1>User Registration</h1>
<%= error_messages_for :user %>
<% form_for :user, :url => users_path do |form| -%>
  <table class="registration">
   <tr>
    <td class="details address demographics">
     <%= render :partial => 'details',
                :locals => {:form => form } %>
     <%= render :partial => 'shared/address',
                :locals => {:form => form } %>
    </td>
   </tr>
  </table>
  <p><%= submit_tag 'Register' %></p>
<% end -%>

And finally, in Listing 10.9 we have the shared address form.

Example 10.9. A Simple Shared Address Partial Using Local Variable

<fieldset class="address">
  <legend>Address</legend>
  <p><label>Street</label><br/>
    <%= form.text_area :street, :rows => 2, :cols => 40 %></p>
  <p><label>City</label><br/>
    <%= form.text_field :city %></p>
  <p><label>State</label><br/>
    <%= form.text_field :state, :size => 2 %></p>
  <p><label>Zip</label><br/>
      <%= form.text_field :zip, :size => 15 %></p>
</fieldset>

The form helper methods, which we’ll cover in Chapter 11, have a variation in which they are called on the form variable yielded by the form_for method. That is exactly what we passed on to these partials via the :locals hash.

The local_assigns Hash

If you need to check for the presence of a certain local variable, you need to do it by checking the local_assigns hash that is part of every template. Using defined? variable won’t work due to limitations of the rendering system.

<% if local_assigns.has_key? :special %>
<%= special %>
<% end %>

Render Collections

One of the best uses of partials is to render collections. Once you get into the habit of rendering collections with partials, you won’t want to go back to the relative ugliness of cluttering your templates with for loops and each.

render :partial => 'entry', :collection => @entries

Simple and precise, and dependent on a naming convention. The most important is how the object being rendered is exposed to the partial template. It is set as a local variable named the same as the partial template itself. The partial corresponding to the last code snippet would reference a local variable named entry.

<%= div_for(entry) do %>
  <%= h(entry.description) %>
  <%= distance_of_time_in_words_to_now entry.created_at %> ago
<% end %>

The partial_counter Variable

There’s another variable set for collection-rendered partials that doesn’t get much attention. It’s a 0-indexed counter variable that tracks the number of times the partial has gotten rendered. It’s useful for rendering numbered lists of things.

The name of the variable is the name of the partial, plus _counter.

<%= div_for(entry) do %>
  <%= entry_counter %>:
  <%= h(entry.description) %>
  <%= distance_of_time_in_words_to_now entry.created_at %> ago
<% end %>

Sharing Collection Partials

If you wanted to use the same partial that you use with a collection, execpt with a single entry object, you’d have to pass it that single instance via the :locals hash described in the preceding section, like this:

render :partial => 'entry', :locals => {:entry => @entry }

I’ve also seen the following hack done as a way to avoid needing that locals parameter:

<% entry = @entry if @entry %>
<% div_for(entry) do %>
  <%= h(entry.description) %>
  <%= distance_of_time_in_words_to_now entry.created_at %> ago
<% end %>

That works, but it’s nasty, repetitive, and introduces an implicit optional dependency on @entry. Don’t do stuff like that. Use the :locals parameter, as intended.

Logging

If you take a look at your development log, you’ll notice that it shows which partials have been rendered and how long they took.

Rendering template within layouts/application
Rendering listings/index
Rendered listings/_listing (0.00663)
Rendered listings/_listing (0.00296)
Rendered listings/_listing (0.00173)
Rendered listings/_listing (0.00159)
Rendered listings/_listing (0.00154)
Rendered layouts/_login (0.02415)
Rendered layouts/_header (0.03263)
Rendered layouts/_footer (0.00114)

Caching

The Rails caching mechanism can make your production-deployed application very responsive. Caching lets you specify that anything from entire pages down to fragments of the page should be captured to disk as HTML files and sent along by your web server on future requests with minimal involvement from Rails itself.

There are three types of caching in Rails:

  • Page cachingThe output of an entire controller action is cached to disk, with no further involvement by the Rails dispatcher.

  • Action cachingThe output of an entire controller action is cached to disk, but the Rails dispatcher is still involved in subsequent requests, and controller filters are executed.

  • Fragment cachingArbitrary bits and pieces of your page’s output can be cached to disk to save the time of having to render them in the future.

Caching in Development Mode?

I wanted to mention up front that caching is disabled in development mode. If you want to play with caching, you’ll need to edit the following setting in the config/environments/development.rb file:

config.action_controller.perform_caching             = false

Of course, remember to change it back before checking it back into your project repository, or you might face some very confusing errors down the road.[1]

Page Caching

The simplest form of caching is page caching, triggered by use of the caches_page macro-style method in a controller. It tells Rails to capture the entire output of the request to disk so that it is served up directly by the web server on subsequent requests without the involvement of the dispatcher. Nothing will be logged to the Rails log, nor will controller filters be triggered—absolutely nothing to do with Rails will happen, just like the static HTML files in your project’s public directory.

Action Caching

By definition, if there’s anything that has to change on every request or specific to an end user’s view of that page, page caching is not an option. On the other hand, if all we need to do is run some filters that check conditions before displaying the page requested, the caches_action method will work. It’s almost like page caching, except that controller filters are executed prior to serving the cached HTML file. That gives you the option to do some extra processing or even redirect if necessary.

Action caching is implemented with fragment caching (covered later in this chapter) and an around filter (covered in Chapter 2, “Working with Controllers”). The cached action content is keyed based on the current host and the path, which means that it will still work even with Rails applications serving multiple subdomains using a DNS wildcard. Also, different representations of the same resource, such as HTML and XML, are treated like separate requests and cached separately.

We’ll use our lil_journal[2] sample application as the basis for our code snippets in this section. The application should have public versus private entries, so for default requests, we should run a filter that figures out whether the visitor is logged in and redirects them to the public action if necessary. Listing 10.10 has the EntriesController code.

Example 10.10. The EntriesController of lil_journal

class EntriesController < ApplicationController

  before_filter :check_logged_in, :only => [:index]

  caches_page :public
  caches_action :index

  def public
    @entries = Entry.find(:all,
                          :limit => 10,
                          :conditions => {:private => false})

    render :action => 'index'
  end

  def index
    @entries = Entry.find(:all, :limit => 10)
  end

  private

    def check_logged_in
      redirect_to :action => 'public' unless logged_in?
    end

end

The public action displays only the public entries and is visible to anyone, which makes it a candidate for page caching. However, since it doesn’t require its own template, we just call render :action => 'index' explicitly at the end of the public action.

Design Considerations

Knowing that your application will eventually require caching should influence your design decisions. Projects with optional authentication often have controller actions that are impossible to page or action-cache, because they handle both login states internally. That would have been the case in Listing 10.10 if we had written the index action to handle both public and private display:

def index
  opts = {}
  opts[:limit] = 10
  opts[:conditions] = {:private => false } unless logged_in?
  @posts = Entry.find(:all, opts)
end

Most of the time, you won’t have too many pages with completely static content that can be cached using cache_page or cache_action, and that’s where fragment caching comes into play.

Fragment Caching

Users are accustomed to all sorts of dynamic content on the page, and your application layout will be filled with things like welcome messages and notification counts. Fragment caching allows us to capture parts of the rendered page on disk and serve them up on subsequent requests without needing to render their content again. The performance improvement is not as dramatic as with page or action caching, since the Rails dispatcher is still involved. However, you can still give your application a blazing speed boost by using fragment caching.

The cache Method

Fragment caching is by nature something you specify in your view template rather than at the controller level. You do so using the cache method of ActionView. It takes a block, which lets you wrap content that should be cached.

Once we log in to our Lil’ Journal sample application, the header section should really display information about the logged-in user, so action-caching the index page is out of the question. We’ll remove the action_cache directive from the EntriesController, but leave cache_page in place for the public action. Then we’ll go into the entries/index.html.erb template and add fragment caching, as shown in Listing 10.11.

Example 10.11. Lil’ Journal’s entries/index.html.erb Template with Fragment Caching

<%= content_tag :h1, "#{@user.name}'s Journal" %>
<% cache do %>
  <%= render :partial => 'entry', :collection => @entries %>
<% end %>

Easy as that—the HTML output of rendering the collection of entries is stored in the fragment cache associated with the entries/index page. That’s fine if we’re only caching one fragment of the page, but most of the time we’ll need to give the fragment some extra identification.

Named Fragments

The cache method takes an optional name parameter.

<% cache "my_fragment" do %>

If you leave it blank, as we have in Listing 10.11, it caches its content keyed to the URL of its parent page. That’s an acceptable solution as long as there is only one fragment on the page.

If we’re caching more than one fragment on the page, we need to add an extra identifier, so that we don’t suffer name collisions. Listing 10.12 is an enhanced version of the entries page, where we’ve added the display of recent comments in the sidebar.

Example 10.12. The Entries Page with Two Fragment Cache Directives

<%= content_tag :h1, "#{@user.name}'s Journal" %>

<% cache(:fragment => 'entries') do %>
  <%= render :partial => 'entry', :collection => @entries %>
<% end %>

<%- content_for :sidebar -%>

  <% cache(:fragment => 'recent_comments') do %>
    <%= render :partial => 'comment', :collection => @recent_comments
%>
  <% end %>

<% end %>

After the code in Listing 10.12 is rendered, there will be two fragments stored in the cache, keyed as follows:

  • /entries/index?fragment=entries

  • /entries/index?fragment=recent_comments

The fact that Rails uses the page’s URL scheme to key fragments in the cache is an elegant solution to a somewhat difficult problem. Consider for instance, what would happen if you added pagination to the Lil’ Journal application and pulled up the second page of entries. Without further work, a pair of additional fragments would be correctly cached for future use:

  • /entries/index?page=2&fragment=entries

  • /entries/index?page=2&fragment=recent_comments

Note

Rails uses the url_for mechanism to construct unique identifiers for fragments out of convenience. There’s no requirement that your fragment keys correspond to actual working URLs in your application.

Global Fragments

Sometimes, you’ll want to fragment-cache content that is not specific to a single URL of your application. To add globally keyed fragments to the cache, we’ll again use the name parameter of the cache helper method, but this time we’ll give it a string identifier instead of a hash.

To demonstrate, let’s add a requirement that our Lil’ Journal application should display user statistics on every page. In Listing 10.13, we cache the stats partial for every user, using their name and a "_stats" suffix as the key.

Example 10.13. The Entries Page with Global User Stats

<%= content_tag :h1, "#{@user.name}'s Journal" %>

<% cache(:fragment => 'entries') do %>
  <%= render :partial => 'entry', :collection => @entries %>
<% end %>

<%- content_for :sidebar -%>

  <% cache(@user.name + "_stats") do %>
    <%= render :partial => 'stats' %>
  <% end %>

  <% cache(:fragment => 'recent_comments') do %>
    <%= render :partial => 'comment', :collection => @recent_comments %>
  <% end %>

<% end %>

There’s one part we’ve left out of our caching discussion so far, and that is the subject of expiring cached content once it is stale—once the data it is reflecting has become out of date.

Avoiding Extra Database Activity

Once you have fragments of your view cached, it no longer makes sense to do the database queries that supply those fragments with their data. After all, the results of those database queries will never be used until the cached fragments are expired. The read_fragment method lets you check for the existence of cached content, and takes the same parameters that you used with the associated cache method.

Here’s how we would modify the index action accordingly:

def index
  unless read_fragment(:fragment => 'entries')
    @entries = Entry.find(:all, :limit => 10)
  end
end

Now the finder method will only get executed if the cache needs to be refreshed.

Expiration of Cached Content

Whenever you use caching, you need to consider any and all situations that will cause the cache to become stale, out of date. Then you need to write code that sweeps away the old content, so to speak, making room for new content to be cached in its place.

Expiring Pages and Actions

The expire_page and expire_action methods let you explicitly delete content from the cache, so that it is regenerated on the next request. You identify the content to expire using the url_for conventions used elsewhere in Rails. Listing 10.14 shows how we’ve added expiration to the create method of the entries controller.

Example 10.14. The Entries create Action

1  def create
2    @entry = @user.entries.build(params[:entry])
3    if @entry.save
4      expire_page :action => 'public'
5      redirect_to entries_path(@entry)
6    else
7      render :action => 'new'
8    end
9  end

Notice how line 4 of Listing 10.14 explicitly expires the page associated with the public action. If you think about it, though, it’s not only the create action that invalidates the cache. The update and destroy actions would invalidate it too.

In your applications, particularly if you’re doing REST-style resources, remember that different representations of the same resource are treated like separate requests and cached separately. If you’ve cached the XML response of an action, you’ll have to expire it by appending :format => :xml to the action specification.

Expiring Fragments

Whoops! I almost forgot (seriously) that we also have cached fragments to clear out, using the expire_fragment method. Now the create action looks like this:

def create
  @entry = @user.entries.build(params[:entry])
  if @entry.save
    expire_page :action => 'public'
    expire_fragment(:fragment => 'entries')
    expire_fragment(:fragment => (@user.name + "_stats"))
    redirect_to entries_path(@entry)
  else
    render :action => 'new'
  end
end

Using regular expressions in expiration calls

There’s actually still (!) a serious problem with the expiration routine that we put in the create action. Remember we said that the fragment caching of entries would work with pagination, and that we’d have cached fragments keyed like this: '/entries/index?page=2&fragment=entries'

As a result, just doing expire_fragment(:fragment => 'entries') will only clear the first page from the cache. For that reason, the expire_fragment method understands regular expressions, and we’ll need to use them in our code:

expire_fragment(r%{entries/.*})

There has to be a better way to handle invalidation than remembering to stick a bunch of complicated expiration statements in all your action methods. Besides, caching is a unique concern, which seems to indicate that it should be applied in a more aspect-oriented fashion.

Automatic Cache Expiry with Sweepers

A Sweeper class is kind of like an ActiveRecord Observer object, except that it’s specialized for use in expiring cached content. When you write a sweeper, you tell it which of your models to observe for changes, just as you would with callback classes and observers.

Listing 10.15 is a sweeper to keep the caching of Lil’ Journal’s entries in order.

Example 10.15. An Entries Sweeper for Lil’ Journal

class EntrySweeper < ActionController::Caching::Sweeper
  observe Entry

  def expire_cached_content(entry)
    expire_page :controller => 'entries', :action => 'public'
    expire_fragment(r%{entries/.*})
    expire_fragment(:fragment => (entry.user.name + "_stats"))
  end

  alias_method :after_save, :expire_cached_content
  alias_method :after_destroy, :expire_cached_content

end

Once you have a Sweeper class (put it in your app/models directory), you need to tell your controller to use that sweeper in conjunction with its actions. Here’s the top of the revised entries controller for Lil’ Journal:

class EntriesController < ApplicationController

  before_filter :check_logged_in, :only => [:index]
  caches_page :public
  cache_sweeper :entry_sweeper, :only => [:create, :update, :destroy]

  ...

Like many other controller macros, the cache_sweeper method takes :only and :except options. There’s no need to bother the sweeper for actions that can’t modify the state of the application, so we do indeed include the :only option in our example.

Like the related observers, sweepers are not limited to observing just one model. The main thing to remember if we go down that route is that our callback methods will need to know how to handle all of them. Ruby’s case statement may come in handy, as shown in Listing 10.16, a full revision of the EntrySweeper, which may now observe Comment as well as Entry objects.

Example 10.16. The EntrySweeper Revised to Observe and Handle Both Entries and Comments

class EntrySweeper < ActionController::Caching::Sweeper
  observe Entry, Comment

  def expire_cached_content(record)
    expire_page :controller => 'entries', :action => 'public'
    expire_fragment(r%{entries/.*})

    user = case entry
      when Entry then record.user
      when Comment then record.entry.user
    end

    expire_fragment(:fragment => (user.name + "_stats"))
  end

  alias_method :after_save, :expire_cached_content
  alias_method :after_destroy, :expire_cached_content

end

Cache Logging

If you’ve turned on caching during development, you can actually monitor the Rails log for messages about caching and expiration.

Processing Entries#index (for 127.0.0.1 at 2007-07-20 23:07:09) [GET]
 ...
Cached page: /entries.html (0.03949)


Processing Entries#create (for 127.0.0.1 at 2007-07-20 23:10:50)
[POST]
 ...
Expired page: /entries.html (0.00085)

It’s a good way to see whether your caching is actually working as expected.

Action Cache Plugin

The Action Cache plugin by Tom Fakes and Scott Laird is a recommended drop-in replacement for the built-in Rails caching facilities. It doesn’t change the Caching API at all, only the underlying implementation.

script/plugin install http://craz8.com/svn/trunk/plugins/action_cache

These are the major features of the Action Cache plugin:

  • Stores cached entries as YAML streams (instead of just HTML) so that the Response headers from the original response can be returned along with cached content.

  • Adds a last-modified header to the response so that clients use a get-if-modified HTTP request. If the client already has cached content, sends a 304 Not Modified response.

  • Ensures that only requests with a 200 OK status are cached. Otherwise, error pages and empty content can get stuck in the cache (and cause difficult-to-diagnose problems.)

  • Allows developers to override Rails with their own implementation of cache key generation.

  • Allows an action to specify an optional Time To Live value for a response, before cached content associated with the response will be automatically expired.

  • Allows control over whether caching occurs for an action at runtime based on request parameters. (For instance, never cache content for site administrators.)

  • A new method, expire_all_actions, clears out the entire action cache contents.

  • Changes the expire_action implementation to actually use the Regexp fragment expiry call, causing all matching cache items to be cleared. For those of you using REST, and providing HTML, JS, and XML for the same action, all three will be expired when you expire one of them with code like this: expire_action :controller => 'foo', :action => 'bar'

Cache Storage

Unlike session data, fragment-cached data can grow to be quite large. Rails gives you four different options for cache storage:

  • FileStoreKeeps 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.

  • MemoryStoreKeeps the fragments in the memory, and can potentially consume an unacceptable amount of memory per process.

  • DRbStoreKeeps the fragments in the memory of a separate, shared DRb process. This option only keeps one cache around for all processes, but requires that you run and manage a separate DRb process as part of your deployment process.

  • MemCacheStoreWorks like DRbStore, but uses a proven cache server named memcached. I informally surveyed a bunch of Rails professionals about cache storage, and all of them suggested that memcache is the best option.[3]

Configuration Example

The :memory_store option is enabled by default.

ActionController::Base.fragment_cache_store = :memory_store

ActionController::Base.fragment_cache_store = :file_store,
"/path/to/cache/directory"
ActionController::Base.fragment_cache_store = :drb_store,
"druby://localhost:9192"
ActionController::Base.fragment_cache_store = :mem_cache_store,
"localhost"

Limitations of File-Based Storage

As long as you’re hosting your Rails application on a single server, setting up caching is fairly straightforward and easy to implement (of course, coding it is a different story).

If you think about the implications of running a cached application on a cluster of distinct physical servers, you might realize that cache invalidation is going to be painful. Unless you set up the file storage to point at a shared filesystem such as NFS or GFS, it won’t work.

Manual Sweeping with rake

If you do choose file-based storage, you probably want to give yourself a way to manually clear your application’s cached content. It’s not difficult to do using Rake. Just add a file to the lib/tasks folder named cache.rake. You’ll be creating a task similar to the one shown in Listing 10.17.

Example 10.17. A cache_sweeper Custom Rake Task

desc "Manually sweep the cache"
task :cache_sweeper do
  FileUtils.rm_rf Dir[File.join(RAILS_ROOT, "public", "entries*")]
#pages
  FileUtils.rm_rf Dir[File.join(RAILS_ROOT, "tmp", "cache*")]
#fragments
end

I used entries in the example task, but remember that you may have to add one or more of your own FileUtils.rm_rf statements corresponding to the pages that your application is caching.

As a final note, keep in mind, that it’s common to use the FileUtils.rm_rf brute-force approach in sweepers instead of the expire_* methods, mostly because a lot of times it’s just easier to blow away entire cached directories and let them be rebuilt as needed.

Conclusion

In this chapter, we’ve covered the ActionView framework with a detailed explanation of ERb templating and how the Rails rendering system works. We’ve also covered the use of partials in-depth, since their use is essential for effective Rails programming.

From the relatively simple principles of templating, we jumped into a complicated subject: caching. Knowing how to implement caching will save you the day you work on Rails application that really need to perform. Indeed, developers of high-traffic Rails websites tend to see Rails as a very fancy HTML generation platform, which helps them create content for caching.

Now it’s time to cover the mechanism whereby you can inject a whole bunch of smarts into your view layer without cluttering up your templates: Helpers.

References

1.

In his great screencast on the subject, Geoffrey Grosenback suggests adding another environment mode to your project named development_with_caching, with caching turned on just for experimentation (http://peepcode.com/products/page-action-and-fragment-caching).

2.

Subversion URL: http://obiefernandez.com/svn/projects/awruby/prorails/lil_journal.

3.

If you go the memcache route, definitely consider using Err the Blog’s CacheFu plugin, available at http://require.errtheblog.com/plugins/browser/cache_fu.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.7.208