Chapter 4. Controller: In Depth

In this chapter, you will learn about the controller layer of the MVC framework implemented in Rails applications. The controller layer of an MVC application is responsible for figuring out what to do with external input. The controller layer interprets user input and responds to user requests by communicating with the model layer, and rendering views using the view layer. You can think of the controller as the conductor of the application; it determines which views to show based on the input received.

The controller layer should be the only layer of your application that knows the client is actually a Web browser. All interaction with the Web server and knowledge of such interaction should be confined to methods in your controller classes. This layer should not contain a great deal of your business logic; the business logic should be contained within your model layer. Theoretically, you could rewrite the controller layer to adapt the application to a different platform, such as a non-browser environment.

Rails implements the controller layer primarily using a component called ActionController. ActionController is joined with ActionView to make up the Action Pack component of Rails. Action Pack provides the functionality for processing incoming requests from the browser and for generating responses to the browser.

What is ActionController?

In Rails, your application's controller and view layers use the Action Pack components of Rails. Controllers are implemented using the ActionController component of Action Pack. The ActionController component provides you with an easy-to-use functionality related to the following areas:

  • Routing

  • Interfacing with the Web server

  • Using sessions

  • Cache management

  • Rendering view templates

Throughout this chapter, you can learn how ActionController helps you with each of these tasks.

In a typical Rails application, you might have many controller classes. Each of your application's model classes will typically have a controller class for working with that model. For example, you might have defined a user model or a book model. You would probably also want to create a UserController class and a BookController class. These controller classes would handle requests to show, create, update, and delete these types of model objects. Each controller class you write will inherit from the Rails class, ActionController::Base. This is how your controller classes gain the power of Rails.

All About Routing

A Web application receives requests from a browser, takes action to process those requests, and returns a response that is directed back to the browser. Sitting in between the browser and the Web application is usually a Web server. The Web server passes the browser requests to your Rails Web application, but once they are passed into your application, where do they go from there? The ActionController component uses a routing subsystem to route the Web requests to the appropriate method in your Rails application. The routing subsystem routes requests to methods that are called action methods. The action methods are contained in controller classes.

The action methods in your application's controller classes receive the incoming requests and invoke methods contained in other layers of the application, such as the model layer; using the view layer, a response is generated and returned to the requesting browser.

The Rails routing mechanism is very flexible and can be adapted to meet any special requirements that you might have. However, you can get basic routing functionality immediately without having to write a single line of configuration code. The basic routing functionality is useful for many complete Web applications that you may write.

Let's start by looking at an example request from the browser to this URL:

www.bookstore.com/book/show/1234

This request is received by the Web server and passed to the routing subsystem of Rails. The routing subsystem interprets this as a request to invoke the show method of the BookController class and pass an id parameter of 1234. The flow of events that occur when this request is received looks like this:

  1. The Web server passes the request to the Rails routing subsystem.

  2. The routing subsystem parses the request, identifying the requested controller and action.

  3. A new instance of the requested controller is created.

  4. The process method of the controller is called and is passed request and response details.

  5. The controller calls the specified action method.

Note

For more information on templates, see Chapter 5.

Observe the pattern here in relation to how the request was routed:

http://server_url/controller name/action name/optional id

This is the default routing mechanism that is built into Rails. The first path element following the server URL is the name of a controller to invoke. The second path element is an action contained within that controller, and the last path element is the id of a data item. Not all of your actions will require a parameter, and so the id path element is optional. For example, a request to list all of the books contained in the store might look like this:

http://www.bookstore.com/book/list

Defining custom routes

You do not have to use the default routing mechanism if it does not meet your needs. Let's look at an example of where you might want to define a custom routing mechanism. You might have a Web application that lets users view articles posted on previous days. Perhaps you want to be able to accept URLs that look like this sample:

http://myarticles.com/article/2008/1/20

You would like this to be interpreted as a request for articles that were created on the date 1/20/2008, and so the general routing pattern would look like this:

http://myarticles.com/article/year/month/day

So how do you tell Rails about that routing pattern? The answer is through a file that was generated when you generated the Rails skeleton for your application using the rails command. This file is the routes.rb file found in the config subdirectory of your Web application directory. Open up config/routes.rb and take a look at the default routes. You should see these routes already defined:

map.connect ':controller/:action/:id.:format'
map.connect ':controller/:action/:id'

Each of the map.connect statements defines a route that connects URLs to controllers and actions. The string passed to the map.connect method specifies a pattern to match the URL against. The routes you see defined look for three path elements. The path elements are mapped to the fields named in this string and placed into a parameters hash. For example, the path book/show/1234 produces the following parameters hash:

@params = {:controller => 'book'
:action => 'show'
:id => 1234 }

The routing subsystem then invokes the show method of the store controller and passes a parameter of :id with a value of 1234.

