
- •Contents
- •Preface to the Second Edition
- •Introduction
- •Rails Is Agile
- •Finding Your Way Around
- •Acknowledgments
- •Getting Started
- •The Architecture of Rails Applications
- •Models, Views, and Controllers
- •Active Record: Rails Model Support
- •Action Pack: The View and Controller
- •Installing Rails
- •Your Shopping List
- •Installing on Windows
- •Installing on Mac OS X
- •Installing on Linux
- •Development Environments
- •Rails and Databases
- •Rails and ISPs
- •Creating a New Application
- •Hello, Rails!
- •Linking Pages Together
- •What We Just Did
- •Building an Application
- •The Depot Application
- •Incremental Development
- •What Depot Does
- •Task A: Product Maintenance
- •Iteration A1: Get Something Running
- •Iteration A2: Add a Missing Column
- •Iteration A3: Validate!
- •Iteration A4: Prettier Listings
- •Task B: Catalog Display
- •Iteration B1: Create the Catalog Listing
- •Iteration B4: Linking to the Cart
- •Task C: Cart Creation
- •Sessions
- •Iteration C1: Creating a Cart
- •Iteration C2: A Smarter Cart
- •Iteration C3: Handling Errors
- •Iteration C4: Finishing the Cart
- •Task D: Add a Dash of AJAX
- •Iteration D1: Moving the Cart
- •Iteration D3: Highlighting Changes
- •Iteration D4: Hide an Empty Cart
- •Iteration D5: Degrading If Javascript Is Disabled
- •What We Just Did
- •Task E: Check Out!
- •Iteration E1: Capturing an Order
- •Task F: Administration
- •Iteration F1: Adding Users
- •Iteration F2: Logging In
- •Iteration F3: Limiting Access
- •Iteration F4: A Sidebar, More Administration
- •Task G: One Last Wafer-Thin Change
- •Generating the XML Feed
- •Finishing Up
- •Task T: Testing
- •Tests Baked Right In
- •Unit Testing of Models
- •Functional Testing of Controllers
- •Integration Testing of Applications
- •Performance Testing
- •Using Mock Objects
- •The Rails Framework
- •Rails in Depth
- •Directory Structure
- •Naming Conventions
- •Logging in Rails
- •Debugging Hints
- •Active Support
- •Generally Available Extensions
- •Enumerations and Arrays
- •String Extensions
- •Extensions to Numbers
- •Time and Date Extensions
- •An Extension to Ruby Symbols
- •with_options
- •Unicode Support
- •Migrations
- •Creating and Running Migrations
- •Anatomy of a Migration
- •Managing Tables
- •Data Migrations
- •Advanced Migrations
- •When Migrations Go Bad
- •Schema Manipulation Outside Migrations
- •Managing Migrations
- •Tables and Classes
- •Columns and Attributes
- •Primary Keys and IDs
- •Connecting to the Database
- •Aggregation and Structured Data
- •Miscellany
- •Creating Foreign Keys
- •Specifying Relationships in Models
- •belongs_to and has_xxx Declarations
- •Joining to Multiple Tables
- •Acts As
- •When Things Get Saved
- •Preloading Child Rows
- •Counters
- •Validation
- •Callbacks
- •Advanced Attributes
- •Transactions
- •Action Controller: Routing and URLs
- •The Basics
- •Routing Requests
- •Action Controller and Rails
- •Action Methods
- •Cookies and Sessions
- •Caching, Part One
- •The Problem with GET Requests
- •Action View
- •Templates
- •Using Helpers
- •How Forms Work
- •Forms That Wrap Model Objects
- •Custom Form Builders
- •Working with Nonmodel Fields
- •Uploading Files to Rails Applications
- •Layouts and Components
- •Caching, Part Two
- •Adding New Templating Systems
- •Prototype
- •Script.aculo.us
- •RJS Templates
- •Conclusion
- •Action Mailer
- •Web Services on Rails
- •Dispatching Modes
- •Using Alternate Dispatching
- •Method Invocation Interception
- •Testing Web Services
- •Protocol Clients
- •Secure and Deploy Your Application
- •Securing Your Rails Application
- •SQL Injection
- •Creating Records Directly from Form Parameters
- •Avoid Session Fixation Attacks
- •File Uploads
- •Use SSL to Transmit Sensitive Information
- •Knowing That It Works
- •Deployment and Production
- •Starting Early
- •How a Production Server Works
- •Repeatable Deployments with Capistrano
- •Setting Up a Deployment Environment
- •Checking Up on a Deployed Application
- •Production Application Chores
- •Moving On to Launch and Beyond
- •Appendices
- •Introduction to Ruby
- •Classes
- •Source Code
- •Resources
- •Index
- •Symbols

