Chapter 5. Action Controller
Action Controller is the C for Controller in MVC. It is through the controller that a user interacts with a Rails application. The controller is responsible for interpreting the client's request to your application, manipulating and loading the model data containing your business logic, and then rendering an appropriate view of the model data as a response for the client.
From the perspective of a Rails application developer,
controllers are the entry point of an application. Requests URIs are
mapped to the controller actions that will serve them by the Rails routing system. The Rails
routing system uses a simple domain-specific language (DSL) in the
config/routes.rb file of your application to map the URIs to controller actions. We'll take a closer look at routing in Chapter 6, Routing.
The controller is also responsible for deciding, based on the parameters of the client's request, what format to return the data to the client in. For example, a web browser is most likely interested in an HTML response, whereas an API client would rather receive the response XML or JSON. Rails makes it trivial to support multiple different response formats via a single controller action. This makes the addition of alternative response formats, such as XML or CSV, very easy to add cleanly to your application.
Controllers also support many more powerful and easy to use features including:
A callback system called filters that allow code to be executed before, after, or around a controller action.
Access to and manipulation of HTTP cookies which can also be cryptographically signed to prevent tampering.
A facility called the flash for passing objects and messages between requests, commonly used for passing an error or status message from one request to the next.
Manipulation and management of client session data with a variety of different sessions store back-ends.
A powerful caching system for caching content with different levels of granularity including page, action, and fragment caching.
Built-in HTTP Basic & HTTP Digest Authentication helpers.
Getting Started
A controller is comprised of controller actions. A controller action is a public instance method defined in the controller class that can be routed to by the Rails framework. A controller action's job is to interact with the model layer of the application and pass the appropriate model data to the view for rendering. Once rendered, the response is sent back to the requesting client.
It is very important to remember that the
config/routes.rb file of a Rails application defines the
publicly accessible dynamic URI paths of a Rails application. Without
any routes, a Rails application's controller actions are inaccessible to requests. For
more information on configuration of your applications routes see Chapter 6, Routing.
Rails provides a generator for creating new controllers. The generator is passed the controller name in underscored or camel case format followed by zero or more actions:
$ rails generate controller name [action...] [option...]
The following options are available:
--helperWhether or not to generate a helper for the controller. The default is
true.-t,NAME--test-framework=NAMESpecify the testing framework to use for testing your controller. The default is
test_unit.-e,NAME--template-engine=NAMESpecify the template engine to use in the controller's views. The default is
erb.
The generator creates the following files:
controller class in
app/controllersdirectory.view templates for each
actioninapp/views/directory.nameview helper for the controller in the
app/helpersdirectory.functional test stub in
test/functionaldirectory.helper test stub in
test/unit/heleprsdirectory.
To get started, let's generate a controller class
named OrdersController with a single action index:
$ rails generate controller Orders index
create app/controllers/orders_controller.rb
route get "orders/index"
invoke erb
create app/views/orders
create app/views/orders/index.html.erb
invoke test_unit
create test/functional/orders_controller_test.rb
invoke helper
create app/helpers/orders_helper.rb
invoke test_unit
create test/unit/helpers/orders_helper_test.rbIt is a convention in Rails to name the
controller as the plural of the name of the resource it is managing. For an Order
resource, which could be an Active Record model or other type of resource, the
controller would be named OrdersController. Let's take a look at
the generated controller:
Example 5.1. A simple Rails controller
class OrdersController < ApplicationController def index end end
This controller contains a single action named
index, which is the action we generated with the Rails
generator. The body of the controller action is where the controller performs its
function of generating a response for the client based on the application's model data
and the view templates.
Controllers in a Rails application subclass
the application's ApplicationController, which can be found in
app/controllers/application_controller.rb. As the parent class
of all controllers in a Rails application, ApplicationController
is the ideal place to include common functionality that is needed by all controllers in
your application.
In order to return some content from this controller action we'll need to render a response. The simplest thing we can do is render some text:
class OrdersController < ApplicationController
def index
render :text => "Welcome to Rails!"
end
endHowever, before the controller action will be
accessible to a client, there needs to be a route that connects a URI
path to it in config/routes.rb. Fortunately, the generator saved us
a bit of work by automatically inserting the following route for us:
get "orders/index"
This route tells Rails to route the /orders/index
URI path to the OrdersController#index
action of the application. If we start up our application with:
$ rails server
We can load up the page in the browser by
navigating a web browser to http://localhost:3000/orders/index to see the
rendered text. (Yes, we know it’s invalid HTML, but bear with us.)
We can also use curl to request the page from the server:
$ curl http://localhost:3000/orders/index Welcome to Rails!
Record Identification
Before we jump into how to actually render
view templates and responses to requests we're going to take a quick look at a very
important concept that is frequently used throughout both Action Controller and Action
View. The concept is called record identification. Record
identification works by using the class name and id of the object to
automatically generate identifiers for an object. Here are some examples of using the
helpers provided by the ActionController::RecordIdentifier
module:
include ActionController::RecordIdentifier user = User.find(1) singular_class_name(user) # => "user" plural_class_name(user) # => "users" dom_id(user) # => "user_1" dom_class(user) # => "user"
By itself, record identification is useful, but isn't that exciting. However, when you build assumptions on top of record identification, then a lot of code can be greatly simplified.
You'll notice record identification is generally used in controllers and views to reduce method calls that once took many options down to method calls that just receive an instance of an object or a collection of objects. For example, rendering a partial view template for a collection of users:
render :partial => "users/user", :collection => @users
With record identification simplifies to:
render @users
Another good example is the redirection called after creating a new record:
redirect_to users_url(@user)
With record identification becomes:
redirect_to @user
As you can see, based on a few assumptions, record identification can really help simplify a lot of code used in common situations in controllers and views. You'll notice the use of record identification throughout the rest of the Action Controller and Action View chapters.
Responding to Requests
The response to an incoming request, unless the
controller action decides to redirect, is generated by calling the controller's
render method. Without exception, every controller action
in your application will either render a response or issue a redirect. There are various
ways of rendering a response.
Generally, you'll render a view template, but it can also useful to return content to the client that isn't generated by a view template. The most common case of not rendering a view template is generating the response to an API request in XML or JSON format. Active Record objects know how to serialize themselves both as XML and JSON, so it makes more sense and takes much less code, to rely on Active Record's serialization support than to create additional view templates.
When looking at idiomatic Rails controller actions you'll see a pattern emerge, which is the following two steps:
Load any required model objects from the data store.
Render a view template or issue a redirect.
The HEAD Response
The head method
returns a response without an empty body. This is useful in situations where an
HTTP status code and headers are sufficient information for the
client. One example is a controller that responds to an uptime monitoring service:
class PingController < ApplicationController
def index
head :ok
end
endYou can use any HTTP
status code or symbol from the section called “HTTP Status Codes” as the first
argument to the head method. You can also add arbitrary
HTTP headers to the response as options:
class PingController < ApplicationController
def index
head :ok, 'Content-Type' => Mime::TEXT
end
endAll passed in header values have
to_s called on them, which is why we can pass in the
Mime::TEXT constant as the value of the Content-Type header.
It is also smart enough to convert a
symbolized header name like :content_type into the correct
HTTP format Content-Type:
class PingController < ApplicationController
def index
head :ok, :content_type => Mime::TEXT
end
endUsing curl we can
verify that the response has been given the correct Content-Type
header:
curl -I http://localhost:3000/ping HTTP/1.1 200 OK Connection: close Date: Mon, 24 May 2010 22:23:58 GMT Content-Type: text/plain X-Runtime: 0.036393 Content-Length: 0 Cache-Control: no-cache
Rendering a Response
In order to actually render content to be
returned to the client you need to call render in your
controller action. The render method has two different
signatures:
render(options = {})Renders a response based on the provided
optionshash. Theoptionshash must contain a key specifying a renderer. The available renderers are::action,:template,:file,:inline,:partial,:text,:json,:js,:xml, and:update. We'll be taking a closer look at each of the renderers throughout the rest of the chapter. An example of rendering an action:class OrdersController < ApplicationController def index render :action => 'index' end endrender(template,options = {})This form of the
rendercall evolved out of the fact that the vast majority of calls torenderuse the:actionrenderer. The way of callingrenderacts as an:actionrender if you pass intemplateas just the name of a template without any path separators. It acts as a:templaterender if you pass intemplateas a template path prefixed with one or more path segments. An example of rendering theindexaction using the convenience form of therendermethod:class OrdersController < ApplicationController def index render 'index' end end
If render is not
called at all during a controller action, the controller implicitly renders an
action with the same name as the current controller action that is being executed.
Comparing our previous example that renders the index
action,
class OrdersController < ApplicationController
def index
render :action => 'index'
end
endthe equivalent implicit default
render looks as follows:
class OrdersController < ApplicationController def index end end
View Templates
The majority of the time you'll
generate a response by rendering a view template. Here we'll discuss where to
find a controller's view templates and how to pass variables from the controller
to the view. We'll take a much closer look at view templates in ???. All of an application’s view templates are found
underneath the app/views directory. The list of view paths
is mutable, so to view the current list of view paths you can inspect the
ActionController::Base.view_paths accessor. This
would give you an up-to-date list of view paths in the event that your code or a
plugin has modified the view paths for your application.
The controller's view templates directory is
found in a subdirectory named after the controller under app/views. The name of the subdirectory is determined by
underscoring the controller's class name after the Controller
portion of the name has been removed. Therefore, the views for the OrdersController are located in app/views/orders. If you're ever in doubt you can call the
controller's controller_path class method to get the
name of the subdirectory:
OrdersController.controller_path # => "orders"
If a controller is namespaced then each namespace is added to the path as a subdirectory:
Admin::OrdersController.controller_path # => "admin/orders"
All instance variables of the instance of the controller that is handling the request are passed to the view template during rendering. These instance variables are called the controller's assigns and are available in the view template as instance variables of the same name.
Common Render Options
The render
call takes a variety of options depending which type of rendering is being
performed. Several of the options are common to all types of rendering, so we'll
go over them right here:
:content_typeSets the HTTP
Content-Typeheader of the response. This can be set to any content type string, such asapplication/rss+xmlor to any registered MIME type in the Rails application. See ??? for more information on MIME types and a complete list of the default MIME type symbols available.:locationSets the HTTP
Locationheader of the response. This header indicates the URI location of the created resource.url_foris called on the value, so you can pass any values understood byurl_forincluding: an absolute URI, a hash ofurl_foroptions or an object compatible with the polymorphic routes.Note that setting the
Locationheader only makes sense if you're also setting the HTTP status code to201 Created.:statusSets HTTP status code for the response. Accepts either a numeric status code, such as
201, or a symbol representing the status code, such as:created. See the section called “HTTP Status Codes” for a complete listing. Defaults to200for200 OK.
Rendering Nothing
Rendering nothing is a way to return a response that has no response body. The response only contains headers. Rendering nothing is the simplest form of all of the different methods of rendering and looks as follows:
class PingController < ApplicationController
def index
render :nothing => true
end
endThis works fine, but we've already
looked at a more powerful and flexible method called head that also returns an empty response body. The advantage of
head is that it allows you to set arbitrary HTTP headers on the response with the passed in options. With the
:nothing option of render you're
limited to setting the Content-Type, Location, and Status headers and using the
headers method available in the controller to set other
arbitrary headers.
Rendering an Action
Rendering an action instructs the controller to render a view template with the name provided from the controller's view directory.
The generator we ran earlier
automatically generated an index.html.erb view template for
us. The template, app/views/orders/index.html.erb, looks
like this:
<h1>Orders#index</h1> <p>Find me in app/views/orders/index.html.erb</p>
We can render this view template as follows:
class OrdersController < ApplicationController
def index
render :action => 'index'
end
endAs we mentioned earlier, the template
could also be rendered using the convenience form of the render method:
class OrdersController < ApplicationController
def index
render 'index'
end
endFollowing is a portion of an idiomatic
OrdersController controller with show, new and create
actions:
class OrdersController < ApplicationController
# GET /blogs/1
def show
@order = Order.find(params[:id])
end
# GET /orders/new
def new
@order = Order.new
end
# POST /orders
def create
@order = Order.new(params[:order])
if @order.save
redirect_to(@order, :notice => 'Order was successfully created.')
else
render :action => "new"
end
end
endNotice how the create method renders the new.html.erb view
template when validation fails. Calling render only
renders a template and never executes the method body of another controller
action. This means that you must correctly set up all of the context the view
template requires in each controller action the view template will be rendered
from.
Options
:layoutThe name of the layout template to render from the
app/views/layoutsdirectory. For example,:layout => 'super_cool_thinger'Defaults to the current controller’s layout. You can disable rendering a layout by setting:layout => false
Rendering a Template
Rendering a template is exactly the same as
rendering an action, but instead of looking for the view template in the
controller’s view template directory, the view template is searched for using
the application’s view template directory, app/views as the
base. This is useful for sharing templates between different controllers,
possibly with a app/views/shared view directory. Like
rendering an action, you can use either the normal method of passing a hash of
options to render or the more concise shortcut syntax. To render a template
app/views/shared/index.html.erb with the regular hash
syntax:
class OrdersController < ApplicationController
def index
render :template => 'shared/index'
end
endOr to render the very same template with the more concise shortcut syntax:
class OrdersController < ApplicationController
def index
render 'shared/index'
end
endOptions
:layoutThe name of the layout template to render from the
app/views/layoutsdirectory. Defaults to the current controller's default layout. You can disable rendering a layout by passingfalse.
Rendering a File
Rendering a file is the same as rendering an
action or template, however, you can render any file on the file system. You
pass the :file option an absolution filename to render. Here
we render the /etc/hosts file from my system:
class HostsController < ApplicationController
def list
render :file => '/etc/hosts', :layout => false, :content_type => Mime::TEXT
end
endWarning
Never accept user input as the
filename to be rendered when rendering a file. The :file
option allows any accessible file on the file system to be read, such as
/etc/passwd or sensitive files from your
application's configuration.
Here we use curl to fetch the
contents of the /etc/hosts file:
$ curl http://localhost:3000/hosts/list ## # Host Database # # localhost is used to configure the loopback interface # when the system is booting. Do not change this entry. ## 127.0.0.1 localhost 255.255.255.255 broadcasthost ::1 localhost fe80::1%lo0 localhost
As is apparent from the example, you
need to be extremely careful when rendering with the :file
option. You must avoid using user input for the filename or be very careful in
validating and sanitizing the input.
Options
:layoutThe name of the layout template to render from the
app/views/layoutsdirectory. Defaults to the current controller's default layout. You can disable rendering a layout by passingfalse.
Inline Rendering
Rendering inline templates is very uncommon
as it means you're rendering a view fragment inline in the controller. This
violates the principles of MVC and should generally be avoided,
since it is trivial to extract the content into its own view template. If you do
have a need for it, though, you just use the :inline option
and provide the contents of the view template as a string:
class CustomersController < ApplicationController
def show
@customer = Customer.find(params[:id])
render :inline => <<-ERB
<%= div_for(@customer) do %>
<p><%= @customer.name %></p>
<% end %>
ERB
end
endHere we render a div containing the customers name using the Ruby heredoc syntax. As
you can clearly see, this would be better extracted to an ERB
template. ERB is the default template type for inline
templates. You can use the :type option to change the
template handler used to render the template.
By default, inline templates are not rendered within a layout, so you'll have
to pass in the layout name you want to use in the :layout
option if you want your content rendered in a layout.
Options
:layoutThe name of the layout template to render from the
app/views/layoutsdirectory. Defaults tofalse.:typeThe type of template handler to use to render the content. You can use any template handler registered in the application. Valid values in an application without additional template handlers installed include:
:erband:builder. Defaults to:erb.
Rendering Partials
If you’re displaying the same chunk
of view code in multiple view templates, you need to refactor and use a partial
instead. Partial templates, more commonly called partials, are reusable view
snippets used to extract a portion of a view into a partial template when you
need to use the same view fragment in two or more different templates. Partial
templates are exactly the same as other templates except that they must be named
with a leading _ character. If you had a partial ERB template for rendering a view snippet for an order you would name
it _order.html.erb.
Rendering partials is typically done from within another view template. The only reason you would ever want to render a partial template in a controller is return an HTML fragment as the response to an AJAX call. The JavaScript library, jQuery by default in Rails 3, would make the request for the partial with an AJAX request and then update the page with the page fragment returned in the response. We’ll take a much closer look at partials in ???, but for now, we'll just stick to the basics of rendering partials.
Here we render the _user.html.erb from the controller's app/views/users view template directory:
class UsersController < ApplicationController
def show
@user = User.find(params[:id])
render :partial => 'user'
end
endYou can also let the rendering
subsystem determine the name of the partial template for you by implicitly
passing the object to the :partial option. Here’s what that
looks like:
class UsersController < ApplicationController
def index
@user = User.find(params[:id])
render @user
end
endOptions
:collectionA collection of objects to render in partials. The partial template is rendered once for each object in the collection.
:layoutThe name of the partial layout template to render from the
app/views/layoutsdirectory. Defaults tofalse.:localsA hash of local variables to be assigned to the partial template. A local variable is created in the partial template with the name of each key in the
:localshash.
Rendering Text
Rendering text is probably the most
straightforward of the rendering options. The content passed in with the
:text option is returned to the client as the response.
By default, rendering text does not wrap the content in the controller's layout,
so you'll need to manually specify a layout if that is desired. Let's look at a
simple example for a PingController, which simply renders
the text PONG. Something like this is useful to respond to
monitoring services, such as Pingdom. Here is the controller:
class PingController < ApplicationController
def index
render :text => 'PONG'
end
endIt is worth noting that the content
type of the response is not automatically set to text/plain
when rendering text. The content type defaults to text/html,
but could be overridden either by the Accept header of the
request or the :content_type option of the render call.
Options
:layoutThe name of the layout template to render from the
app/views/layoutsdirectory. Defaults tofalse.
Rendering XML
Rendering an XML response
couldn't be easier with Rails, especially with Active Model and Active Record’s
object’s support for serializing themselves to XML. You can
pass a string of XML markup to the :xml
option or any object that defines a to_xml method. The
XML renderer will automatically call the to_xml method for you if the object you pass in respond_to?(:to_xml).
Assuming a simple Customer model with name and birthdate attributes that can serialize itself to XML:
Customer.create(:name => "Mr. T", :birthdate => "1952-05-21")
Calling to_xml on
the customer object returns the following XML string:
<customer> <name>Mr. T</name> <birthdate type="date">1952-05-21</birthdate> <id type="integer">1</id> </customer>
You can explicitly call to_xml on the object to get the XML string:
class CustomersController < ApplicationController
def show
@customer = Customer.find(params[:id])
render :xml => @customer.to_xml
end
endBut it isn't required because the
object implements a to_xml method:
class CustomersController < ApplicationController
def show
@customer = Customer.find(params[:id])
render :xml => @customer
end
endWhen allowing the XML renderer to implicitly call to_xml for
you, you can also pass any other options accepted by to_xml to the render call. For example,
to change the root element from customer to person we would do the following:
class CustomersController < ApplicationController
def show
@customer = Customer.find(params[:id])
render :xml => @customer, :root => 'person'
end
end
Additionally, when rendering XML, the controller automatically sets the correct Content-Type header for the you, which we can see by viewing the
headers with curl:
$ curl -I http://localhost:3000/customers/1.xml HTTP/1.1 200 OK Connection: close Date: Sun, 23 May 2010 15:59:22 GMT ETag: "d3d1de8feb536eb14aab247d613f44d6" Content-Type: application/xml; charset=utf-8 X-Runtime: 0.038544 Content-Length: 0 Cache-Control: max-age=0, private, must-revalidate
Another handy way to generate XML is to create a hash of data and pass that to the :xml option. Hashes have a to_xml built-in
thanks to Active Support, so the XML renderer does the right
thing:
class CustomersController < ApplicationController
def show
render :xml => { :customer => "Jim" }
end
endFor more information on XML serialization please see ???.
Rendering JSON
JSON, which stands
for JavaScript Object Notation, is an object serialization format based on the
JavaScript language that has recently gained a lot of momentum. The JSON format is a nice, human readable, lightweight data
interchange format. Let's take a look at the same customer object in JSON format by calling to_json on it:
{"name":"Mr. T","birthdate":"1952-05-21","id":1}Rendering JSON is
more-or-less the same as rendering XML, as Rails provides
excellent support for serializing objects to JSON. There is one difference in
the way the JSON encoding works, though. Basically, any
object can be encoded as JSON, so the JSON
renderer return the passed in object as the response if the object responds to
to_str. Otherwise the JSON
encoder will encode the object and return the result. To return your object in
JSON format simply pass it in to the render call as the :json option:
class CustomersController < ApplicationController
def show
@customer = Customer.find(params[:id])
render :json => @customer
end
endAs with the XML
rendering, you can pass any of the options supported by the JSON encoder as options to the render call.
For example, to only return the customer's name we could add the :only option:
class CustomersController < ApplicationController
def show
@customer = Customer.find(params[:id])
render :json => @customer, :only => :name
end
endAgain, from the HTTP
headers, you can see that Action Controller does the right thing and sets the
correct Content-Type headers for the response:
$ curl -I http://localhost:3000/customers/1.json HTTP/1.1 200 OK Connection: close Date: Sun, 23 May 2010 20:33:20 GMT ETag: "eb99282aedf4a9b4a22a6a99d7b21c64" Content-Type: application/json; charset=utf-8 X-Runtime: 0.041876 Content-Length: 0 Cache-Control: max-age=0, private, must-revalidate
Just like with XML serialization, it can be convenient to pass the JSON renderer a hash of data, which it will automatically convert to a JSON string for you:
class CustomersController < ApplicationController
def show
render :json => { :customer => "Jim" }
end
endFor more information on JSON serialization please see ???.
JSONP
The JSON renderer also supports adding a JSONP (JSON with Padding) callback to the returned JSON response. JSONP is a technique used to circumvent the same origin policy implemented by web browsers. The same origin policy ensures that client-side scripts can't make cross-domain requests. For more information see the
JSONP was created as a work around to the same origin policy because web browsers do permit the cross-domain inclusion of JavaScript files. JSONP works by including a JavaScript file with an associated query parameter that specifies the name of a callback function. The server then wraps the returned JSON data in a call to the specified function.
To support JSONP you simply have to pass along an additional :callback option to the render call:
class CustomersController < ApplicationController
def show
@customer = Customer.find(params[:id])
render :json => @customer, :callback => params[:callback]
end
endWe simply pass along provided
callback from params[:callback] to the render call. You are not limited to naming the callback
parameter :callback; you can choose any name you like.
Let's use curl again to take a look at the returned
JSON:
curl http://localhost:3000/customers/1.json?callback=showCustomer
This returns the following
JSON representation of the object wrapped in the
showCustomer function call, as we requested with the
callback query parameter.
showCustomer({"name":"Mr. T","birthdate":"1952-05-21","id":1})In a real-world implementation of
JSONP, the client JavaScript would most likely
dynamically inject a script element into the HTML page to make the JSONP request happen
dynamically. The script tag would look something like
this:
<script type="text/javascript" src="http://example.com/customers/1?callback=showCustomer"></script>
This is only a brief introduction into how to support JSONP callbacks in your application. For a more in-depth look at JSON and JSONP take a look at the Wikipedia JSON page.
Rendering JavaScript
Support for rendering JavaScript is similar
to that for rendering XML and JSON. If the
object passed in with the :js option responds to to_js then the result of the to_js
call is used as the response body. If the object doesn't respond to to_js then the object is assumed to be a string containing
JavaScript content. Where support for JavaScript differs than that for XML and JSON is that Rails does not
automatically provide any predefined to_js methods.
This means that you will have to define your own to_js
method on the object if you want to take advantage of that functionality.
The most likely scenario is that you
need to generate a bit of JavaScript on the server-side of your application to
service an AJAX request from a link, such as a link at the
bottom of the intro to an article to dynamically load the body of the article.
The HTML representation of the show
action is a view that shows an excerpt of the article with a link to load the
rest of the content:
<%= truncate @article.body %> <div id='read-more'> <%= link_to "Read More", article_path(@article), :remote => true %> </div>
We haven't covered linking yet, but
the link_to call with the :remote =>
true option instructs the unobtrusive JavaScript library to issue an
AJAX call to the show action of
the ArticlesController when clicked. When the AJAX call arrives at the controller we make the call to
render with the :js option:
class ArticlesController < ApplicationController
def show
@article = Article.find(params[:id])
render :js => "$('read-more').replace(#{@article.body.to_json})"
end
endThe render
call generates JavaScript code to replace the contents of the read-more
div with the body of the article, which the Prototype library
running in the browser will execute when it receives it. This will replace the
div with the link with the body of the article. This is
fully functional, but placing inline view code in the controller violates the
MVC architecture. It would be recommended to create a view
template for the JavaScript snippet and render that instead.
Using curl to request the JavaScript from the controller:
$ curl http://localhost:3000/articles/1
$('read-more').replace("Lorem ipsum dolor sit amet ...")Inspecting the headers shows that
Rails correctly set the Content-Type header of the response
to text/javascript:
$ curl -I http://localhost:3000/articles/1 HTTP/1.1 200 OK Connection: close Date: Mon, 24 May 2010 03:40:23 GMT ETag: "d58fcb69d89c7685861e4047f9658982" Content-Type: text/javascript; charset=utf-8 X-Runtime: 0.048712 Content-Length: 0 Cache-Control: max-age=0, private, must-revalidate
Redirecting
HTTP redirects are a way
to redirect the user's browser from one page to another. Browser redirection works
by returning an HTTP status code in the Redirection 3xx range along
with an URI in the Location response header.
Specifically one of the following codes:
301 Moved Permanently302 Found303 See Other307 Temporary Redirect
By default, the redirect_to method in Rails uses the 302 Found
HTTP status code to indicate that the response to the request is
found under a different URI and that the browser should issue a
GET request to retrieve it. This is actually the definition
of 303 See Other, but all browsers implement support for 302 Found as if it were 303 See Other.
It is a best practice in web applications
to issue a redirect after a successful POST request. Having the
browser request a new page with a GET request after a successful
POST request prevents duplicate form submission, including
annoying form resubmission warnings, stemming from use of the browser’s refresh and
back buttons. If the browser fetches a new page with a GET
request after the POST request then these problems are mitigated.
In Rails the pattern is to redirect on success and render on failure.
The most common example you'll see in
every Rails application is seen in RESTful controllers that
respond to a POST request with a #create method that
creates a resource and then redirect to #show for that resouce upon
success. Likewise, PUT requests that hit an #update method
redirect to the same #show method upon success. Here’s an
example:
class BlogsController < ApplicationController
# ...
def create
@blog = Blog.new(params[:blog])
if @blog.save
redirect_to(@blog, :notice => 'Blog was successfully created.')
else
render :action => "new"
end
end
# ...
endRedirecting Back
Redirecting back to the page that
issued the request can sometimes be handy when there’s a form that’s rendered in
multiple places. Rails makes it easy: redirect_to :back Note
however that if there is no referrer, ActionController::RedirectBackError will be raised.
Response Headers
Action Controller goes to great lengths
to do as much work as it can for you. This includes attempting to always set all of
the correct HTTP headers in each response. However, there are times
when you'll need to add arbitrary HTTP headers to a response. Rails
provides full access to the HTTP response headers through the
headers accessor, which is available in your controller
actions. The headers accessor returns a hash-like object of
HTTP headers that you can modify as see you see fit. To set or
modify a header, simply access the headers as you would a
hash where the key is the name of the HTTP header:
headers['Content-Type'] = 'text/plain'
Also, since the object is hash-like, feel free to use any other hash methods on it:
headers.keys # => ["Content-Type"]
Cookies
The HTTP protocol is stateless,
meaning that from the perspective of the web server each request is completely
independent of the request that preceded it. HTTP cookies are a
solution to the HTTP protocol being stateless. Cookies can be set by a
web application and delivered to the client's browser using the HTTP
Set-Cookie header. Any cookies that are set by the application are
then sent back to the application with each request by the client's web browser in the
HTTP
Cookie header. This allows the web application to act statefully.
Maintaining state is possible because the web application can store information in the
client's cookies and reference the data on subsequent requests.
One obvious example of using cookies to maintain state is session management. Sessions are implemented for you in Rails by storing the either the id of the session or the entire session in the cookie, depending on whether the application is using a client or server-side session store. We'll take a look a deeper look at sessions in the next section the section called “Sessions”.
Working with Cookies
Rails makes it quick and easy to access, modify,
or delete cookies through a simple hash-like cookie jar accessible via the
cookies accessor method. The cookies accessor method is available in both the controllers and views
of an application. Let's take a look at storing and retrieving data from a cookie.
In the simplest case, you just assign a value to a key in the cookie jar through the
cookies accessor:
cookies[:timezone] = "Eastern Time (US & Canada)"
The key is the name of the cookie to be stored in the client's browser and the value is the value the cookie will have. More properties in point form:
When a cookie is set to a string value, the cookie's path is set to
/, which means that the cookie is valid for the entire site of the application.When set directly to a string value, the cookie is not given an expiry.
A cookie that does not have an expiry will automatically be expired by the browser when browser's session is finished.
A browser's session is finished which is when the browser is shut down.
Reading the value of a cookie is as simple as storing data in a cookie. Just access the cookie jar with key name of the cookie you want the value for:
cookies[:timezeone] # => "Eastern Time (US & Canada)"
Note
The cookies hash uses
indifferent access, so symbols or strings as keys turn into the same thing:
cookies[:timezone] and cookies['timezone'] are equivalent.
If you want to change any of the properties of
the cookie being set then you need to assign the cookie as a hash instead of a
string. For example, setting a login style preference cookie when a user accesses
the /admin page of your application might look as follows:
cookies[:login_style] = { :value => 'openid', :path => '/admin', :expires => 10.years.from_now }The cookie's value is set to openid, its path is set to /admin, which is the path
of the currently requested page of the application, and the cookie is set to expire
in 10 years.
Following is the complete list of properties that can be set on a cookie:
:domainThe domain the cookie is valid for. Useful for sharing a cookie between subdomains. Defaults to
nil, which means that the client will use the requested domain as the domain for the cookie.:expiresThe expiration of the cookie as a
Timeobject. Defaults tonil, which means the cookie is valid until the end of the current browser session.:httponlyWhether or not client side scripts, such as JavaScript, have access to the cookie. Defaults to
false, which means that client side scripts do have access to the cookie.:pathThe path the cookie is valid for. Defaults to
/, which means that all paths are valid.:secureInstructs the client to only transmit the cookie over a secure HTTPS connection to the server. Defaults to
false.:valueThe value to store in the cookie.
Deleting cookies is very simple as well. You call
the delete method on the cookie jar object and pass in the
key name of the cookie you want to delete:
cookies.delete(:login_style)
If you provided path or domain information when
storing the cookie then you need to pass the same path or domain information as
options to the delete call. For example, for the following
cookie:
cookies[:login_style] = { :value => 'openid', :path => '/admin' }You would have to delete this cookie with the associated path:
cookies.delete(:login_style, :path => '/admin')
Behind the scenes Rails instructs the browser to
discard the cookie by setting the value of the cookie to an empty string and the
expiration of the cookie to the long expired time of UNIX time
0, which is January 1, 1970.
Permanent Cookies
A permanent cookie is basically just a cookie
that doesn't expire for a very, very long time. In Rails a permanent cookie is a
cookie that doesn't expire for 20 years from the current time. Setting a permanent
cookie using the permanent method of the cookie jar is just
a clean way to set a 20 year in the future expiry date and it looks as follows:
>cookies.permanent[:login_style] = "openid"
This sets login_style cookie
to have an expiry 20 years in the future. You access permanent cookies in the same
way you access regular cookies:
cookies[:login_style] # => "openid"
Signed Cookies
By default, cookies stored by an application are
stored in plain text and modifiable by the client of the application. By default, no
effort is made to obfuscate or verify that the contents of the cookie have not been
tampered with. This means that you definitely shouldn't store any sensitive
information or anything that you wouldn't want the user of the application to be
able to modify in a regular cookie. If you want to ensure that the contents of the
cookie haven't been tampered with then you can use a signed cookie. Signed cookies
are created in a similar way as permanent cookies, except that you use the
signed method on the cookie jar.
Signing and verifying cookies relies upon a
secret token that is assigned to Rails.application.config.secret_token. By default, Rails generates a 128
character token for you and assigns to the secret_token
setting in config/initializers/secret_token.rb when you
generate your application. Using the signed method on the
cookie store instructs Rails to cryptographically sign the contents of the cookie
with a Hash-based Message Authentication Code (HMAC). The Message
Authentication Code (MAC) allows Rails to simultaneously verify
both the data integrity and authenticity of the signed data. This prevents anyone
without the secret key stored in config/initializers/secret_token.rb from tampering with the contents of
the cookie. The contents of the cookie can still be viewed by the client, but not
modified.
Warning
Just because you've signed a cookie doesn't mean that the signed cookie data can't be reused verbatim by an attacker that gets their hands on the contents of the cookie. The attacker can simply replay the stolen cookie data in an attempt to exploit your application.
Signing a cookie is very simple and looks as follows:
cookies.signed[:offer] = "Free Shipping"
Accessing signed cookies is the same as accessing regular cookies:
cookies[:offer] # => "Free Shipping
You can even combine the permanent and signed methods to get a
permanent, signed cookie:
cookies.permanent.signed[:remember_me] = user.token
Sessions
Maintaining state between a user's requests is a requirement of most of today's web applications. Tracking the state of an application's user between HTTP transactions has many important uses including personalization of content, tracking desired items for purchase in a shopping cart, knowing which user is currently logged in, and many others.
Since HTTP is a stateless protocol by design, the server doesn't maintain any user information between requests. This means that it is up to the application framework or developer to provide session support.
Rails has complete support for sessions and makes maintaining user state easy through client or server based session stores. Rails supports several different session stores, allowing the developer to choose the type of session store that best meets the needs of the application.
Using the session
The session is used like a hash and is available through
the session accessor in controllers and views. To
store an object in the session assign a key in the session to the value to store:
session[:user_id] = @user.id
Above, just the user's id was stored in the session, but any object can be stored in the
session:
session[:user] = @user
Any object can be stored in the session because Rails
automatically serializes and deserializes session data to and from the session store
using Marshal.dump and Marshal.load.
However, in practice, you should only store simple objects like strings and numbers
in the session for a few reasons:
Storing only lightweight, simple data in the session will help you avoid issues that might arise during deserialization of the stored objects from the session. Different Ruby versions have incompatible serialization approaches and Procs/lambdas for example have never played well with serialization.
Keeping a minimal amount of data in the session will also save on communication overhead for each request and response when you're using cookie session store.
Instead of storing entire user objects in the session, store just the
user's id and load the user object on demand
during the request.
Session data is retrieved by accessing the key in the session where the value was stored:
session[:user_id] # => 256700
To clear a particular key of the session you set the value
to nil:
session[:user_id] = nil
To clear out the entire session you call:
reset_session
Note
Since there is an overhead required to maintaining a user's session, it can sometimes be desirable to disable sessions for the parts of an application where sessions aren't required or for clients, such as web crawlers, that don't use cookies. Fortunately in Rails 3, the session is lazy loaded. This means that the session won't be loaded unless it is accessed during a request.
Keep in mind that resetting the session will also wipe out any other data that Rails is storing in the session, such as the CSRF token or any flash messages that have been set.
Let's take a look at a more complete example of session
use. The following methods are used to provide a current_user helper method to the application's controllers and views
and also provides a method to facilitate loading the user from the session cookie:
class ApplicationController < ActionController::Base
helper_method :current_user
private
def current_user
@current_user
end
def current_user=(user)
session[:user_id] = user ? user.id : nil
@current_user = user
end
def login_from_session
if session[:user_id]
self.current_user = User.find_by_id(session[:user_id])
end
end
endWe're now going to take a look at the different session stores offered by Rails and how to configure them in your application.
Session Stores
A session store determines how and where session data is stored. Rails provides three different session stores that you can use in your application: the cookie store, the memcache store, and the Active Record store. By default, Rails uses the cookie session store. Let's take a closer look at each of the different session stores and why you might want to choose one over the other.
Cookie Store
The cookie session store keeps session data in a cookie in the client's browser. The cookie store is great because it requires no setup or configuration and is much faster than the other session stores. The session data is sent in a cookie with each request by the client and the server writes the session data in the cookie headers of each response, so it is a good idea to keep the session small to reduce the overhead of each request/response cycle.
Since the data is stored in a cookie, the maximum size of the encoded session data is limited to the maximum usable size of a cookie by all browsers, which is 4 KB. However, since the data is encoded and has a cryptographic signature attached to it, the most data you'll likely fit in the cookie store is around 2.5 KB. If you need to store more than around 2.5 KB of data you'd want to use one of the other session stores. If you do end up putting too much data in the session you'll end up triggering an exception when Rails processes the session data for the response.
Another thing to keep in mind is that the session data is readable by the client. The data is cryptographically signed by Rails using a Hash-based Message Authentication Code (HMAC) to prevent tampering, but the data is not encrypted. This means that you would never want to store sensitive private information in the session when using the cookie session store. If you must store private information in the session then you'd want to use one of the other session stores that maintains session data on the server.
Active Record Store
The Active Record session store keeps all session data in the database. There are two reasons you might want to use this session store. The first is if you need to store more data in the session than will fit in the cookie session store. The second is if you need to store confidential information in the session.
The maximum size of the data stored in the Active
Record session store depends on the maximum size of the database column you
choose to store the session data in. By default, Rails uses a :text column for storing the session data. When
using MySQL, this stores around 64 KB of data.
Memcached Store
The Memcached session store is a server-side session store similar to the Active Record session store, but using Memcached for the back-end storage of the sessions. One major difference from the Active Record session store is that session data is not persistent in the Memcached session store. This is an inherent limitation of Memcached itself not being a persistent data store. The maximum size of session that can be stored in a Memcached server is 1 MB.
The reason you might want to use the Memcached session store is to leverage your existing Memcached infrastructure and to keep the load of sessions from impacting your database. You will still only want to use this session store if cookie session store is not adequate for your needs. Additionally, if anything happens to the Memcached process or the server that is running Memcached then the sessions that server contains will be lost. Unless your users won't notice losing their sessions then you'll probably not want to use the Memcached session store.
Configuration
Rails automatically configures your application with the
cookie session store when you use the rails
command to generate your Rails project. Customizing the configuration of the session
store is very straightforward if you do need to stray from the default settings. The
configuration of the session store is done in config/initializers/session_store.rb. There are two accessors on
ActionController::Base for configuring a session store:
ActionController::Base.session_storeThe session store to use. One of
:cookie_store,:mem_cache_store, or:active_record_store. Defaults to:cookie_store.ActionController::Base.sessionThe hash of session options for customization of the behavior of the session store. Options common to each session store are:
:keyThe name of the cookie to store the session data in. Defaults to
_session_id.:domainThe domain the session cookie is valid for. Useful for sharing a session cookie between subdomains. Defaults to
nil, which means that the client will use the requested domain as the domain for the session cookie.:pathThe path the session cookie is valid for. Defaults to
/. Setting:pathtonilinstructs the client to use the requested path as the path for the session cookie.:expire_afterHow long from the time of the request to expire the session cookie in. The number of seconds provided, such as
1.hour, will be added to the current time at the time of the request. Defaults tonil, which means that the browser will discard the session cookie when it closes.:secureInstructs the client to only transmit the session cookie when it has an HTTPS connection to the server. Defaults to
false.:httponlyPrevents client side scripts from accessing the session cookie. This helps prevent cross site scripting (XSS) session hijacking attacks. Defaults to
true.:cookie_onlyHelps to prevent session hijacking by only setting the
idof the session from the value of the sessionidstored in the session cookie when using server side session stores. Defaults totrue.
Following is an example of a config/initializers/session_store.rb file that configures a Rails
application to use the Active Record session store with a couple of custom settings:
ActionController::Base.session_store = :active_record_store
ActionController::Base.session = {
:key => '_blog_session',
:domain => '.example.com',
:expire_after => 1.week
}
Next we'll look at configuring each of the different session stores individually.
Cookie Store
The cookie store is the default session store used by
Rails. The default configuration of the cookie store generated by the Rails
application generator in the config/initializers/session_store.rb file looks like the
following:
ActionController::Base.session = {
:key => '_blog_session',
:secret => 'fdd1077479a7a13a8f3346c952047494b37a2fb6203b9a4a0ec03'
}Rails names the session :key based on the name of your application. In the example the name
of the application is blog. A
cryptographically secure :secret is also
generated. The :secret option is required
when using the cookie store. Also, the :secret was shortened in order to fit it on a single line, but is
normally 128 characters. To generate a new cryptographically secure secret key
you can use the rake secret task provided by
Rails. Changing the :secret will invalidate
all of your existing sessions, so use with caution.
There are two additional session options specific to the cookie session store:
:secretThe secret token used to cryptographically sign the session data with to prevent tampering. The secret needs to be random and greater than 30 characters in order to prevent dictionary attacks. The best way to generate a new cryptographically secure secret is to use the rake secret task provided by Rails.
Caution
Changing the session secret will invalidate all of your existing sessions.
The secret can also be a
Procobject, for example::secret => Proc.new { Account.current.secret_key }.:digestThe cryptographic digest algorithm used to verify the integrity of the session data. Available options include:
DSS,DSS1,MD2,MD4,MD5,MDC2,RIPEMD160,SHA, andSHA1. If the version of OpenSSL your installation of Ruby was compiled against is sufficient you may also have access to:SHA224,SHA256,SHA384, andSHA512. The default isSHA1.
Active Record Store
To configure your application to use the Active Record
session store place the following line in config/initializers/session_store.rb:
ActionController::Base.session_store = :active_record_store
Before you can use the Active Record session store you'll need to generate and run a database migration. Run the following rake task to generate the migration:
$ rake db:sessions:create
The migration creates a sessions table with session_id, data, created_at, and updated_at columns. Run the migration to create the sessions table in your database:
$ rake db:migrate
There is also a rake task for clearing all sessions in the table:
$ rake db:sessions:clear
When using the Active Record session store each session will be stored in your database. If your application gets a lot of traffic there will end up being a substantial number of session stored in the database, which can negatively affect your application's performance. The simplest way to clear out old sessions would be to use rails runner from a cron task. The following command run from the root of your Rails project will delete all sessions in the production database that haven't been updated in the past 30 days:
$ rails runner -e production \ "ActiveRecord::SessionStore::Session.delete_all( [ 'updated_at < ?', 30.days.ago ] )"
Memcached Store
To configure your applications to use the Memcached
session store place the following line in config/initializers/session_store.rb:
ActionController::Base.session_store = :mem_cache_store
You'll need to have Memcached servers accessible to your Rails application in order to use the Memcached store. See the section called “Memcached” for instructions on setting up Memcached on your machine.
There are several additional session options specific to the Memcached session store:
:cacheThe
MemCacheinstance to use instead of creating a new connection to the servers defined by:memcache_server. This allows you to reuse an existing connection to Memcached from elsewhere in your application.:failoverWhether or not the client should fail over to another Memcached server if the first server is unavailable. Defaults to
true.:loggerThe logger to use for logging debug and info output. Defaults to
nil. Set toRails.loggerto use the same logger as is configured for the rest of your Rails application.:memcache_serverA server or list of Memcached servers to use for storing sessions. Each server is specified either by its hostname or its hostname and port in the format of
hostname:port. Defaults tolocalhost:11211.:namespaceThe Memcached namespace to store the sessions under. Defaults to
rack.session.:timeoutThe time-out to use for socket reads. Defaults to
0.5seconds. You can set the time-out tonilto disable time-outs, however this is not recommended.
Flash
The flash in Rails is a hash-like object for passing objects from one request to the next. The flash is most commonly used to pass notice of success or failure of an operation to the user during the next request. Since an instance variable wouldn't survive from one request to the next, the flash utilizes the session to temporarily store the objects. Objects placed in the flash are available to the next requested action and are then cleared by Rails.
The most common use of the flash is displaying status
messages in an application, either in the same controller action or after a redirect.
Rails assumes that you'll be using the :notice key of the flash for
notices and success type messages and the :alert key for warnings and
errors. If you stick with this convention you get to use a few additional helpers that
are built into Rails, this is just the default. You are free to use whatever keys are
suitable to your application in the flash.
Storing Objects in the Flash
Rails assumes that you'll be using the :notice key in the flash for neutral or positive status messages and
assumes you'll be using the :alert key for warnings, alerts, or
other errors. As mentioned earlier, you are not restricted in any way as to which
flash keys you use in your application. Let's take a look at a few simple examples
of setting the flash.
Note
Unlike some other hash-like objects in Rails
that allow for indifferent symbol or string access to keys, the flash is not one
of them. In the flash :notice and "notice"
are two distinct keys. In practice most Rails applications use symbols for keys
when accessing the flash.
The following example shows idiomatic use of using the flash to display a status message to the user after successfully creating an order:
class OrdersController < ApplicationController
def create
@order = Order.new(params[:order])
if @order.save
redirect_to @order, :notice => "Successfully created the order"
else
render :action => "new"
end
end
endIn the example the flash was set using the
:notice option in the redirect_to
call. This is equivalent to setting the :notice key in the flash
directly:
flash[:notice] = "Successfully created the order."
You can also set and access the flash using the
notice and alert convenience
accessors provided by Rails:
flash.notice = "Successfully created the order." flash.notice # => "Successfully created the order." flash.alert = "An error occurred processing your request" flash.alert # => "An error occurred processing your request"
The redirect_to method
was given the ability to modify the flash itself because the majority of the calls
to the flash in a Rails application occur on the line directly preceding the
redirection.
The reason that only the flash :notice is being set in the example is because the else condition of the action doesn't issue a redirect. This means that
the view will have full access to the @order instance variable
and will likely be displaying the errors generated by the failed Order#save call.
Displaying notices with flash[:notice] is by far the most common use of the flash in a Rails
application. Displaying errors an warnings is also necessary in an application, but
it is less common when using Active Record because Active Record objects support
validations and error messages that can be directly shown in the view template. This
makes setting a flash[:alert] message redundant in most cases.
Setting a flash[:alert] message is more common for handling
unexpected errors that could occur in an action when dealing with code that could
raise exceptions or fail unexpectedly. For example:
class DiscountsController < ApplicationController
rescue_from DiscountService::Error, :with => :unavailable
def index
end
def show
@discount = DiscountService.lookup(params[:id])
end
protected
def unavailable
redirect_to discounts_url, :alert => "The discount service is temporarily unavailable"
end
endNote
The flash is stored in the session. This means that if you delete or reset the user's session then you'll also be wiping out any stored flash messages.
In this example the DiscountEngine.lookup in the show method
connects to an external discount web service. In the event that the service is not
available at the time of the request and a DiscountEngine::Error is raised then the rescue_from declaration handles the exception and invokes the
unavailable method. unavailable redirects the user back to the index action and sets flash[:alert] to the error
message, which can then be displayed to the user in the following request.
As mentioned earlier, if you want to use keys
other than :notice and :alert in your
application then feel free to do so. You can set any key you want in the flash:
flash["warning"] = "Your account is running low on resources" flash[:error] = "Could not connect to the search server"
You can also use any flash key with redirect_to by passing in a :flash hash. This
is best illustrated with an example:
redirect_to account_url, :flash => { :warning => "Your account has been suspended" }Now that we've taken a look at storing objects in the flash we can move on to how to display the flash in your view templates.
Accessing the Flash from View Templates
The flash is accessible within your views through
the flash method, which returns an instance of the flash.
You can display the contents of the notice placed in the flash in ??? like follows:
<% if flash[:notice] %> <p class="notice"><%= flash[:notice] %></p> <% end %>
Rails provides the notice and alert methods as convenience
accessors to flash[:notice] and flash[:alert]
respectively. The following is equivalent to the previous example, but uses the
convenience accessor:
<% if notice %> <p class="notice"><%= notice %></p> <% end %>
Rails assumes that you'll be using flash[:notice] for success or other messages and flash[:alert] for failure or alert messages. You're free to use
whatever scheme you choose, but if you follow the Rails idioms then you get the
convenience of the notice and alert convenience options and methods in redirect_to and your views respectively.
Limiting an Object to the Current Request
Sometimes you want to use the flash to pass a
message or object within the current request, but not have the message available to
the next request. This is possible using the now method of
the flash. Using flash.now is exactly the same as using
flash, but with the call to now tacked on:
flash.now[:failure] = "Oh no, something horrible happened!"
Objects placed in the flash using flash.now are accessed in the same way as other objects in the
flash, but are cleared out of the flash at the end of the current request:
flash[:failure] #=> "Oh no, something horrible happened!"
Keeping the Flash for an Extra Request
Once in a while you may want to keep the contents
of the flash for an extra request. This is possible using the keep method. You can keep the entire flash or just a single key in the
flash. To keep the entire flash, just call keep on the
flash with no arguments:
flash.keep
Or you can pass in the key of the flash that you want to keep for the next request:
flash.keep(:notice)
Discarding the Flash at the End of the Current Request
You can also discard the entire flash or certain
keys from the flash. This means that the entire flash or specific keys from the
flash will be removed from the flash at the end of the current request. To discard
the entire flash you call discard with no arguments:
flash.discard
You can also pass the key you want to discard to only get rid of a particular object in the flash:
flash.discard(:notice)
Controller Callbacks
Action Controller supports the execution of callbacks, known as filters, before, after, or both before and after a controller action. The filters are called before filters, after filters, and around filters respectively. Filters are excellent for executing code or methods before some or all of a controllers actions. This prevents you from having to repeat yourself by placing redundant code at the start or end of many controller actions.
Tip
If you see common code or methods called in multiple controller actions in a controller you should consider using filters to do the work for you. This will reduce the duplication and improve the quality of your controller actions.
Filters are commonly used for tasks such as authentication, setting user specific settings, such as the user's timezone, and other initialization or finalization tasks. In this section we'll take a look at the types of filters available, the different ways of defining filters, as well as some more advanced use of filters.
Filters
Before filters are by far the most common type of controller callback in Rails applications. The are executed before a controller action.
Let's take a look at a simple “before filter” that ensures that there is a logged in user to the application:
class ApplicationController < ActionController::Base
before_filter :login_required
private
def login_required
redirect_to new_session_url unless logged_in?
end
endThis code instructs the controller to call
login_required before every controller action in the
application. Since we put the code in ApplicationController,
which is the parent class of all controllers in a Rails application and filters are
inherited to their subclasses, we've effectively required a logged in user for all
controller action in the application.
Unfortunately, if every single controller action
in the application is protected by the filter then there will be no way for the user
to actually log in to the application. Fortunately, you can tell a controller not to
run a before filter using the skip_before_filter method.
Let's take a look at the SessionsController, which we'll use
for logging the user into and out of the application:
class SessionsController < ApplicationController
skip_before_filter :login_required
def new
# display the login form
end
def create
# authenticate the user
end
def destroy
# log the user out of the application
end
endSessionsController is a
subclass of ApplicationController, which means that, by
default, the before filter we previously defined to execute login_required will still execute before each controller action.
This would prevent the user from ever logging into the application and, the way
we've written it, would actually cause a redirection loop. The solution is the
skip_before_filter call on line 2 of the SessionsController. This instructs the controller to not execute
the login_required before filter at all in this controller.
This allows the user to log in and log out of the application without triggering the
login_required filter.
After and Around Filters
More filters are available to you beyond just before_filter; in
particular: after_filter and around_filter.
After filters are executed after a controller action. After filters are less commonly used in a Rails application, but could be used for tasks such as compressing the response body, cleaning up resources, or logging of the generated response body. Here’s an example:
class ApplicationController < ActionController::Base
after_filter :log_activity
private
def log_activity
# ...
end
endAround filters are executed both before and after a controller action. You might need this when loading something transactionally and need to ensure that some activity happens after the action. Here’s an example:
class ApplicationController < ActionController::Base
around_filter :load_something_special
private
def load_something_special
# ...
begin
# load something special
yield
ensure
# reset special things
end
end
endDefining Filters
Filters can be defined in three different ways: by passing a symbol as a method reference, by passing an external object, or by passing the filter a block. The most commonly used method is passing a symbol as a method reference:
class AccountsController < ApplicationController
before_filter :ensure_account_owner
private
def ensure_account_owner
redirect_to login_url unless current_user.account_owner?
end
endAs you can see from the example, the symbol
passed to before_filter is the name of the method to
execute. The method name can be any instance method defined for the controller.
Making the method private indicates that the method is not a
public controller action and prevents Rails from routing requests to it.
The second method of filtering involves passing
an instance of an object to the filter definition that responds to a method named
filter. Most commonly, you'd pass in a class constant:
class AccountsController < ApplicationController before_filter AccessControlFilter end
Then you'd have an AccessControlFilter class that has a static filter method:
class AccessControlFilter
def self.filter(controller)
redirect_to login_url unless current_user.account_owner?
end
endYou could also pass in an instance of an object
with a filter instance method:
class AccountsController < ApplicationController before_filter AccessControlFilter.new end
class AccessControlFilter
def filter(controller)
# verify the user is the account owner
end
endLastly, you can pass the filter a block to the filter call:
class AccountsController < ApplicationController
before_filter {|controller| # stuff stuff stuff }
endAs you can see from the example, the block receives the instance of the controller as its first block argument. The block format is useful for simpler cases where the code required is compact and doesn't necessary need to be factored out into its own method.
Limiting Filters to Particular Actions
It can be very useful to limit when a filter runs
to a subset of the actions in a controller. This is easily done using the :only and :except options in the call to
before_filter, after_filter,
or around_filter.
:onlyA symbol or array of symbols representing the only controller action or actions that should execute the filter.
:exceptA symbol or array of symbols representing the controller action or actions that should not execute the filter.
Sending Files
Sending files through Rails because they can’t be hosted on a public, static URL is a common requirement for web applications. Here’s how to do it:
send_file '/path/to/your_awesome_special_file.zip'
In the context of the controller, it would look like this:
class ProductsController < ApplicationController
before_filter :authenticate
def download_the_thing
send_file '/path/to/your_awesome_special_file.zip'
end
...
endPretty straightforward stuff. Note that if your web server supports
the X-Sendfile header, Rails will use it by default, allowing the
file-sending Rails process to serve other requests, and not be stuck just sending the
file.
The send_file method also lets you manually specify an
HTTP content type (Rails will guess based on the file-extension
otherwise), whether to display the file inline or have it be downloaded, the status
code, and whether you want the browser to guess the filename from the URL. See the Rails API docs for more detail.
Warning
Sanitize the filepath if you’re taking it from user-input/a params variable, or it becomes really easy for a malcious user to access
anything on the filesystem the Rails app can access.






Add a comment



Add a comment