Now that you understand how the default route is implemented, let's figure out how you would define a route to map the URL containing year, month, and date path elements. Remember that, as in the case of the default route, you must pass a route pattern string to the map.connect method. The pattern string matching the desired URLs would look like this:

"article/:year/:month/:day"

This pattern matches the path elements of the URLs that you want to use. If you create a route using this string passed to the map.connect method, does that define a complete route? If you think it does, let me ask this: What action method would these requests be routed to? This route pattern does not include an action name. Therefore, you know the route is not complete as is. The pattern string is complete, though. It contains enough information to match the desired URL format. You need to pass an additional parameter to map.connect to specify an action. This would give you a route that looks like this:

map.connect "article/:year/:month/:day",
:controller => "article",
:action => "show_date"

This route works as long as the URL always contains a year, a month, and a date. Suppose you want to also allow users to enter only a year, or only a year and month, and see the articles corresponding to those timeframes. To make that work, you can tell the route that the :day and :month elements do not have to be there, or in Ruby code, you can say that these elements can have a nil value. You would now modify the route to look like this:

map.connect "article/:year/:month/:day",
:controller => "article",
:action => "show_date",
:day => nil,
:month => nil

Now things are beginning to look pretty good, but what if someone tried to use a URL that looked like this:

http://myarticles.com/article/8

This URL doesn't look like it is meant for the route you just defined. However, remember when you said that the :day and :month elements are not required, and the route was modified to allow those elements to not be present? Given that, this URL can match the route you defined. It would interpret the value 8 as the :year parameter. This is probably not what you want, though. The route would be better if it restricted the date fields to be valid date values. Let's add that validation by modifying the route to look like this:

map.connect " article/:year/:month/:day",
:controller => " article",
:action => "show_date",
:requirements => {:year => /(19|20)dd/,
:month => /[01]?d/,
:day => /[0-3]?d/ },
:day => nil,
:month => nil

Now, a :requirements parameter is added that specifies requirements for the :year, :month, and :day fields. Regular expressions are used to make sure that each of the date fields contains a valid value. Now you have a well-defined route for accepting requests like the one you originally specified:

http://myarticles.com/article/2008/1/20

Defining a custom default route

Now you understand a bit about how requests are routed to your controllers and actions in a Rails application. Routing is accomplished by parsing path elements of a URL to map a request to a controller and action method. This convention is okay for most of your Web application, but what about your application's home page? Typically, you want the home page to be routable just by going to your application's server URL without specifying any path elements. For example, the home page for myarticles.com should be reachable by using this simple URL:

http://www.myarticles.com

Further suppose that your home page featured some dynamic functionality that required processing by your code. How would Rails know what controller and method to route the home page request to? The answer is to define a route that matches a URL with no path elements, like this:

map.connect "",
:controller => "home",
:action => "index"

By specifying an empty pattern string, this pattern matches against any URL that it sees, including the home page request with no path elements. It tells the routing subsystem to send this request to the index method of the home controller.

Warning

You must also delete public/index.html in order for the default route to work. This is because Rails will bypass the routing mechanism for any html files that it finds directly in the public directory.

Be careful where you place this in the routes.rb file, though. If a URL matches multiple routes defined in routes.rb, the first one matched will be the route used. This is important to keep in mind. If you placed this route as the first route listed in your routes.rb file, this would catch every request, and every request would end up getting routed to the index method of the home controller. You would want to list this route at the end of all your routes.

Using named routes

The routes that you've seen so far are called anonymous routes. There is also a way of creating a named route. A named route will allow you to simplify the URLs that you use in your code.

Named routes are very simple to create. Instead of using the method map.connect in your routes.rb file, you replace the word connect with the name you want to give to that route. For example, you could create a named route for the year/month/date articles route that was created previously by renaming it like this:

map.dates " article/:year/:month/:day",
:controller => " article",
:action => "show_date",
:requirements => {:year => /(19|20)dd/,
:month => /[01]?d/,
:day => /[0-3]?d/ },
:day => nil,
:month => nil

You should also rename your home page route to home, like this:

map.home "",
:controller => "home",
:action => "index"

This creates a name route called home that will use the home controller and the index action. Next, you'll see how useful named routes can be.

Within the code that you write, either in classes or view templates, you'll often need to specify links. Especially for situations where there is a link that you find yourself using over and over again, such as a link to an action that is accessible from every page, it is useful to have a way of specifying that link without having to hardcode the full URL into every page template. Named routes give you the ability to specify a link URL using a convenient name.

Without taking advantage of a named route, you might have a link specified like this repeated in many of your template files:

<%= link_to 'Home',:controller => 'home',:action => 'index' %>

However, if you take advantage of the named route you created above using map.home in the routes.rb file, this link can be specified like this:

<%= link_to 'Home', home_url %>