Chapter 21
Action Controller and Rails
In the previous chapter we worked out how Action Controller routes an incoming request to the appropriate code in your application. Now let’s see what happens inside that code.
21.1Action Methods
When a controller object processes a request, it looks for a public instance method with the same name as the incoming action. If it finds one, that method is invoked. If not, but the controller implements method_missing, that method is called, passing in the action name as the first parameter and an empty argument list as the second. If no method can be called, the controller looks for a template named after the current controller and action. If found, this template is rendered directly. If none of these things happen, an “Unknown Action” error is generated.
By default, any public method in a controller may be invoked as an action method. You can prevent particular methods from being accessible as actions by making them protected or private. If for some reason you must make a method in a controller public but don’t want it to be accessible as an action, hide it using hide_action.
class OrderController < ApplicationController
def create_order
order = Order.new(params[:order]) if check_credit(order)
order.save else
# ...
end end
hide_action :check_credit

ACTION METHODS 425
def check_credit(order)
# ...
end end
If you find yourself using hide_action because you want to share the nonaction methods in one controller with another, consider moving these methods into separate libraries—your controllers may contain too much application logic.
Controller Environment
The controller sets up the environment for actions (and, by extension, for the views that they invoke). In the old days, this environment was established in instance variables (@params, @request, and so on). This has now been officially deprecated—you should use the accessor methods listed here.
action_name
The name of the action currently being processed.
cookies
The cookies associated with the request. Setting values into this object stores cookies on the browser when the response is sent. We discuss cookies on page 435.
headers
A hash of HTTP headers that will be used in the response. By default,
Cache-Control is set to no-cache. You might want to set Content-Type headers for special-purpose applications. Note that you shouldn’t set cookie values in the header directly—use the cookie API to do this.
params
A hash-like object containing request parameters (along with pseudoparameters generated during routing). It’s hash-like because you can index entries using either a symbol or a string—params[:id] and params[’id’] return the same value. Idiomatic Rails applications use the symbol form.
request
The incoming request object. It includes the attributes
•domain, which returns the last two components of the domain name of the request.
•remote_ip, which returns the remote IP address as a string. The string may have more than one address in it if the client is behind a proxy.
•env, the environment of the request. You can use this to access values set by the browser, such as
request.env['HTTP_ACCEPT_LANGUAGE' ]
Report erratum

ACTION METHODS 426
•method returns the request method, one of :delete, :get, :head, :post, or :put.
•delete?, get?, head?, post?, and put? return true or false based on the request method.
•xml_http_request? and xhr? return true if this request was issued by one of the AJAX helpers. Note that this parameter is independent of the method parameter.
class BlogController < ApplicationController def add_user
if request.get? @user = User.new
else
@user = User.new(params[:user]) @user.created_from_ip = request.env["REMOTE_HOST"] if @user.save
redirect_to_index("User #{@user.name} created") end
end end
end
See the documentation of ActionController::AbstractRequest for full details.
response
The response object, filled in during the handling of the request. Normally, this object is managed for you by Rails. As we’ll see when we look at filters on page 449, we sometimes access the internals for specialized processing.
session
A hash-like object representing the current session data. We describe this on page 437.
In addition, a logger is available throughout Action Pack. We describe this on page 243.
Responding to the User
Part of the controller’s job is to respond to the user. There are basically four ways of doing this.
•The most common way is to render a template. In terms of the MVC paradigm, the template is the view, taking information provided by the controller and using it to generate a response to the browser.
•The controller can return a string directly to the browser without invoking a view. This is fairly rare but can be used to send error notifications.
Report erratum

