- •Introduction
- •Rails Is Agile
- •Finding Your Way Around
- •Acknowledgments
- •Getting Started
- •Models, Views, and Controllers
- •Installing Rails
- •Installing on Windows
- •Installing on Mac OS X
- •Installing on Unix/Linux
- •Rails and Databases
- •Keeping Up-to-Date
- •Rails and ISPs
- •Creating a New Application
- •Hello, Rails!
- •Linking Pages Together
- •What We Just Did
- •Building an Application
- •The Depot Application
- •Incremental Development
- •What Depot Does
- •Task A: Product Maintenance
- •Iteration A1: Get Something Running
- •Iteration A2: Add a Missing Column
- •Iteration A4: Prettier Listings
- •Task B: Catalog Display
- •Iteration B1: Create the Catalog Listing
- •Iteration B2: Add Page Decorations
- •Task C: Cart Creation
- •Sessions
- •More Tables, More Models
- •Iteration C1: Creating a Cart
- •Iteration C3: Finishing the Cart
- •Task D: Checkout!
- •Iteration D2: Show Cart Contents on Checkout
- •Task E: Shipping
- •Iteration E1: Basic Shipping
- •Task F: Administrivia
- •Iteration F1: Adding Users
- •Iteration F2: Logging In
- •Iteration F3: Limiting Access
- •Finishing Up
- •More Icing on the Cake
- •Task T: Testing
- •Tests Baked Right In
- •Testing Models
- •Testing Controllers
- •Using Mock Objects
- •Test-Driven Development
- •Running Tests with Rake
- •Performance Testing
- •The Rails Framework
- •Rails in Depth
- •Directory Structure
- •Naming Conventions
- •Active Support
- •Logging in Rails
- •Debugging Hints
- •Active Record Basics
- •Tables and Classes
- •Primary Keys and IDs
- •Connecting to the Database
- •Relationships between Tables
- •Transactions
- •More Active Record
- •Acts As
- •Aggregation
- •Single Table Inheritance
- •Validation
- •Callbacks
- •Advanced Attributes
- •Miscellany
- •Action Controller and Rails
- •Context and Dependencies
- •The Basics
- •Routing Requests
- •Action Methods
- •Caching, Part One
- •The Problem with GET Requests
- •Action View
- •Templates
- •Builder templates
- •RHTML Templates
- •Helpers
- •Formatting Helpers
- •Linking to Other Pages and Resources
- •Pagination
- •Form Helpers
- •Layouts and Components
- •Adding New Templating Systems
- •Introducing AJAX
- •The Rails Way
- •Advanced Techniques
- •Action Mailer
- •Sending E-mail
- •Receiving E-mail
- •Testing E-mail
- •Web Services on Rails
- •Dispatching Modes
- •Using Alternate Dispatching
- •Method Invocation Interception
- •Testing Web Services
- •Protocol Clients
- •Securing Your Rails Application
- •SQL Injection
- •Cross-Site Scripting (CSS/XSS)
- •Avoid Session Fixation Attacks
- •Creating Records Directly from Form Parameters
- •Knowing That It Works
- •Deployment and Scaling
- •Picking a Production Platform
- •A Trinity of Environments
- •Iterating in the Wild
- •Maintenance
- •Finding and Dealing with Bottlenecks
- •Case Studies: Rails Running Daily
- •Appendices
- •Introduction to Ruby
- •Ruby Names
- •Regular Expressions
- •Source Code
- •Cross-Reference of Code Samples
- •Resources
- •Index
CACHING, PAR T ONE 318
Actions
These options describe what should happen if a verification fails. If no actions are specified, the verification returns an empty response to the browser on failure.
:add_flash =>hash
Merges the given hash of key/value pairs into the flash. This can be used to generate error responses to users.
:redirect_to =>params
Redirect using the given parameter hash.
16.8 Caching, Part One
Many applications seem to spend a lot of their time doing the same thing over and over. A blog application renders the list of current articles for every visitor. A store application will display the same page of product information for everyone who requests it.
All this repetition costs us resources and time on the server. Rendering the blog page may require half a dozen database queries, and it may end up running through a number of Ruby methods and Rails templates. It isn’t a big deal for an individual request, but multiply that by many a thousand hits an hour, and suddenly your server is starting to glow a dull red. Your users will see this as slower response times.
In situations such as these, we can use caching to greatly reduce the load on our servers and increase the responsiveness of our applications. Rather than generate the same old content from scratch, time after time, we create it once and remember the result. The next time a request arrives for that same page, we deliver it from the cache, rather than create it.
Rails offers three approaches to caching. In this chapter, we’ll describe two of them, page caching and action caching. We’ll look at the third, fragment caching, on page 366 in the Action View chapter.
Page caching is the simplest and most efficient form of Rails caching. The Page caching first time a user requests a particular URL, our application gets invoked
and generates a page of HTML. The contents of this page are stored in the cache. The next time a request containing that URL is received, the HTML of the page is delivered straight from the cache. Your application never sees the request. In fact Rails is not involved at all: the request is handled entirely within the web server, which makes page caching very,
Prepared exclusively for Rida Al Barazi
Report erratum
|
CACHING, PAR T ONE |
319 |
|
very efficient. Your application delivers these pages at the same speed that |
|
|
the server can deliver any other static content. |
|
|
Sometimes, though, our application needs to be at least partially involved |
|
|
in handling these requests. For example, your store might display details |
|
|
of certain products only to a subset of users (perhaps premium customers |
|
|
get earlier access to new products). In this case, the page you display will |
|
|
have the same content, but you don’t want to display it to just anyone— |
|
|
you need to filter access to the cached content. Rails provides action |
|
|
caching for this purpose. With action caching, your application controller action caching |
|
|
is still invoked and its before filters are run. However, the action itself is |
|
|
not called if there’s an existing cached page. |
|
|
Let’s look at this in the context of a site that has public content and |
|
|
premium, members-only, content. We have two controllers, a login con- |
|
|
troller that verifies that someone is a member and a content controller |
|
|
with actions to show both public and premium content. The public con- |
|
|
tent consists of a single page with links to premium articles. If someone |
|
|
requests premium content and they’re not a member, we redirect them to |
|
|
an action in the login controller that signs them up. |
|
|
Ignoring caching for a minute, we can implement the content side of this |
|
|
application using a before filter to verify the user’s status and a couple of |
|
|
action methods for the two kinds of content. |
|
File 17 |
class ContentController < ApplicationController |
|
|
before_filter :verify_premium_user, :except => :public_content |
|
def public_content
@articles = Article.list_public end
def premium_content
@articles = Article.list_premium end
private
def verify_premium_user return
user = session[:user_id]
user = User.find(user) if user unless user && user.active?
redirect_to :controller => "login", :action => "signup_new" end
end end
As the content pages are fixed, they can be cached. We can cache the public content at the page level, but we have to restrict access to the cached premium content to members, so we need to use action-level caching for it. To enable caching, we simply add two declarations to our class.
Prepared exclusively for Rida Al Barazi
Report erratum
CACHING, PAR T ONE 320
File 17 |
class ContentController < ApplicationController |
before_filter :verify_premium_user, :except => :public_content
caches_page :public_content caches_action :premium_content
The caches_page directive tells Rails to cache the output of public_content( ) the first time it is produced. Thereafter, this page will be delivered directly from the web server.
The second directive, caches_action, tells Rails to cache the results of executing premium_content( ) but still to execute the filters. This means that we’ll still validate that the person requesting the page is allowed to do so, but we won’t actually execute the action more than once.
Caching is, by default, enabled only in production environments. You can turn it on or off manually by setting
ActionController::Base.perform_caching = true | false
You should make this change in your application’s environment files (in config/environments).
What to Cache
Rails action and page caching is strictly URL based. A page is cached according to the content of the URL that first generated it, and subsequent requests to that same URL will return the saved content.
This means that dynamic pages that depend on things not in the URL are poor candidates for caching. These include the following.
•Pages where the content is time based (although see Section 16.8,
Time-Based Expiry of Cached Pages, on page 323).
•Pages whose content depends on session information. For example, if you customize pages for each of your users, you’re unlikely to be able to cache them (although you might be able to take advantage of fragment caching, described starting on page 366).
•Pages generated from data that you don’t control. For example, a page displaying information from our database might not be cachable if non-Rails applications can update that database too. Our cached page would become out-of-date without our application knowing.
However, caching can cope with pages generated from volatile content that’s under your control. As we’ll see in the next section, it’s simply a question of removing the cached pages when they become outdated.
Prepared exclusively for Rida Al Barazi
Report erratum
CACHING, PAR T ONE |
321 |
Expiring Pages
Creating cached pages is only one half of the equation. If the content initially used to create these pages changes, the cached versions will become out-of-date, and we’ll need a way of expiring them.
The trick is to code the application to notice when the data used to create a dynamic page has changed and then to remove the cached version. The next time a request comes through for that URL, the cached page will be regenerated based on the new content.
Expiring Pages Explicitly
The low-level way to remove cached pages is with the expire_page( ) and expire_action( ) methods. These take the same parameters as url_for( ) and expire the cached page that matches the generated URL.
For example, our content controller might have an action that allows us to create an article and another action that updates an existing article. When we create an article, the list of articles on the public page will become obsolete, so we call expire_page( ), passing in the action name that displays the public page. When we update an existing article, the public index page remains unchanged (at least, it does in our application), but any cached version of this particular article should be deleted. Because this cache was created using caches_action, we need to expire the page using expire_action( ), passing in the action name and the article id.
File 17 |
def create_article |
|
article = Article.new(params[:article]) |
|
if article.save |
|
expire_page :action => "public_content" |
|
else |
|
# ... |
|
end |
|
end |
|
def update_article |
|
article = Article.new(params[:article]) |
|
if article.save |
|
expire_action :action => "premium_content", :id => article |
|
else |
|
# ... |
|
end |
|
end |
|
The method that deletes an article does a bit more work—it has to both |
|
invalidate the public index page and remove the specific article page. |
File 17 |
def delete_article |
Article.destroy(params[:id])
expire_page :action => |
"public_content" |
expire_action :action => |
"premium_content", :id => params[:id] |
end |
|
Prepared exclusively for Rida Al Barazi
Report erratum
|
CACHING, PAR T ONE |
322 |
|
Expiring Pages Implicitly |
|
|
The expire_xxx methods work well, but they also couple the caching func- |
|
|
tion to the code in your controllers. Every time you change something in |
|
|
the database, you also have to work out which cached pages this might |
|
|
affect. While this is easy for smaller applications, this gets more difficult |
|
|
as the application grows. A change made in one controller might affect |
|
|
pages cached in another. Business logic in helper methods, which really |
|
|
shouldn’t have to know about HTML pages, now needs to worry about |
|
|
expiring cached pages. |
|
|
Fortunately, Rails can simplify some of this coupling using sweepers. A sweepers |
|
|
sweeper is a special kind of observer on your model objects. When some- |
|
|
thing significant happens in the model, the sweeper expires the cached |
|
|
pages that depend on that model’s data. |
|
|
Your application can have as many sweepers as it needs. You’ll typically |
|
|
create a separate sweeper to manage the caching for each controller. Put |
|
|
your sweeper code in app/models. |
|
File 20 |
class ArticleSweeper < ActionController::Caching::Sweeper |
|
observe Article
#If we create a new article, the public list
#of articles must be regenerated
def after_create(article) expire_public_page
end
#If we update an existing article, the cached version
#of that particular article becomes stale
def after_update(article) expire_article_page(article.id)
end
#Deleting a page means we update the public list
#and blow away the cached article
def after_destroy(article) expire_public_page expire_article_page(article.id)
end private
def expire_public_page
expire_page(:controller => "content", :action => 'public_content') end
def expire_article_page(article_id)
expire_action(:controller => |
"content", |
|
:action |
=> |
"premium_content", |
:id |
=> |
article_id) |
end
end
The flow through the sweeper is somewhat convoluted.
Prepared exclusively for Rida Al Barazi
Report erratum
CACHING, PAR T ONE 323
•The sweeper is defined as an observer on one or more Active Record classes. In this case it observes the Article model. (We first talked about observers back on page 270.) The sweeper uses hook methods (such as after_update( )) to expire cached pages if appropriate.
•The sweeper is also declared to be active in a controller using the cache_sweeper directive.
class ContentController < ApplicationController
before_filter :verify_premium_user, :except => :public_content caches_page :public_content
caches_action :premium_content
cache_sweeper :article_sweeper,
:only => [ :create_article, :update_article, :delete_article ]
#...
•If a request comes in that invokes one of the actions that the sweeper is filtering, the sweeper is activated. If any of the Active Record observer methods fires, the page and action expiry methods will be called. If the Active Record observer gets invoked but the current action is not selected as a cache sweeper, the expire calls in the sweeper are ignored. Otherwise, the expiry takes place.
Time-Based Expiry of Cached Pages
Consider a site that shows fairly volatile information such as stock quotes or news headlines. If we did the style of caching where we expired a page whenever the underlying information changed, we’d be expiring pages constantly. The cache would rarely get used, and we’d lose the benefit of having it.
In these circumstances, you might want to consider switching to timebased caching, where you build the cached pages exactly as we did previously but don’t expire them when their content becomes obsolete.
You run a separate background process that periodically goes into the cache directory and deletes the cache files. You choose how this deletion occurs—you could simply remove all files, the files created more than so many minutes ago, or the files whose names match some pattern. That part is application-specific.
The next time a request comes in for one of these pages, it won’t be satisfied from the cache and the application will handle it. In the process, it’ll automatically repopulate that particular page in the cache, lightening the load for subsequent fetches of this page.
Prepared exclusively for Rida Al Barazi
Report erratum