By using home_url, Rails knows that you are referring to the route named home and will automatically use the controller and method specified in that route. Not only does this reduce the amount of code that you have to type, but it also abstracts the home page link into a single place-the named route. If you decided to change the name of the controller or action method used to display the home page, you will not have to change all of your view templates that use that link. You only have to change the named route.

You can use named routes to generate URLs in your controller code by using the name of the route followed by _url. For example, in the previous link, you used the home_url method to create a link to the home page of the application.

You can also pass parameters to the URL generation methods as a hash to specify details of the URL. For example, here is how you could create a URL to request all articles for the year 2007 using the dates route that you created earlier:

@articles_2007 = dates_url(:year => 2007)

The parameter :year maps to the :year parameter that you defined in the dates route definition. You can also pass in parameters that are not defined in the route and they will be appended to the query string as additional parameters. For example, consider the following code:

@articles_2007 = dates_url(:year => 2007,:group_by => 'weekday')

This would result in having an additional parameter named group_by passed into your action method. You could then use this additional parameter to construct an appropriate query.

Constructing URLs with url_for

Rails provides a method named url_for that allows you another way of constructing URLs within your code. Recall the route that is defined for you when you first generate a new Rails application:

map.connect ':controller/:action/:id'

Assume that somewhere in your code, you want to create a URL that would map to the action and controller specified in that route. The url_for method constructs a URL given a set of options that you pass to it.

@link = url_for(:controller=>"article",:action=>"show",
  :id=>123)

This would create a link in the @link variable with a value similar to this:

http://www.myapp.com/article/show/123

The url_for method takes the parameters passed to it and creates a URL that maps those parameters to a pattern specified in one of your routes. This abstracts the details of how your URLs are specified out of your code. The details of your URLs have to exist in one place only, your routes.rb file.

In addition to specifying the controller and action when you call the url_for method, there are a number of additional parameters that url_for supports that allow you to customize the URLs that it generates. These options are listed in Table 4.1.

Table 4.1. url_for Supported Parameters

Option

Data Type

Description

:anchor

String

Adds an anchor name to the generated URL.

:host

String

Sets the host and port name used in the generated URL.

:only_path

Boolean

Specifices that only the path should be generated. The protocol, host name, and port are left out of the generated URL.

:protocol

String

Sets the protocol used in the Generated URL, that is, 'https.'

:user

String

Used for inline HTTP authentication. (used only if:password is also present)

:password

String

Used for inline HTTP authentication. (used only if:user is also present)

:escape

Boolean

Determines whether the returned URL will be HTML escaped or not. (true by default)

:trailing_slash

Boolean

Appends a slash to the generated URL.

Note

With the release of Rails 2.0 a new style of routing become popular. That style is called RESTful routes. This is a way of constructing routes that correspond to a RESTful architecture. RESTful architecture and routes are covered in Chapter 12.

Creating and Using Controllers

Now that you understand routes, you should know how requests end up in the action methods of your controller classes. Now let's see what your controller methods do with those requests.

Generating controllers

Controller classes can be automatically generated for you using the Rails script/generate script. You pass the controller parameter and the name of the controller you want to generate to the script like this:

ruby script/generate controller User

You would run the previous command from the root of your Rails application directory. If you run this command, you will see output similar to this:

exists app/controllers/
exists app/helpers/
create app/views/user
exists test/functional/
create app/controllers/user_controller.rb
create test/functional/user_controller_test.rb
create app/helpers/user_helper.rb

Three files are created by running the script: the controller class file, a functional test file for the controller, and a helper file. The controller file is what you want to look at now, so open up the app/controllers/user_controller.rb file. The file should look like this:

class UserController < ApplicationController
end

The only thing you see in this file is that it is a class that extends the ApplicationController class. The ApplicationController class was automatically generated by Rails when you created the application skeleton using the rails command. Take a look at that file, which you can find in app/controllers/application.rb:

# Filters added to this controller apply to all controllers in
   the application.
# Likewise, all the methods added will be available for all
   controllers.

class ApplicationController < ActionController::Base
    helper:all # include all helpers, all the time

    # See ActionController::RequestForgeryProtection for details
    # Uncomment the:secret if you're not using the cookie session
   store
    protect_from_forgery #:secret =>
    '7dbe320f76e9f0135ab2eb16457a5b20'
end

The ApplicationController extends a Rails internal class called ActionController::Base. Extending this class is what gives your controllers all of the builtin functionality that they have, which you'll learn about in this chapter. In the ApplicationController, you see a method named protect_from_forgery being called. This method adds protection from malicious attacks against your application.

A common type of attack that is carried out against Web applications is called a cross-site request forgery (CSRF). This type of attack is prevented by adding a token based on the current session to all forms and Ajax requests. This allows your controllers to accept only the requests that contain this forgery protection token.