ACTION METHODS 427
•The controller can return nothing to the browser.1 This is sometimes used when responding to an AJAX request.
•The controller can send other data to the client (something other than HTML). This is typically a download of some kind (perhaps a PDF document or a file’s contents).
We’ll look at these in more detail shortly.
A controller always responds to the user exactly one time per request. This means that you should have just one call to a render, redirect_to, or send_xxx method in the processing of any request. (A DoubleRenderError exception is thrown on the second render.) The undocumented method erase_render_results discards the effect of a previous render in the current request, permitting a second render to take place. Use at your own risk.
Because the controller must respond exactly once, it checks to see whether a response has been generated just before it finishes handling a request. If not, the controller looks for a template named after the controller and action and automatically renders it. This is the most common way that rendering takes place. You may have noticed that in most of the actions in our shopping cart tutorial we never explicitly rendered anything. Instead, our action methods set up the context for the view and return. The controller notices that no rendering has taken place and automatically invokes the appropriate template.
You can have multiple templates with the same name but with different extensions (.rhtml, .rxml, and .rjs). If you don’t specify an extension in a render request (or if Rails issues a render request on your behalf), it searches for the templates in the order given here (so if you have an .rhtml template and an .rjs template, a render call will find the .rhtml version unless you explicitly say render(:file => "xxx.rjs").2
Rendering Templates
A template is a file that defines the content of a response for our application. Rails supports three template formats out of the box: rhtml, which is HTML with embedded Ruby code; builder, a more programmatic way of constructing XML content; and rjs, which generates JavaScript. We’ll talk about the contents of these files starting on page 465.
By convention, the template for action action of controller control will be in the file app/views/control/action.xxx (where xxx is one of rhtml, rxml, or rjs). The
1.In fact, the controller returns a set of HTTP headers, because some kind of response is expected.
2.There’s an obscure exception to this. Once Rails finds a template, it caches it. If you’re in
development mode and you change the type of a template, Rails may not find it, because it will give preference to the previously cached name. You’ll have to restart your application to get the new template invoked.
Report erratum

ACTION METHODS 428
app/views part of the name is the default. It may be overridden for an entire application by setting
ActionController::Base.template_root =dir_path
The render method is the heart of all rendering in Rails. It takes a hash of options that tell it what to render and how to render it.
It is tempting to write code in our controllers that looks like this.
# DO NOT DO THIS def update
@user = User.find(params[:id])
if @user.update_attributes(params[:user]) render :action => show
end
render :template => "fix_user_errors" end
It seems somehow natural that the act of calling render (and redirect_to) should somehow terminate the processing of an action. This is not the case. The previous code will generate an error (because render is called twice) in the case where update_attributes succeeds.
Let’s look at the render options used in the controller here (we’ll look separately at rendering in the view starting on page 509).
render()
With no overriding parameter, the render method renders the default template for the current controller and action. The following code will render the template app/views/blog/index.
class BlogController < ApplicationController def index
render end
end
So will the following (as the default action of a controller is to call render if the action doesn’t).
class BlogController < ApplicationController def index
end end
And so will this (as the controller will call a template directly if no action method is defined).
class BlogController < ApplicationController end
Report erratum

ACTION METHODS 429
render(:text =>string)
Sends the given string to the client. No template interpretation or HTML escaping is performed.
class HappyController < ApplicationController def index
render(:text => "Hello there!") end
end
render(:inline =>string, [ :type =>"rhtml"|"rxml"|"rjs" ], [ :locals =>hash] )
Interprets string as the source to a template of the given type, rendering the results back to the client. If the :locals hash is given, the contents are used to set the values of local variables in the template.
The following code adds method_missing to a controller if the application is running in development mode. If the controller is called with an invalid action, this renders an inline template to display the action’s name and a formatted version of the request parameters.
class SomeController < ApplicationController
if RAILS_ENV == "development"
def method_missing(name, *args) render(:inline => %{
<h2>Unknown action: #{name}</h2>
Here are the request parameters:<br/> <%= debug(params) %> })
end end
end
render(:action =>action_name)
Renders the template for a given action in this controller. Sometimes folks use the :action form of render when they should use redirects—see the discussion starting on page 432 for why this is a bad idea.
def display_cart if @cart.empty?
render(:action => :index) else
# ...
end end
Note that calling render(:action...) does not call the action method; it simply displays the template. If the template needs instance variables, these must be set up by the method that calls the render.
Let’s repeat this, because this is a mistake that beginners often make: calling render(:action...) does not invoke the action method—it simply renders that action’s default template.
Report erratum

ACTION METHODS 430
render(:file =>path, [ :use_full_path =>true|false], [:locals =>hash)
Renders the template in the given path (which must include a file extension). By default this should be an absolute path to the template, but if the :use_full_path option is true, the view will prepend the value of the template base path to the path you pass in. The template base path is set in the configuration for your application (described on page 237). If specified, the values in the :locals hash are used to set local variables in the template.
render(:template =>name)
Renders a template and arranges for the resulting text to be sent back to the client. The :template value must contain both the controller and action parts of the new name, separated by a forward slash. The following code will render the template app/views/blog/short_list.
class BlogController < ApplicationController def index
render(:template => "blog/short_list") end
end
render(:partial =>name, ...)
Renders a partial template. We talk about partial templates in depth on page 509.
render(:nothing => true)
Returns nothing—sends an empty body to the browser.
render(:xml =>stuff )
Renders stuff as text, forcing the content type to be application/xml.
render(:update) do |page| ... end
Renders the block as an rjs template, passing in the page object.
render(:update) do |page|
page[:cart].replace_html :partial => 'cart', :object => @cart page[:cart].visual_effect :blind_down if @cart.total_items == 1
end
All forms of render take optional :status, :layout, and :content_type parameters. The :status parameter is used to set the status header in the HTTP response. It defaults to "200 OK". Do not use render with a 3xx status to do redirects; Rails has a redirect method for this purpose.
The :layout parameter determines whether the result of the rendering will be wrapped by a layout (we first came across layouts on page 98, and we’ll look at them in depth starting on page 505). If the parameter is false, no layout will be applied. If set to nil or true, a layout will be applied only if there is one associated with the current action. If the :layout parameter has a string as a
Report erratum

ACTION METHODS 431
value, it will be taken as the name of the layout to use when rendering. A layout is never applied when the :nothing option is in effect.
The :content_type parameter lets you specify a value that will be passed to the browser in the Content-Type HTTP header.
Sometimes it is useful to be able to capture what would otherwise be sent to the browser in a string. The render_to_string method takes the same parameters as render but returns the result of rendering as a string—the rendering is not stored in the response object and so will not be sent to the user unless you take some additional steps. Calling render_to_string does not count as a real render: you can invoke the real render method later without getting a DoubleRender error.
Sending Files and Other Data
We’ve looked at rendering templates and sending strings in the controller. The third type of response is to send data (typically, but not necessarily, file contents) to the client.
send_data
Send a string containing binary data to the client.
send_data(data, options...)
Sends a data stream to the client. Typically the browser will use a combination of the content type and the disposition, both set in the options, to determine what to do with this data.
def sales_graph
png_data = Sales.plot_for(Date.today.month)
send_data(png_data, :type => "image/png", :disposition => "inline") end
Options: |
|
:disposition |
string Suggests to the browser that the file should be displayed inline (option inline) |
|
or downloaded and saved (option attachment, the default) |
:filename |
string A suggestion to the browser of the default filename to use when saving this |
|
data |
:status |
string The status code (defaults to "200 OK") |
:type |
string The content type, defaulting to application/octet-stream |
Report erratum

ACTION METHODS 432
send_file
Send the contents of a file to the client.
send_file(path, options...)
Sends the given file to the client. The method sets the Content-Length, Content-Type,
Content-Disposition, and Content-Transfer-Encoding headers.
Options: |
|
|
:buffer_size |
number |
The amount sent to the browser in each write if streaming is enabled |
|
|
(:stream is true). |
:disposition |
string |
Suggests to the browser that the file should be displayed inline (option |
|
|
inline) or downloaded and saved (option attachment, the default). |
:filename |
string |
A suggestion to the browser of the default filename to use when saving |
|
|
the file. If not set, defaults to the filename part of path. |
:status |
string |
The status code (defaults to "200 OK"). |
:stream |
true or false |
If false, the entire file is read into server memory and sent to the |
|
|
client. Otherwise, the file is read and written to the client in :buffer_size |
|
|
chunks. |
:type |
string |
The content type, defaulting to application/octet-stream. |
You can set additional headers for either send_ method using the headers attribute in the controller.
def send_secret_file send_file("/files/secret_list") headers["Content-Description" ] = "Top secret"
end
We show how to upload files starting on page 501.
Redirects
An HTTP redirect is sent from a server to a client in response to a request. In effect it says, “I can’t handle this request, but here’s some URL that can.” The redirect response includes a URL that the client should try next along with some status information saying whether this redirection is permanent (status code 301) or temporary (307). Redirects are sometimes used when web pages are reorganized; clients accessing pages in the old locations will get referred to the page’s new home. More commonly, Rails applications use redirects to pass the processing of a request off to some other action.
Redirects are handled behind the scenes by web browsers. Normally, the only way you’ll know that you’ve been redirected is a slight delay and the fact that the URL of the page you’re viewing will have changed from the one you requested. This last point is important—as far as the browser is concerned, a redirect from a server acts pretty much the same as having an end user enter the new destination URL manually.
Redirects turn out to be important when writing well-behaved web applications.
Report erratum

ACTION METHODS 433
Let’s look at a simple blogging application that supports comment posting. After a user has posted a comment, our application should redisplay the article, presumably with the new comment at the end. It’s tempting to code this using logic such as the following.
class BlogController def display
@article = Article.find(params[:id]) end
def add_comment
@article = Article.find(params[:id]) comment = Comment.new(params[:comment]) @article.comments << comment
if @article.save
flash[:note] = "Thank you for your valuable comment" else
flash[:note] = "We threw your worthless comment away" end
# DON'T DO THIS render(:action => 'display')
end end
The intent here was clearly to display the article after a comment has been posted. To do this, the developer ended the add_comment method with a call to render(:action=>'display'). This renders the display view, showing the updated article to the end user. But think of this from the browser’s point of view. It sends a URL ending in blog/add_comment and gets back an index listing. As far as the browser is concerned, the current URL is still the one that ends blog/add_comment. This means that if the user hits Refresh or Reload (perhaps to see whether anyone else has posted a comment), the add_comment URL will be sent again to the application. The user intended to refresh the display, but the application sees a request to add another comment. In a blog application this kind of unintentional double entry is inconvenient. In an online store it can get expensive.
In these circumstances, the correct way to show the added comment in the index listing is to redirect the browser to the display action. We do this using the Rails redirect_to method. If the user subsequently hits Refresh, it will simply reinvoke the display action and not add another comment.
def add_comment
@article = Article.find(params[:id]) comment = Comment.new(params[:comment]) @article.comments << comment
if @article.save
flash[:note] = "Thank you for your valuable comment" else
flash[:note] = "We threw your worthless comment away"
Report erratum

ACTION METHODS 434
end
redirect_to(:action => 'display') end
Rails has a simple yet powerful redirection mechanism. It can redirect to an action in a given controller (passing parameters), to a URL (on or off the current server), or to the previous page. Let’s look at these three forms in turn.
redirect_to
Redirects to an action
redirect_to(:action => ..., options...)
Sends a temporary redirection to the browser based on the values in the options hash. The target URL is generated using url_for, so this form of redirect_to has all the smarts of Rails routing code behind it. See Section 20.2, Routing Requests, on page 393 for a description.
redirect_to
Redirect to a URL.
redirect_to(path)
Redirects to the given path. If the path does not start with a protocol (such as http://), the protocol and port of the current request will be prepended. This method does not perform any rewriting on the URL, so it should not be used to create paths that are intended to link to actions in the application (unless you generate the path using url_for or a named route URL generator).
def save
order = Order.new(params[:order]) if order.save
redirect_to :action => "display" else
session[:error_count] ||= 0 session[:error_count] += 1 if session[:error_count] < 4
flash[:notice] = "Please try again" else
# Give up -- user is clearly struggling redirect_to("/help/order_entry.html")
end end
end
Report erratum