Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Agile Web Development With Rails, 1st Edition (2005).pdf
Скачиваний:
28
Добавлен:
17.08.2013
Размер:
7.99 Mб
Скачать

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