There is a parameter named :secret that is commented out by default. The :secret parameter can be used to specify a salt value which is used to generate the forgery protection token. The salt value assists in making the token more secure. If you do a Google search on CSRF you can learn more about this kind of attack.

In the ApplicationController, you also see a call to the method helper with a parameter of :all being passed. This makes all of the helper classes available to all of your controllers.

Take a look at the helper file that was created by the generator. Open up app/helpers/user_helper.rb.

module UserHelper
end

This is an empty Ruby module. Helper modules are where you will place methods that you want to share across your view templates. Often, you'll need a method that is used by all of the view templates associated with the controller. All of the methods that you put into the helper modules are automatically made available to all of the view templates associated with that controller.

Note

You can start defining methods for the helper files in Chapter 5 when views are covered.

If you know the names of some of the action methods that you want to include in your controller class, you can also specify those to the script/generate script and have stubs for those methods created, as well. For example, run this generate command:

ruby script/generate controller Book list show new

This creates a Book controller class for you with method stubs for each of the method names you passed: list, show, and new. Your generated controller class will look like this:

class BookController < ApplicationController
 def list
 end

 def show
 end
def new
 end
end

You might have also noticed that a few additional files were created corresponding to each of the action methods:

app/views/book/list.html.erb
app/views/book/show.html.erb
app/views/book/new.html.erb

These are Rails view templates, which you will use to describe the pages rendered as a result of each of those actions. You'll learn more about the view templates in Chapter 5 when views are covered in depth. For the remainder of this chapter, it's safe to ignore those files.

That's all you need to get started with creating a controller class. In the remainder of this chapter, you'll see how you can build up the empty controller class to handle all of your browser requests.

Action methods

Methods contained in your controllers that have requests routed to them are called action methods. Note that not all methods in your controllers are necessarily action methods. You may have some helper methods used by your action methods that are never routed to.

As you saw in the previous section, you can have the script/generate script create stubs for your action methods, or you can hand-code any action methods that you want to add. Action methods are defined just like any standard instance method. What makes action methods different is that they are able to use functionality provided to your class by Rails to access the Web request and response information.

Rails provides built-in functionality to allow you to easily perform the following functions in the action methods of your controllers:

  • Use request parameters submitted by the browser.

  • Render a template in response to a request.

  • Send a redirect to the requesting browser.

  • Send short feedback messages to the browser.

Using request parameters

Many of the requests that your action methods will receive will contain request parameters submitted by the browser, which you will need to process the requested action. This includes parameters submitted in a URL using an HTTP GET request, and parameters contained in the HTTP header of a POST request. As an example, assume the following URL is passed to your Rails application:

http://www.myapp.com/user/show/123

Using the default route, Rails will route this request to the show method of your UserController class. A user id is also passed to the show method. The show method should look up the user identified by that user id and display details about that user. Let's start creating a show method to perform those actions:

class UserController < ApplicationController
    def show
    end
end

The first thing you need to do is get the user id that is passed to the show method. Rails makes all of the request parameters available through a params hash. Obtain the user id from the params hash in your show method:

class UserController < ApplicationController
    def show
        user_id = params[:id]
    end
end

Recall from the earlier discussion of Rails routing mechanisms that the id parameter passed in the URL path is made available as the :id parameter. You could then use a model class to retrieve the user corresponding to that id:

class UserController < ApplicationController
    def show
        user_id = params[:id]
        @user = User.find(user_id)
    end
end

Assuming, you have a User model, calling its find method and passing the user id will retrieve the User object for the desired user. Notice that an instance variable, @user, receives the returned User object. Any instance variables are automatically available to your view templates. By setting the User object as an instance variable, your view template will be able to use that object to display information about the user.

Rendering templates

Rails templates define the views of your Rails application. At the end of your action, you typically will render a template to return a new Web page to the user. Continuing with the example request to display information about a particular user through the show method, add a render call to display a user view template:

class UserController < ApplicationController
    def show
        user_id = params[:id]
        @user = User.find(user_id)
        render:template => "show"
    end
end

Here the render method is called with an options hash containing a single value, the name of a template to render. In this case, the show template is specified. This would cause the view template stored in app/views/user/show.html.erb to be rendered. While this correctly illustrates how to render a template, if the template you want to render is named the same as the action method, the render call is not necessary. By default, an action method that does not perform any renders or redirects will render a template containing the same name as the action method, if such a template exists. With that in mind, modify the method to only call the render method if the user being looked up is not found:

class UserController < ApplicationController
    def show
        if !@user = User.find(params[:id])
            render:template => "user_not_found"
        end
    end
end

Now if the user is found, no explicit render is called, so the default show.html.erb template will be rendered, which is what you want. However, if the user is not found, a template named user_not_found.html.erb will be rendered. This template could contain an error message for the user. One other change was made here. Instead of using a temporary user_id local variable to hold the user id value, the params[:id] value is now passed directly to the User.find method, saving you a line of code.

