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.
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.
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:
The Web server passes the request to the Rails routing subsystem.
The routing subsystem parses the request, identifying the requested controller and action.
A new instance of the requested controller is created.
The process
method of the controller is called and is passed request and response details.
The controller calls the specified action method.
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
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
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.
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.
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.
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 |
---|---|---|
| String | Adds an anchor name to the generated URL. |
| String | Sets the host and port name used in the generated URL. |
| Boolean | Specifices that only the path should be generated. The protocol, host name, and port are left out of the generated URL. |
| String | Sets the protocol used in the Generated URL, that is, 'https.' |
| String | Used for inline HTTP authentication. (used only if:password is also present) |
| String | Used for inline HTTP authentication. (used only if:user is also present) |
| Boolean | Determines whether the returned URL will be HTML escaped or not. (true by default) |
| Boolean | Appends a slash to the generated URL. |
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.
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.
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.
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.
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.
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.
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.
Where views are covered in Chapter 5, you can see many more ways to use the render
method to create other responses.
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.
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.
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.
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 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 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 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
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.
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.
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.
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:
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
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
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
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
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 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 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.
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 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.
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.
3.135.249.178