Note

Where views are covered in Chapter 5, you can see many more ways to use the render method to create other responses.

Redirects

In addition to rendering a template, Rails also has built-in functionality that allows you to easily send a redirect to the browser. Let's look at an example of where you might want to use a redirect:

def create
    @book = Book.new(params[:book])
    if @book.save
        redirect_to:action=>'show',:id=>@book.id
    else
        redirect_to:action=>'new'
    end
end

In this example, a new Book object is created and saved. If the save operation is successful, the user is redirected to the show page to show the details of the new book. If the save is not successful, the user is redirected back to the new book page so that the user can try again. Like the render method, the redirect_to method takes an options hash to determine where it should redirect the browser to. In this first use of redirect_to above, an :action and an :id value are passed. This instructs the browser to redirect to the action method specified. Because a controller is not specified, the same controller that contains this create method is assumed. You could have also passed a :controller value to redirect to a method in a different controller.

The :id parameter passed to redirect_to is also passed on to the show method. There is also a shortcut you can use to specify the book's id. You could write the redirect_to method like this:

redirect_to:action=>'show',:id=>@book

Just by passing the @book object as the :id value, Rails extracts the id value from the @book object and uses that as the value for the :id parameter.

Sending feedback with flash

The flash feature of Rails is a way of passing simple feedback messages from your application back to the browser. Do not confuse this use of the word flash, with the name of the Flash Web development technology from Adobe. They are not related. In this case, flash is the name for an internal storage container used by Rails to store temporary display data. The flash area is implemented as a special kind of hash, and you work with it much like you work with a regular hash in Ruby.

Data that is stored into the flash is kept for the duration of one action, and then it is removed. The flash is a convenient place to store short status messages that need to be communicated from one action to the next. Examples of where flash is commonly used include displaying results of a login attempt, results of a file upload, or results of a form submission.

You populate the flash in a controller method by using a symbol key value passed to flash, like this:

flash[:login_result] = 'Successful Login'

Take a look at a controller method that uses the flash:

def login
    if login_user
        flash[:notice] = 'Successful Login'
        redirect_to:action=>'home'
    else
        flash[:notice] = 'Your login attempt was unsuccessful'
        redirect_to:action=>'create'
    end
end

Here, the result of a login attempt is stored in a flash :notice parameter. The flash parameters you use can be any name you choose, but it is common practice to use :notice, :warning, and :error to denote common types of status messages.

The data you store in flash is then used within your view templates. Although view templates are covered in detail in Chapter 5, let's preview a portion of a view template that uses the flash:

<div>
    <h1>The Book Store</h1>
    <% unless flash[:notice].blank? %>
        <div id="notification"><%= flash[:notice]   %></div>
    <% end %>
</div>

This small template snippet displays the flash[:notice] string unless it is blank. This snippet could be put into your layout templates so that the notice would be displayed on any of your views that included a notice. This also gives a reason for using standard names for your flash messages, such as the recommended :notice, :warning, and :error. If the flash names were pagespecific, you wouldn't be able to use this snippet in a common layout file.

Using flash.now and flash.keep

Normally any data that you store into the flash area is cleared after one request. If you would like to extend the life of data stored in the flash, you can do that using the flash.keep method. The flash.keep method will extend the life of the flash for one additional request.

In the previous examples where flash was used, after storing a string into flash, the next page was displayed by using a redirect. Remember that when storing data in the flash, it is kept for the life of one action (that is, one request). If, in the controller where you set the flash, you rendered a template instead of doing a redirect, the flash would still be kept until the next page request, which is probably not what you wanted. If you are rendering a template instead of redirecting when you set flash, you should use the flash.now method. The flash.now method changes the behavior of the flash so that the data is kept for only the current request.

Sending other types of data to the browser

You've seen how to render templates and send redirects to the browser. Both of these actions normally result in a new HTML page being displayed. However, Rails also assists you if you want to send non-HTML data to the browser from an action method.

Returning text

If you want to return text to the browser from one of your action methods, you use the render method with the :text hash key, like this:

render:text => "hello, world"

This will send the specified text to the browser without being wrapped in any template or layout. This can be useful for testing purposes.

Returning JSON data

If you are using Ajax in your Web application, you may often want to render JSON data from your action methods. This can be done using the :json parameter with the render method as shown here:

render:json => {:name => "Timothy"}

This will return the specified hash as a JSON encoded string. This also sets the content type for the HTTP response as application/json.

Rendering a specific file

If you want to render a template that is not located in the normal place that Rails looks for view templates, or you want to render some other type of file, you can use the render method with the :file parameter.

# Renders the template located at the absolute path specified
render:file => "c:/path/to/some/template.erb"

The call to render above will render the Erb template stored in the non-standard location specified.

Returning XML

Returning XML content to the browser is no more difficult than returning text or JSON data was. You use the render method with the :xml parameter like this:

render:xml => book.to_xml

This returns the XML string generated by the book.to_xml method to the browser. The correct HTTP content type for XML data, text/xml, is also set for you.

Using Filters

Filters are methods that are run before or after a controller's action methods are executed. Filters are very useful when you want to ensure that a given block of code is run, no matter what action method is called. Rails supports three types of filter methods:

  • Before filters

  • After filters

  • Around filters

Before filters

Before filters are executed before the code in the controller action is executed. Filters are defined at the top of a controller class that calls them. To set up a before filter, you call the before_filter method and pass it a symbol that represents the method to be executed before action methods. Here is an example of how you would use a before filter:

class UserController < ApplicationController
    before_filter:verify_logged_in
    def verify_logged_in
       ...
    end
end

In this example, the method verify_logged_in is applied as a before filter. Before any of the action methods are called, the verify_logged_in method is called.

Instead of passing a symbol to the before_filter method, you could pass a snippet of Ruby code that would be executed as the before filter.

You may not want a filter to apply to all of the action methods in a controller. For example, if the UserController class had an action method named login, which handled the logging in of a user, you obviously would not want to apply the verify_logged_in filter before calling that action. You can exclude methods from a filter by passing the :except parameter, like this:

before_filter:verify_logged_in,:except =>:login

Now the filter is called before all of the controller's action methods, except for the login method. You can also pass a comma-separated list of methods to exclude:

before_filter:verify_logged_in,:except =>:login,:list

If you find the list of exclusions growing to the point that you want more methods without the filter than you have the filter being applied to, you can use the :only parameter, which has the opposite effect. When you pass the :only parameter, all action methods will be excluded from the filter, except for those specified in the :only parameter:

before_filter:verify_logged_in,:only =>:show,:edit

In this example, the verify_logged_in filter method is called only before the show and edit methods. It is not called before any other action methods in the controller.

After filters

After filters are executed after the code in the controller action is executed. As with before filters, you define after filters at the top of the controller class in which they are called. You use the after_filter method to set up an after filter, like this:

class PhotoController < ApplicationController
    after_filter:resize_photo
    def resize_photo
       ...
    end
end

The setup is identical to the way you set up before filters. The method represented by the symbol passed to after_filter is executed after your controller action methods. Like with the before_filter method, you could pass a snippet of Ruby code instead of a symbol to the after_filter method.

You can also use the :except and :only parameters with after filters, just as they are used with before filters.

Around filters

Around filters contain code that is executed both before and after the controller's code is executed. Around filters are useful when you would otherwise want to use both a before and an after filter. The way you implement an around filter is different and a bit more complex than how before and after filters are implemented. A common way to implement an around filter is to define a special class that contains before and after methods. Let's walk through the implementation of a common example of where around filters are used to provide logging for your controllers.

First, create a logging class that contains a before and after method:

class ActionLogger
    def before(controller)
        @start_time = Time.new
    end

    def after(controller)
        @end_time = Time.now
        @elapsed_time = @end_time.to_f - @start_time.to_f
        @action = controller.action_name
# next save this logging detail to a file or database
    table
    end
end

In this ActionLogger class, the before method captures the time an action is started, and the after method captures the time an action completes, the elapsed time, and the name of the action that is being executed. You could then write this data to a log file, or perhaps use a log model that you would create an instance of here and save it with this data.

Now, look at how you use the ActionLogger class as an around filter. In your controller class, simply add the around_filter method and pass an instance of the ActionLogger as a parameter, like this:

class PhotoController < ApplicationController
    around_filter ActionLogger.new
end

The ActionLogger will now be called before and after all of the action methods that you add to the PhotoController class.

You can also pass method references and blocks to the around_filter method. If you pass a method reference, the reference must point to a method that has a call to the yield method to call the action being called. The example below is borrowed from the Rails API documentation. This shows how you might use an around filter to catch exceptions from your action methods.

around_filter:catch_exceptions

private
def catch_exceptions
    yield
rescue => exception
    logger.debug "Caught exception! #{exception}"
    raise
end

This provides simple exception handling for all of your action methods.

The final way of using an around filter is by passing a block to the around_filter method. When you pass a code block to the around_filter method, the block explicitly calls the action using action.call instead of using the yield method. Below is an example that logs a before and after message around each action method call.

around_filter do |controller, action|
    logger.debug "before #{controller.action_name}"
    action.call
    logger.debug "after #{controller.action_name}"
end

Protecting filter methods

Something that I haven't talked about yet is the fact that you can potentially route to any method (that you put into a controller class) from a browser. For example, assume that you have the default route defined:

map.connect ':controller/:action/:id'

You could type the following address in your browser to make a direct call to the after filter method that was defined in the previous section:

www.myapp.com/photo/resize_photo

However, when you defined the resize_photo filter method, you probably did not intend this method to be routable from a browser call. In this case, how can you prevent this method from being routable?

The answer goes back to something that is common in most object-oriented programming languages: the ability to protect methods within a class. All methods contained in Ruby classes have one of these protection levels:

  • Public: These methods are accessible by any external class or method that uses the class in which they are defined.

  • Protected: These methods are accessible only within the class in which they are defined, and in classes that inherit from the class in which they are defined.

  • Private: These methods are only accessible within the class in which they are defined. No external class or method can call these methods.

By default, methods are always public, meaning that any external class or method can access them. You can declare methods as protected or private by putting a protected or private keyword before the methods that you want to protect. This example contains protected and private methods:

class SuperHero
    def say_hello
    ...
    end

    protected
    def use_power
       ...
    end

    private
    def get_real_identity
       ...
    end
def assign_sidekick
       ...
    end
end

This class has one protected method and two private methods. In the SuperHero class, because you don't want just anyone to know a hero's true identity, the get_real_identity method is made a private method. Only other methods within the SuperHero class can call it. The use_power method can be called only by methods within the SuperHero class or methods in classes that inherit from the SuperHero class.

Note

Protected and private methods are not routable from the browser.

Getting back to the discussion of filter methods, anytime you define a filter method, you should make it a protected or private method, as you normally do not want your filter methods to be routable from the browser.

Working with Sessions

Sessions are a common technique in Web applications to remember data that you want to preserve across multiple requests. Remember that the underlying protocol of the Web, HTTP, is a stateless protocol, meaning that each request to the server is like calling a new invocation of your application. Inherently, there is no memory or state preserved across requests. This was fine when the Web was used mostly as a home for static informational pages without a lot of dynamic content.

However, as the Web became more dynamic and Web applications became more popular, the need to maintain state across multiple browser requests became pressing. This is where the session comes in handy. The session is a container that allows you to store information that you want to use across multiple requests. The session data is stored either in the server's file system, the server's memory, or in a database.

Sessions are commonly used to store information about a user's browsing session. For example, when a user logs into your application, information about that user is saved to the session so that the user can navigate around within the Web application without having to log in for each new page request. Without sessions, your Web application would not be able to remember the user as they browsed through various pages of the Web application. Sessions are also commonly used to store shopping cart information on a shopping site, as well as user preferences.

Each session stored on the server, either in the database or in the file system, is identified by a unique id. The unique id is stored in a session cookie that is sent to the browser. The browser returns this session cookie with each page request so that the server can look up the session and preserve state across requests.

Rails has built-in support that makes using a session simple in a Rails application. Rails automatically creates a session for each user of your application. You store information into the session by using the session hash. The session hash is used just like any regular Ruby hash. For example, you can store a user's id into the session like this:

session[:user_id] = @user.id

As a result of storing this to the session hash, the user id is saved to the session store and is available to future requests. Retrieving information from the session is just as easy, using standard hash access techniques like this:

user_id = session[:user_id]

You may have noticed a directory called tmp in your Rails application directory tree. This is the directory in which the session data is stored. There are actually three choices for where Rails stores session data. The available options are as follows:

  • File system

  • Database

  • In memory

The file system is used by default and requires no additional configuration. This is sufficient for development, testing, and many small-scale Web applications. You run into problems with storing the session on the file system if you have a Web application that is load balanced and served off of multiple servers, as is commonly done for performance reasons. In this situation, not all requests are routed to the same physical server. Your application exists on multiple physical servers, and a load-balancing router will route Web requests across the different instances of your application.

If your session is stored on the file system and a user is routed to a different physical server during a browsing session, the application will not be able to find the session associated with that user. This makes storing the session in the database a popular alternative for Rails production environments. Rails also allows you to store session information in memory. This option performs very well because reading and writing from memory is a very fast operation, compared to reading and writing to disk.

Using the ActiveRecord session storage

As its name implies, the ActiveRecord session storage uses ActiveRecord to store the session data into a table in your database. By having the session stored in the database, it becomes accessible from multiple computers and thus works well in an environment where you have load-balanced servers.

Let's look at how you set up a Rails application to use ActiveRecord session storage. There are a few simple steps to follow, which are described here:

  1. Create a migration to set up session storage in your database. Just as you use migrations to set up the database tables that hold your application's data, you can also use a migration to create the session data table. In fact, you can create this migration automatically using this rake command:

    rake db:sessions:create RAILS_ENV=production
  2. Apply the session setup migration. Now you can run the rake migrate command to apply the new migration, like this:

    rake db:migrate RAILS_ENV=production
  3. Configure Rails to use ActiveRecord session storage. Next, you have to tell Rails that you are using ActiveRecord session storage. You do this by editing the config/environment.rb file. Simply remove the comment from the following line:

    Config.action_controller.session_store =:active_record_store
  4. Restart the application. This is the last thing you need to do. After the application is restarted, sessions will be stored in the database. How you restart the server depends on the server that you are using. To restart a Rails application that is using the WEBrick server, stop the existing server by pressing Ctrl+C in the console window in which you started the server and restart it with the following:

    ruby script/server

Using MemCached session storage

MemCached is used to provide the in-memory session storage option. MemCached is based on software that was originally developed by Danga Interactive for the LiveJournal blog-hosting Web site. When using MemCached, sessions are stored in your server's memory and are never written to disk. Because this option does not require any hard disk I/O, it is much faster than the other options. For more information about using Memcached see the Ruby on Rails wiki site, and the Memcached home page:

http://wiki.rubyonrails.org/rails/pages/MemCached
www.danga.com/memcached/

Caching

Caching is an important technique that you can use to increase the performance of any Web application. Caching speeds up Web applications by storing the result of calculations, renderings, and database calls for subsequent requests. The Action Controller component of Rails includes built-in support for caching in your Rails applications.

Rails support for caching is available at these three levels of granularity:

  • Page

  • Action

  • Fragment

Page caching

Page caching is a caching technique where the entire output of an action is stored as an HTML file that the Web server can serve without having to go through Rails to call the action again. Using this technique can improve performance by as much as 100 times over having to always dynamically generate the content. Unfortunately, this technique is only useful for stateless pages that do not differentiate between application users. Applications in which a user logs in and is given unique views of data are not a candidate for this technique. Applications that do not require a user logon to view data, such as wikis and blogs, may benefit from this technique.

You can turn on page caching for any methods in your controller classes by using the caches_page method call. You pass the actions that you want to cache as parameters to caches_page. You do not have to include all of your controller's actions. Here is an example:

class BlogController < ActionController::Base
    caches_page:show,:new

    def show
       ...
    end

    def new
       ...
    end
end

This causes the results of the show and new methods to be cached. The first time the actions are run, the HTML result is cached. This HTML cache file will be returned on subsequent calls to these methods without having to call the actions again.

You can expire cached pages by deleting the cached file. When a cached file is deleted, it is regenerated on the next call to the action to which it applies. To delete a cached page, you use the expire_page method. A common time to delete a cached page is when you perform an update to the page that is cached. In your update action method, you would also delete the cached page like this:

class BlogController < ActionController::Base
    def update
       ...
        expire_page:action => "show",:id => params[:id]
        redirect_to:action => "show",:id => params[:id]
    end
end

The action and id for which the page has been cached are passed to the expire_page method to delete the cached page. You can then perform a redirect to regenerate the newly updated page and thus create a new cached page.

Action caching

As with page caching, action caching saves the entire output of an action response. The difference is that with action caching, the action calls are still routed to the controller so that any filters can still be applied. This is useful when you have a filter setup to provide a restriction on who can view the cached action.

class BookController < ApplicationController
    before_filter:authenticate
    caches_action:show,:list
end

In this example, the methods show and list require that the user is authenticated before the methods are called. This is accomplished with the authenticate before filter. If, the show and list actions were cached using page caching, the before filter would never be called once the page was cached. Therefore, to preserve the authentication requirement, these pages must be cached using the action caching technique.

Fragment caching

Fragment caching is used to cache blocks within templates rather than caching the entire output of an action method. This technique is useful when certain parts of an action change frequently and would be difficult to cache, and other parts remain relatively static and thus can be cached. Fragment caching is done in view templates instead of the controller classes as the other forms of caching were.

A fragment cache is designated with a cache_do block. The lines inside the block enclosed in the cache_do statement will be cached. Here is an example of code that you might use in a view template:

<b>Welcome <%= @user.name %></b>
<% cache do %>
    Please choose a topic:
    <%= render:partial => "topic",:collection => @topics %>
<% end %>

In this example, the first line displays a user's name. Because the name is different for every user that logs in, this is not a good candidate for caching. Following the user's name, a list of topics is displayed. Because the list of topics remains relatively static, this is a good candidate for caching, and thus the lines that display the topic lists are wrapped with the cache_do statement.

Summary

In this chapter, you learned how Rails helps you to implement the controller layer in an MVC Web application. The ActionController component of Rails is what allows you to easily create controllers for your applications.

Some of the topics that are related to the controller layer of a Web application that you learned about in this chapter are: routing, creating and using controllers in a Rails application, using filters, sessions, and content caching. These are all topics that you will find yourself making use of over and over again as you develop real world Rails applications.

You have now learned about how Rails helps you create the model and controller layers of your Web application. In the next chapter, you will learn how Rails helps you with the final layer of your MVC Web application, the view layer. The view layer is closely related to the controller layer and it is a good idea to have the knowledge you gained in this chapter fresh in your mind as you read that chapter.

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

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