The Rails 4 Way (2014)
Chapter 4. Working with Controllers
Remove all business logic from your controllers and put it in the model. (My) instructions are precise, but following them requires intuition and subtle reasoning.
—Nick Kallen
Like any computer program, your Rails application involves the flow of control from one part of your code to another. The flow of program control gets pretty complex with Rails applications. There are many bits and pieces in the framework, many of which execute each other. And part of the framework’s job is to figure out, on the fly, what your application files are called and what’s in them, which of course varies from one application to another.
The heart of it all, though, is pretty easy to identify: It’s the controller. When someone connects to your application, what they’re basically doing is asking the application to execute a controller action. Sure, there are many different flavors of how this can happen and edge cases where it doesn’t exactly happen at all. But if you know how controllers fit into the application life cycle, you can anchor everything else around that knowledge. That’s why we’re covering controllers before the rest of the Rails APIs.
Controllers are the C in MVC. They’re the first port of call, after the dispatcher, for the incoming request. They’re in charge of the flow of the program: They gather information and make it available to the views.
Controllers are also very closely linked to views, more closely than they’re linked to models. It’s possible to write the entire model layer of an application before you create a single controller, or to have different people working on the controller and model layers who never meet or talk to each other. However, views and controllers are more tightly coupled to one another. They share a lot of information and the names you choose for your variables in the controller will have an effect on what you do in the view.
In this chapter, we’re going to look at what happens on the way to a controller action being executed, and what happens as a result. In the middle, we’ll take a long look at how controller classes themselves are set up, particularly in regard to the many different ways that we can render views. We’ll wrap up the chapter with a couple of additional topics related to controllers: action callbacks and streaming.
4.1 Rack
Rack is a modular interface for handling web requests, written in Ruby, with support for many different web servers. It abstracts away the handling of HTTP requests and responses into a single, simple call method that can be used by anything from a plain Ruby script all the way to Rails itself.
Listing 2.1: HelloWorld as a Rack application
1 classHelloWorld
2 def call(env)
3 [200, {"Content-Type" => "text/plain"}, ["Hello world!"]]
4 end
5 end
An HTTP request invokes the call method and passes in a hash of environment variables, akin to the way that CGI works. The call method should return a 3-element array consisting of the status, a hash of response headers, and finally, the body of the request.
As of Rails 2.3, request handling was moved to Rack and the concept of middleware was introduced. Classes that satisfy Rack’s call interface can be chained together as filters. Rack itself includes a number of useful filter classes that do things such as logging and exception handling.
Rails 3 took this one step further and was re-architected from the ground up to fully leverage Rack filters in a modular and extensible manner. A full explanation of Rails’ Rack underpinnings are outside the scope of this book, especially since Rack does not really play a part in day-to-day development of applications. However, it is essential Rails 4 knowledge to understand that much of Action Controller is implemented as Rack middleware modules. Want to see which Rack filters are enabled for your Rails 4 application? There’s a rake task for that!
$ rake middleware
use Rack::Runtime
use Rack::MethodOverride
use ActionDispatch::RequestId
use Rails::Rack::Logger
use ActionDispatch::ShowExceptions
use ActionDispatch::DebugExceptions
use ActionDispatch::RemoteIp
use ActionDispatch::Reloader
use ActionDispatch::Callbacks
use ActiveRecord::Migration::CheckPending
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
use ActionDispatch::Cookies
use ActionDispatch::Session::CookieStore
use ActionDispatch::Flash
use ActionDispatch::ParamsParser
use Rack::Head
use Rack::ConditionalGet
use Rack::ETag
run Example::Application.routes
What’s checking for pending Active Record migrations have to do with serving requests anyway?
1 moduleActiveRecord
2 classMigration
3 classCheckPending
4 ...
5
6 def call(env)
7 ActiveRecord::Base.logger.silence do
8 ActiveRecord::Migration.check_pending!
9 end
10 @app.call(env)
11 end
12 end
13 end
14 end
Ahh, it’s not that pending Active Record migrations has anything specifically to do with serving requests. It’s that Rails 4 is designed in such a way that different aspects of its behavior are introduced into the request call chain as individual Rack middleware components or filters.
4.1.1 Configuring Your Middleware Stack
Your application object allows you to access and manipulate the Rack middleware stack during initialization, via config.middleware like
1 # config/application.rb
2
3 moduleExample
4 classApplication < Rails::Application
5 ...
6 # Rack::ShowStatus catches all empty responses the app it wraps and
7 # replaces them with a site explaining the error.
8 config.middleware.use Rack::ShowStatus
9 end
10 end
Rack Lobster
As I found out trying to experiment with the hilariously-named Rack::Lobster, your custom Rack middleware classes need to have an explicit initializer method, even if they don’t require runtime arguments.
The methods of config.middleware give you very fine-grained control over the order in which your middleware stack is configured. The args parameter is an optional hash of attributes to pass to the initializer method of your Rack filter.
4.1.1.1 config.middleware.insert_after(existing_middleware, new_middleware, args)
Adds the new middleware after the specified existing middleware in the middleware stack.
4.1.1.2 config.middleware.insert_before(existing_middleware, new_middleware, args)
Adds the new middleware before the specified existing middleware in the middleware stack.
4.1.1.3 config.middleware.delete(middleware)
Removes a specified middleware from the stack.
4.1.1.4 config.middleware.swap(existing_middleware, new_middleware, args)
Swaps a specified middleware from the stack with a new class.
4.1.1.5 config.middleware.use(new_middleware, args)
Takes a class reference as its parameter and just adds the desired middleware to the end of the middleware stack.
4.2 Action Dispatch: Where It All Begins
Controller and view code in Rails has always been part of its Action Pack framework. As of Rails 3, dispatching of requests was extracted into its own sub-component of Action Pack called Action Dispatch. It contains classes that interface the rest of the controller system to Rack.
4.2.1 Request Handling
The entry point to a request is an instance of ActionDispatch::Routing::RouteSet, the object on which you can call draw at the top of config/routes.rb.
The route set chooses the rule that matches, and calls its Rack endpoint. So a route like
get 'foo', to: 'foo#index'
has a dispatcher instance associated to it, whose call method ends up executing
FooController.action(:index).call
As covered in Section “Routes as Rack Endpoints”, the route set can call any other type of Rack endpoint, like a Sinatra app, a redirect macro or a bare lambda. In those cases no dispatcher is involved.
All of this happens quickly, behind the scenes. It’s unlikely that you would ever need to dig into the source code of ActionDispatch; it’s the sort of thing that you can take for granted to just work. However, to really understand the Rails way, it is important to know what’s going on with the dispatcher. In particular, it’s important to remember that the various parts of your application are just bits (sometimes long bits) of Ruby code, and that they’re getting loaded into a running Ruby interpreter.
4.2.2 Getting Intimate with the Dispatcher
Just for the purpose of learning, let’s trigger the Rails dispatching mechanism manually. We’ll do this little exercise from the ground up, starting with a new Rails application:
$ rails new dispatch_me
Now, create a single controller demo, with an index action (Note that haml is setup as our template language):
$ cd dispatch_me/
$ rails generate controller demo index
create app/controllers/demo_controller.rb
route get "demo/index"
invoke haml
create app/views/demo
create app/views/demo/index.html.haml
invoke test_unit
create test/controllers/demo_controller_test.rb
invoke helper
create app/helpers/demo_helper.rb
invoke test_unit
create test/helpers/demo_helper_test.rb
invoke assets
invoke coffee
create app/assets/javascripts/demo.js.coffee
invoke scss
create app/assets/stylesheets/demo.css.scss
If you take a look at app/controllers/demo_controller.rb, you’ll see that it has an index action:
classDemoController < ApplicationController
def index
end
end
There’s also a view template file, app/views/demo/index.html.haml with some placeholder language. Just to see things more clearly, let’s replace it with something we will definitely recognize when we see it again. Replace the contents of index.html.haml with
Hello!
Not much of a design accomplishment, but it will do the trick.
Now that we’ve got a set of dominos lined up, it’s just a matter of pushing over the first one: the dispatcher. To do that, start by firing up the Rails console from your Rails application directory.
$ rails console
Loading development environment
>>
There are some variables from the web server that Rack expects to use for request processing. Since we’re going to be invoking the dispatcher manually, we have to set those variables like this in the console (output ommited for brevity)
>> env = {}
>> env['REQUEST_METHOD'] = 'GET'
>> env['PATH_INFO'] = '/demo/index'
>> env['rack.input'] = StringIO.new
Now that we’ve replicated an HTTP environment, we’re now ready to fool the dispatcher into thinking it’s getting a request. Actually, it is getting a request. It’s just that it’s coming from someone sitting at the console, rather than from a proper web server:
>> rack_body_proxy = DispatchMe::Application.call(env).last
>> rack_body_proxy.last
=> "<!DOCTYPE html>\n<html>\n<head>\n <title>DispatchMe</title>\n
<link data-turbolinks-track=\"true\" href=\"/assets/application.css?body=1\"
media=\"all\" rel=\"stylesheet\" />\n<link data-turbolinks-track=\"true\"
href=\"/assets/demo.css?body=1\" media=\"all\" rel=\"stylesheet\" />\n
<script data-turbolinks-track=\"true\"
src=\"/assets/jquery.js?body=1\"></script>\n
<script data-turbolinks-track=\"true\"
src=\"/assets/jquery_ujs.js?body=1\"></script>\n
<script data-turbolinks-track=\"true\"
src=\"/assets/turbolinks.js?body=1\"></script>\n
<script data-turbolinks-track=\"true\"
src=\"/assets/demo.js?body=1\"></script>\n
<script data-turbolinks-track=\"true\"
src=\"/assets/application.js?body=1\"></script>\n
<meta content=\"authenticity_token\" name=\"csrf-param\" />\n
<meta content=\"cmfwNmZzzqRv94sv75OnO5Mon1C0XeWzuG90PUOeqPc=\"
name=\"csrf-token\" />\n</head>\n<body>\n\nHello\n\n\n</body>\n</html>\n"
If you want to see everything contained in the ActionDispatch::Response object returned from call then try the following code:
>> y DispatchMe::Application.call(env)
The handy y method formats its argument as a yaml string, making it a lot easier to understand. We won’t reproduce the output here because it’s huge.
So, we’ve executed the call method of of our Rails application and as a result, the index action got executed and the index template (such as it is) got rendered and the results of the rendering got wrapped in some HTTP headers and returned.
Just think: If you were a web server, rather than a human, and you had just done the same thing, you could now return that document, headers and “Hello!” and all, to a client.
You can follow the trail of bread crumbs even further by diving into the Rails source code, but for purposes of understanding the chain of events in a Rails request, and the role of the controller, the peek under the hood we’ve just done is sufficient.
Tim says… Note that if you give Rack a path that resolves to a static file, it will be served directly from the web server without involving the Rails stack. As a result, the object returned by the dispatcher for a static file is different than what you might expect. |
4.3 Render unto View…
The goal of the typical controller action is to render a view template—that is, to fill out the template and hand the results, usually an HTML document, back to the server for delivery to the client. Oddly—at least it might strike you as a bit odd, though not illogical—you don’t actually need to define a controller action, as long as you’ve got a template that matches the action name.
You can try this out in under-the-hood mode. Go into app/controller/demo_controller.rb, and delete the index action so that the file will look empty, like this:
classDemoController < ApplicationController
end
Don’t delete app/views/demo/index.html.haml, and then try the console exercise (DispatchMe::Application.call(env) and all that) again. You’ll see the same result.
By the way, make sure you reload the console when you make changes—it doesn’t react to changes in source code automatically. The easiest way to reload the console is simply to type reload!. But be aware that any existing instances of Active Record objects that you’re holding on to will also need to be reloaded (using their individual reload methods). Sometimes it’s simpler to just exit the console and start it up again.
4.3.1 When in Doubt, Render
Rails knows that when it gets a request for the index action of the demo controller, what really matters is handing something back to the server. So if there’s no index action in the controller file, Rails shrugs and says, “Well, let’s just assume that if there were an index action, it would be empty anyway, and I’d just render index.html.haml. So that’s what I’ll do.”
You can learn something from an empty controller action, though. Let’s go back to this version of the demo controller:
classDemoController < ApplicationController
def index
end
end
What you learn from seeing the empty action is that, at the end of every controller action, if nothing else is specified, the default behavior is to render the template whose name matches the name of the controller and action, which in this case means app/views/demo/index.html.haml.
In other words, every controller action has an implicit render command in it. And render is a real method. You could write the preceding example like this:
def index
render "demo/index"
end
You don’t have to, though, because it’s assumed that it’s what you want, and that is part of what Rails people are talking about when they discuss convention over configuration. Don’t force the developer to add code to accomplish something that can be assumed to be a certain way.
The render command, however, does more than just provide a way of telling Rails to do what it was going to do anyway.
4.3.2 Explicit Rendering
Rendering a template is like putting on a shirt: If you don’t like the first one you find in your closet—the default, so to speak—you can reach for another one and put it on instead.
If a controller action doesn’t want to render its default template, it can render a different one by calling the render method explicitly. Any template file in the app/views directory tree is available. (Actually, that’s not exactly true. Any template on the whole system is available!) But why would you want your controller action to render a template other than its default? There are several reasons, and by looking at some of them, we can cover all of the handy features of the controller’s render method.
4.3.3 Rendering Another Action’s Template
A common reason for rendering an entirely different template is to redisplay a form, when it gets submitted with invalid data and needs correction. In such circumstances, the usual web strategy is to redisplay the form with the submitted data, and trigger the simultaneous display of some error information, so that the user can correct the form and resubmit.
The reason that process involves rendering another template is that the action that processes the form and the action that displays the form may be—and often are—different from each other. Therefore, the action that processes the form needs a way to redisplay the original (form) template, instead of treating the form submission as successful and moving on to whatever the next screen might be.
Wow, that was a mouthful of an explanation. Here’s a practical example:
1 classEventController < ActionController::Base
2 def new
3 # This (empty) action renders the new.html.haml template, which
4 # contains the form for inputting information about the new
5 # event record and is not actually needed.
6 end
7
8 def create
9 # This method processes the form input. The input is available via
10 # the params hash, in the nested hash keyed to :event
11 @event = Event.new(params[:event])
12 if @event.save
13 # ignore the next line for now
14 redirect_to dashboard_path, notice: "Event created!"
15 else
16 render action: 'new' # doesn't execute the new method!
17 end
18 end
19 end
On failure, that is, if @event.save does not return true, we render the “new” template. Assuming new.html.haml has been written correctly, this will automatically include the display of error information embedded in the new (but unsaved) Event object.
Note that the template itself doesn’t “know” that it has been rendered by the create action rather than the new action. It just does its job: It fills out and expands and interpolates, based on the instructions it contains and the data (in this case, @event) that the controller has passed to it.
4.3.4 Rendering a Different Template Altogether
In a similar fashion, if you are rendering a template for a different action, it is possible to render any template in your application by calling render with a string pointing to the desired template file. The render method is very robust in its ability to interpret which template you’re trying to refer to.
render template: '/products/index.html.haml'
A couple of notes: It’s not necessary to pass a hash with :template, because it’s the default option. Also, in our testing, all of the following permutations worked identically when called from ProductsController:
render '/products/index.html.haml'
render 'products/index.html.haml'
render 'products/index.html'
render 'products/index'
render 'index'
render :index
The :template option only works with a path relative to the template root (app/views, unless you changed it, which would be extremely unusual).
Tim says… Use only enough to disambiguate. The content type defaults to that of the request and if you have two templates that differ only by template language, you’re Doing It Wrong. |
4.3.5 Rendering a Partial Template
Another option is to render a partial template (usually referred to simply as a partial). Usage of partial templates allows you to organize your template code into small files. Partials can also help you to avoid clutter and encourage you to break your template code up into reusable modules.
There are a few ways to trigger partial rendering. The first, and most obvious, is using the :partial option to explicitly specify a partial template. Rails has a convention of prefixing partial template file names with an underscore character, but you never include the underscore when referring to partials.
render partial: 'product' # renders app/views/products/_product.html.haml
Leaving the underscore off of the partial name applies, even if you’re referring to a partial in a different directory than the controller that you’re currently in!
render partial: 'shared/product'
# renders app/views/shared/_product.html.haml
The second way to trigger partial rendering depends on convention. If you pass render :partial an object, Rails will use its class name to find a partial to render. You can even omit the :partial option, like in the following example code.
render partial: @product
render @product
render 'product'
All three lines render the app/views/products/_product.html.haml template.
Partial rendering from a controller is mostly used in conjunction with Ajax calls that need to dynamically update segments of an already displayed page. The technique, along with generic use of partials in views, is covered in greater detail in Chapter 10, “Action View”.
4.3.6 Rendering Inline Template Code
Occasionally, you need to send the browser the result of translating a snippet of template code, too small to merit its own partial. I admit that this practice is contentious, because it is a flagrant violation of proper separation of concerns between the MVC layers.
Rails treats the inline code exactly as if it were a view template. The default type of view template processing is ERb, but passing an additional :type option allows you to choose Haml.
render inline: "%span.foo #{@foo.name}", type: "haml"
Courtenay says… If you were one of my employees, I’d reprimand you for using view code in the controller, even if it is only one line. Keep your view-related code in the views! |
4.3.7 Rendering Text
What if you simply need to send plain text back to the browser, particularly when responding to Ajax and certain types of web service requests?
render text: 'Submission accepted'
Unfortunately, if you don’t pass an additional :content_type option, Rails will default the response MIME type to text/html, rather than text/plain. The solution is to be explicit about what you want.
render text: 'Submission accepted', content_type: 'text/plain'
4.3.8 Rendering Other Types of Structured Data
The render command also accepts a series of (convenience) options for returning structured data such as JSON or XML. The content-type of the response will be set appropriately and additional options apply.9
4.3.8.1 :json
JSON10 is a small subset of JavaScript selected for its usability as a lightweight data-interchange format. It is mostly used as a way of sending data down to JavaScript code running in a rich web application via Ajax calls. Active Record has built-in support for conversion to JSON, which makes Rails an ideal platform for serving up JSON data, as in the following example:
render json: @record
As long as the parameter responds to to_json, Rails will call it for you, which means you don’t have to call it yourself with ActiveRecord objects.
Any additional options passed to render :json are also included in the invocation of to_json.
render json: @projects, include: :tasks
Additionally, if you’re doing JSONP, you can supply the name of a callback function to be invoked in the browser when it gets your response. Just add a :callback option with the name of a valid JavaScript method.
render json: @record, callback: 'updateRecordsDisplay'
4.3.8.2 :xml
Active Record also has built-in support for conversion to XML, as in the following example:
render xml: @record
As long as the parameter responds to to_xml, Rails will call it for you, which means you don’t have to call it yourself with ActiveRecord objects.
Any additional options passed to render :xml are also included in the invocation of to_xml.
render xml: @projects, include: :tasks
4.3.9 Rendering Nothing
On rare occasions, you don’t want to render anything at all. (To avoid a bug in Safari, rendering nothing actually means sending a single space character back to the browser.)
head :unauthorized
The head method allows you to return a response with no content, and a specific status code. You could achieve the same result as the above code snippet, by calling render nothing: true and explicitly providing a status.
render nothing: true, status: 401
The head method also accepts an options hash, that is interpreted as header names and values to be included with the response. To illustrate, consider the following example which returns an empty response with a status of 201, and also sets the Location header:
head :created, location: auction_path(@auction)
4.3.10 Rendering Options
Most calls to the render method accept additional options. Here they are in alphabetical order.
:content_type
All content flying around the web is associated with a MIME type.11 For instance, HTML content is labeled with a content-type of text/html. However, there are occasions where you want to send the client something other than HTML. Rails doesn’t validate the format of the MIME identifier you pass to the :content_type option, so make sure it is valid.
:layout
By default, Rails has conventions regarding the layout template it chooses to wrap your response in, and those conventions are covered in detail in Chapter 10, “Action View”. The :layout option allows you to specify whether you want a layout template to be rendered if you pass it a boolean value, or the name of a layout template, if you want to deviate from the default.
render layout: false # disable layout template
render layout: 'login' # a template app/views/layouts is assumed
:status
The HTTP protocol includes many standard status codes12 indicating a variety of conditions in response to a client’s request. Rails will automatically use the appropriate status for most common cases, such as 200 OK for a successful request.
The theory and techniques involved in properly using the full range of HTTP status codes would require a dedicated chapter, perhaps an entire book. For your convenience, Table 4.1 demonstrates some codes that I’ve occasionally found useful in my day-to-day Rails programming.
Status Code |
Description |
200 OK |
Everything is fine and here is your content. |
201 Created |
A new resource has been created and its location |
can be found in the Location HTTP response |
|
header. |
|
307 Temporary Redirect |
The requested resource resides temporarily under |
a different URI. |
|
Occasionally, you need to temporarily redirect |
|
the user to a different action, perhaps while |
|
some long-running process is happening or while |
|
the account of a particular resource’s owner is |
|
suspended. |
|
This particular status code dictates that an |
|
HTTP response header named Location contain |
|
the URI of the resource that the client |
|
redirects to. Since the render method doesn’t |
|
take a hash of response header fields, you have |
|
to set them manually prior to invoking render. |
|
Luckily, the response hash is in scope within |
|
controller methods, as in the following example: |
|
def paid_resource |
|
if current_user.account_expired? |
|
response.headers['Location'] = |
|
account_url(current_user) |
|
render text: "Account expired", |
|
status: 307 |
|
end |
|
end |
|
401 Unauthorized |
Sometimes a user will not provide credentials to |
view a restricted resource or authentication |
|
and/or authorization will fail. Assuming using a |
|
Basic or Digest HTTP Authentication scheme, when |
|
that happens you should probably return a 401. |
|
403 Forbidden |
I like to use 403 in conjunction with a short |
The server understood the |
render :text message in situations where the |
request, but is refusing |
client has requested a resource that is not |
to fulfill it. |
normally available via the web application’s |
interface. |
|
In other words, the request appears to have |
|
happened via artificial means. A human or robot, |
|
for reasons innocent or guilty (it doesn’t |
|
matter) is trying to trick the server into doing |
|
something it isn’t supposed to do. |
|
For example, my current Rails application is |
|
public-facing and is visited by the GoogleBot on |
|
a daily basis. Probably due to a bug existing at |
|
some point, the URL /favorites was indexed. |
|
Unfortunately, /favorites is only supposed to |
|
be available to logged-in users. However, once |
|
Google knows about a URL it will keep coming |
|
back for it in the future. This is how I told it |
|
to stop: |
|
def index |
|
return render nothing: true, |
|
status: 403 unless logged_in? |
|
@favorites = current_user.favorites.all |
|
end |
|
404 Not Found |
You may choose to use 404 when a resource of a |
The server cannot find the |
specific given ID does not exist in your |
resource you requested. |
database (whether due to it being an invalid ID |
or due to the resource having been deleted). |
|
For example, “GET /people/2349594934896107” |
|
doesn’t exist in our database at all, so what do |
|
we display? Do we render a show view with a |
|
flash message saying no person with that ID |
|
exists? Not in our RESTful world. A 404 would be |
|
better. |
|
Moreover, if we happen to be using something |
|
like paranoia and we know that the resource |
|
used to exist in the past, we could respond with |
|
410 Gone. |
|
500 Internal Server |
The server encountered an unexpected condition |
Error |
which prevented it from fulfilling the request. |
you probably know by now, this is the status |
|
code that Rails serves up if you have an error |
|
in your code. |
|
503 Service Unavailable |
The 503 code comes in very handy when taking |
The server is temporarily |
a site down for maintenance, particularly when |
unavailable. |
upgrading RESTful web services. |
4.4 Additional Layout Options
You can specify layout options at the controller class level if you want to reuse layouts for multiple actions.
classEventController < ActionController::Base
layout "events", only: [:index, :new]
layout "global", except: [:index, :new]
end
The layout method can accept either a String, Symbol, or boolean, with a hash of arguments after.
String
Determines the template name to use.
Symbol
Call the method with this name, which is expected to return a string with a template name.
true
Raises an argument error.
false
Do not use a layout.
The optional arguments are either :only or :except and expect an array of action names that should or should not apply to the layout being specified.
4.5 Redirecting
The life cycle of a Rails application is divided into requests. Rendering a template, whether the default one or an alternate one—or, for that matter, rendering a partial or some text or anything—is the final step in the handling of a request. Redirecting, however, means terminating the current request and asking the client to initiate a new one.
Look again at the example of the form-handling create method:
1 def create
2 if @event.save
3 flash[:notice] = "Event created!"
4 redirect_to :index
5 else
6 render :new
7 end
8 end
If the save operation succeeds, we store a message in the flash hash and redirect_to a completely new action. In this case, it’s the index action. The logic here is that if the new Event record gets saved, the next order of business is to take the user back to the top-level view.
The main reason to redirect rather than just render a template after creating or editing a resource (really a POST action) has to do with browser reload behavior. If you didn’t redirect, the user would be prompted to re-submit the form if they hit the back button or reload.
Sebastian says… Which redirect is the right one? When you use Rails’ redirect_to method, you tell the user agent (i.e., the browser) to perform a new request for a different URL. That response can mean different things, and it’s why modern HTTP has four different status codes for redirection.The old HTTP 1.0 had two codes: 301 aka Moved Permanently and 302 aka Moved Temporarily. A permanent redirect meant that the user agent should forget about the old URL and use the new one from now on, updating any references it might have kept (i.e., a bookmark or in the case of Google, its search databases). A temporary redirect was a one-time only affair. The original URL was still valid, but for this particular request the user agent should fetch a new resource from the redirection URL. But there was a problem: If the original request had been a POST, what method should be used for the redirected request? For permanent redirects it was safe to assume the new request should be a GET, since that was the case in all usage scenarios. But temporary redirects were used both for redirecting to a view of a resource that had just been modified in the original POST request (which happens to be the most common usage pattern), and also for redirecting the entire original POST request to a new URL that would take care of it. HTTP 1.1 solved this problem with the introduction of two new status codes: 303 meaning See Other and 307 meaning Temporary Redirect. A 303 redirect would tell the user agent to perform a GET request, regardless of what the original verb was, whereas a 307 would always use the same method used for the original request. These days, most browsers handle 302 redirects the same way as 303, with a GET request, which is the argument used by the Rails Core team to keep using 302 in redirect_to. A 303 status would be the better alternative, because it leaves no room for interpretation (or confusion), but I guess nobody has found it annoying enough to push for a patch. If you ever need a 307 redirect, say, to continue processing a POST request in a different action, you can always accomplish your own custom redirect by assigning a path to response.header["Location"] and then rendering with render status: 307. |
4.5.1 The redirect_to Method
The redirect_to method takes two parameters:
redirect_to(target, response_status = {})
The target parameter takes one of several forms.
Hash - The URL will be generated by calling url_for with the argument provided.
redirect_to action: "show", id: 5
Active Record object - The URL will be generated by calling url_for with the object provided, which should generate a named URL for that record.
redirect_to post
String starting with protocol like http:// - Used directly as the target url for redirection.
redirect_to "http://www.rubyonrails.org"
redirect_to articles_url
String not containing a protocol - The current protocol and host is prepended to the argument and used for redirection.
redirect_to "/"
redirect_to articles_path
:back - Back to the page that issued the request. Useful for forms that are triggered from multiple places. Short-hand for redirect_to(request.env["HTTP_REFERER"]). When using redirect_to :back, if there is no referrer set, a RedirectBackError will be raised. You may specify some fallback behavior for this case by rescuing RedirectBackError.
Redirection happens as a “302 Moved” header unless otherwise specified. The response_status parameter takes a hash of arguments. The code can be specified by name or number, as in the following examples:
redirect_to post_url(@post), status: :found
redirect_to :atom, status: :moved_permanently
redirect_to post_url(@post), status: 301
redirect_to :atom, status: 302
It is also possible to assign a flash message as part of the redirection. There are two special accessors for commonly used flash names alert and notice, as well as a general purpose flash bucket.
redirect_to post_url(@post), alert: "Watch it, mister!"
redirect_to post_url(@post), status: :found, notice: "Pay attention to the road"
redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
redirect_to :atom, alert: "Something serious happened"
New to Rails 4, is the ability to register your own flash types by using the new ActionController::Flash.add_flash_types macro style method.
classApplicationController
...
add_flash_types :error
end
When a flash type is registered, a special flash accessor similar to alert and notice, becomes available to be used with redirect_to.
redirect_to post_url(@post), error: "Something went really wrong!"
Courtenay says… Remember that redirect and render statements don’t magically halt execution of your controller action method. To prevent DoubleRenderError, consider explicitly calling return after redirect_to or render like this: 1 def show 2 @user = User.find(params[:id]) 3 if @user.activated? 4 render :activated andreturn 5 end 6 ... 7 end |
4.6 Controller/View Communication
When a view template is rendered, it generally makes use of data that the controller has pulled from the database. In other words, the controller gets what it needs from the model layer, and hands it off to the view.
The way Rails implements controller-to-view data handoffs is through instance variables. Typically, a controller action initializes one or more instance variables. Those instance variables can then be used by the view.
There’s a bit of irony (and possible confusion for newcomers) in the choice of instance variables to share data between controllers and views. The main reason that instance variables exist is so that objects (whether Controller objects, String objects, and so on) can hold on to data that they don’t share with other objects. When your controller action is executed, everything is happening in the context of a controller object—an instance of, say, DemoController or EventController. Context includes the fact that every instance variable in the code belongs to the controller instance.
When the view template is rendered, the context is that of a different object, an instance of ActionView::Base. That instance has its own instance variables, and does not have access to those of the controller object.
So instance variables, on the face of it, are about the worst choice for a way for two objects to share data. However, it’s possible to make it happen—or make it appear to happen. What Rails does is to loop through the controller object’s variables and, for each one, create an instance variable for the view object, with the same name and containing the same data.
It’s kind of labor-intensive, for the framework: It’s like copying over a grocery list by hand. But the end result is that things are easier for you, the programmer. If you’re a Ruby purist, you might wince a little bit at the thought of instance variables serving to connect objects, rather than separate them. On the other hand, being a Ruby purist should also include understanding the fact that you can do lots of different things in Ruby—such as copying instance variables in a loop. So there’s nothing really un-Ruby-like about it. And it does provide a seamless connection, from the programmer’s perspective, between a controller and the template it’s rendering.
Stephen says… I’m a cranky old man, and dammit, Rails is wrong, wrong, wrong. Using instance variables to share data with the view sucks. If you want to see how my Decent Exposure library helps you avoid this horrible practice, skip ahead to Section “Decent Exposure” |
4.7 Action Callbacks
Action callbacks enable controllers to run shared pre and post processing code for its actions. These callbacks can be used to do authentication, caching, or auditing before the intended action is performed. Callback declarations are macro style class methods, that is, they appear at the top of your controller method, inside the class context, before method definitions. We also leave off the parentheses around the method arguments, to emphasize their declarative nature, like this:
before_action :require_authentication
As with many other macro-style methods in Rails, you can pass as many symbols as you want to the callback method:
before_action :security_scan, :audit, :compress
Or you can break them out into separate lines, like this:
before_action :security_scan
before_action :audit
before_action :compress
You should make your action callback methods protected or private; otherwise, they might be callable as public actions on your controller (via the default route).
Tim says… In addition to protected and private, one can declare a method should never be dispatched with the more intention-revealing hide_action. |
Importantly, action callbacks have access to request, response, and all the instance variables set by other callbacks in the chain or by the action (in the case of after callbacks). Action callbacks can set instance variables to be used by the requested action, and often do so.
4.7.1 Action Callback Inheritance
Controller inheritance hierarchies share action callbacks downward. Your average Rails application has an ApplicationController from which all other controllers inherit, so if you wanted to add action callbacks that are always run no matter what, that would be the place to do so.
classApplicationController < ActionController::Base
after_action :compress
Subclasses can also add and/or skip already defined action callbacks without affecting the superclass. For example, consider the two related classes in Listing 4.1, and how they interact.
Listing 4.1: A Pair of Cooperating before callbacks
1 classBankController < ActionController::Base
2 before_action :audit
3
4 protected
5
6 def audit
7 # record this controller's actions and parameters in an audit log
8 end
9
10 end
11
12 classVaultController < BankController
13 before_action :verify_credentials
14
15 protected
16
17 def verify_credentials
18 # make sure the user is allowed into the vault
19 end
20
21 end
Any actions performed on BankController (or any of its subclasses) will cause the audit method to be called before the requested action is executed. On the VaultController, first the audit method is called, followed by verify_credentials, because that’s the order in which the callbacks were specified. (Callbacks are executed in the class context where they’re declared, and the BankController has to be loaded before VaultController, since it’s the parent class.)
If the audit method happens to call render or redirect_to for whatever reason, verify_credentials and the requested action are never called. This is called halting the action callback chain.
4.7.2 Action Callback Types
An action callback can take one of three forms: method reference (symbol), external class, or block. The first is by far the most common and works by referencing a protected method somewhere in the inheritance hierarchy of the controller. In the bank example in Listing 4.1, bothBankController and VaultController use this form.
4.7.2.1 Action Callback Classes
Using an external class makes for more easily reused generic callbacks, such as output compression. External callback classes are implemented by having a static callback method on any class and then passing this class to the action callback method, as in Listing 4.2. The name of the class method should match the type of callback desired (eg: before, after, around).
Listing 4.2: An output compression action callback
1 classOutputCompressionActionCallback
2 defself.after(controller)
3 controller.response.body = compress(controller.response.body)
4 end
5 end
6
7 classNewspaperController < ActionController::Base
8 after_action OutputCompressionActionCallback
9 end
The method of the action callback class is passed the controller instance it is running in. It gets full access to the controller and can manipulate it as it sees fit. The fact that it gets an instance of the controller to play with also makes it seem like feature envy, and frankly, I haven’t had much use for this technique.
4.7.2.2 Inline Method
The inline method (using a block parameter to the action method) can be used to quickly do something small that doesn’t require a lot of explanation or just as a quick test.
1 classWeblogController < ActionController::Base
2 before_action do
3 redirect_to new_user_session_path unless authenticated?
4 end
5 end
The block is executed in the context of the controller instance, using instance_eval. This means that the block has access to both the request and response objects complete with convenience methods for params, session, template, and assigns.
4.7.3 Action Callback Chain Ordering
Using before_action and after_action appends the specified callbacks to the existing chain. That’s usually just fine, but sometimes you care more about the order in which the callbacks are executed. When that’s the case, you can use prepend_before_action and prepend_after_action. Callbacks added by these methods will be put at the beginning of their respective chain and executed before the rest, like the example in Listing 4.3.
Listing 4.3: An example of prepending before action callbacks
1 classShoppingController < ActionController::Base
2 before_action :verify_open_shop
3
4 classCheckoutController < ShoppingController
5 prepend_before_action :ensure_items_in_cart, :ensure_items_in_stock
The action callback chain for the CheckoutController is now :ensure_items_in_cart, :ensure_items_in_stock, :verify_open_shop. So if either of the ensure callbacks halts execution, we’ll never get around to seeing if the shop is open.
You may pass multiple action callback arguments of each type as well as a block. If a block is given, it is treated as the last argument.
4.7.4 Around Action Callbacks
Around action callbacks wrap an action, executing code both before and after the action that they wrap. They may be declared as method references, blocks, or objects with an around class method.
To use a method as an around_action, pass a symbol naming the Ruby method. Use yield within the method to run the action.
For example, Listing 4.4 has an around callback that logs exceptions (not that you need to do anything like this in your application; it’s just an example).
Listing 4.4: An around action callback to log exceptions
1 around_action :catch_exceptions
2
3 private
4
5 def catch_exceptions
6 yield
7 rescue => exception
8 logger.debug "Caught exception! #{exception}"
9 raise
10 end
To use a block as an around_action, pass a block taking as args both the controller and the action parameters. You can’t call yield from blocks in Ruby, so explicitly invoke call on the action parameter:
1 around_action do |controller, action|
2 logger.debug "before #{controller.action_name}"
3 action.call
4 logger.debug "after #{controller.action_name}"
5 end
To use an action callback object with around_action, pass an object responding to :around. With an action callback method, yield to the block like this:
1 around_action BenchmarkingActionCallback
2
3 classBenchmarkingActionCallback
4 defself.around(controller)
5 Benchmark.measure { yield }
6 end
7 end
4.7.5 Action Callback Chain Skipping
Declaring an action callback on a base class conveniently applies to its subclasses, but sometimes a subclass should skip some of the action callbacks it inherits from a superclass:
1 classApplicationController < ActionController::Base
2 before_action :authenticate
3 around_action :catch_exceptions
4 end
5
6 classSignupController < ApplicationController
7 skip_before_action :authenticate
8 end
9
10 classHackedTogetherController < ApplicationController
11 skip_action_callback :catch_exceptions
12 end
4.7.6 Action Callback Conditions
Action callbacks may be limited to specific actions by declaring the actions to include or exclude, using :only or :except options. Both options accept single actions (like only: :index) or arrays of actions (except: [:foo, :bar]).
1 classJournal < ActionController::Base
2 before_action :authorize, only: [:edit, :delete]
3
4 around_action except: :index do |controller, action_block|
5 results = Profiler.run(&action_block)
6 controller.response.sub! "</body>", "#{results}</body>"
7 end
8
9 private
10
11 def authorize
12 # Redirect to login unless authenticated.
13 end
14 end
4.7.7 Action Callback Chain Halting
The before_action and around_action methods may halt the request before the body of a controller action method is run. This is useful, for example, to deny access to unauthenticated users. As mentioned earlier, all you have to do to halt the before action chain is call render or redirect_to. After action callbacks will not be executed if the before action chain is halted.
Around action callbacks halt the request unless the action block is called. If an around action callback returns before yielding, it is effectively halting the chain and any after action callbacks will not be run.
4.8 Streaming
Rails has built-in support for streaming binary content back to the requesting client, as opposed to its normal duties rendering view templates.
4.8.1 ActionController::Live
Being introduced in Rails 4 is the ActionControler::Live module, a controller mixin that enables the controller actions to stream on-the-fly generated data to the client.
The ActionController::Live mixin adds an I/O like interface object named stream to the response object. Using stream, one can call write, to immediately stream data to the client, and close to explicitly close the stream. The response object is equivalent to the what you’d expect in the context of the controller, and can be used to control various things in the HTTP response, such as the Content-Type header.
The following example demonstrates how one can stream a large amount of on-the-fly generated data to the browser:
1 classStreamingController < ApplicationController
2 include ActionController::Live
3
4 # Streams about 180 MB of generated data to the browser.
5 def stream
6 10_000_000.times do |i|
7 response.stream.write "This is line #{i}\n"
8 end
9 ensure
10 response.stream.close
11 end
12 end
When using live streaming, there are a couple of things to take into consideration:
· All actions executed from ActionController::Live enabled controllers are run in a separate thread. This means the controller action code being executed must be threadsafe.
· A concurrent Ruby web server, such as puma13, is required to take advantage of live streaming.
· Headers must be added to the response before anything is written to the client.
· Streams must be closed once finished, otherwise a socket may be left open indefinitely.
For an interesting perspective on why live streaming was added into Rails, and how to utilize it to serve Server-Sent Events, make sure to read Aaron Patterson’s blog post on the subject.
4.8.2 View streaming via render stream: true
By default, when a view is rendered in Rails, it first renders the template, and then the layout of the view. When returning a response to a client, all required Active Record queries are run, and the entire rendered template is returned.
Introduced in version 3.1, Rails added support to stream views to the client. This allows for views to be rendered as they are processed, including only running Active Record scoped queries when they are needed. To achieve this, Rails reverses the ordering that views are rendered. The layout is rendered first to the client, and then each part of the template is processed.
To enable view streaming, pass the option stream to the render method.
1 classEventController < ActionController::Base
2 def index
3 @events = Events.all
4 render stream: true
5 end
6 end
This approach can only be used to render templates. To render other types of data, such as JSON, take a look at the Section “ActionController::Live”.
Rails also supports sending buffers and files with two methods in the ActionController::Streaming module: send_data and send_file.
4.8.3 send_data(data, options = {})
The send_data method allows you to send textual or binary data in a buffer to the user as a named file. You can set options that affect the content type and apparent filename, and alter whether an attempt is made to display the data inline with other content in the browser or the user is prompted to download it as an attachment.
4.8.3.1 Options
The send_data method has the following options:
:filename
Suggests a filename for the browser to use.
:type
Specifies an HTTP content type. Defaults to 'application/octet-stream'.
:disposition
Specifies whether the file will be shown inline or downloaded. Valid values are inline and attachment (default).
:status
Specifies the status code to send with the response. Defaults to '200 OK'.
4.8.3.2 Usage Examples
Creating a download of a dynamically generated tarball:
send_data my_generate_tarball_method('dir'), filename: 'dir.tgz'
Sending a dynamic image to the browser, like for instance a captcha system:
1 require 'RMagick'
2
3 classCaptchaController < ApplicationController
4
5 def image
6 # create an RMagic canvas and render difficult to read text on it
7 ...
8
9 img = canvas.flatten_images
10 img.format = "JPG"
11
12 # send it to the browser
13 send_data img.to_blob, disposition: 'inline', type: 'image/jpg'
14 end
15 end
4.8.4 send_file(path, options = {})
The send_file method sends an existing file down to the client using Rack::Sendfile middleware, which intercepts the response and replaces it with a webserver specific X-Sendfile header. The web server then becomes responsible for writing the file contents to the client instead of Rails. This can dramatically reduce the amount of work accomplished in Ruby and takes advantage of the web servers optimized file delivery code.14
4.8.4.1 Options
Here are the options available for send_file:
:filename
Suggests a filename for the browser to use. Defaults to File.basename(path)
:type
Specifies an HTTP content type. Defaults to 'application/octet-stream'.
:disposition
Specifies whether the file will be shown inline or downloaded. Valid values are 'inline' and 'attachment' (default).
:status
Specifies the status code to send with the response. Defaults to '200 OK'.
:url_based_filename
Should be set to true if you want the browser to guess the filename from the URL, which is necessary for i18n filenames on certain browsers (setting :filename overrides this option).
There’s also a lot more to read about Content-* HTTP headers15 if you’d like to provide the user with additional information that Rails doesn’t natively support (such as Content-Description).
4.8.4.2 Security Considerations
Note that the send_file method can be used to read any file accessible to the user running the Rails server process, so be extremely careful to sanitize16 the path parameter if it’s in any way coming from untrusted users.
If you want a quick example, try the following controller code:
1 classFileController < ActionController::Base
2 def download
3 send_file(params[:path])
4 end
5 end
Give it a route
get 'file/download' => 'file#download'
then fire up your server and request any file on your system:
$ curl http://localhost:3000/file/download?path=/etc/hosts
##
# 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
Courtenay says… There are few legitimate reasons to serve static files through Rails. Unless you are protecting content, I strongly recommend you cache the file after sending it. There are a few ways to do this. Since a correctly configured web server will serve files in public/ and bypass rails, the easiest is to just copy the newly generated file to the public directory after sending it: 1 public_dir = File.join(Rails.root, 'public', controller_path) 2 FileUtils.mkdir_p(public_dir) 3 FileUtils.cp(filename, File.join(public_dir, filename)) |
All subsequent views of this resource will be served by the web server.
4.8.4.3 Usage Examples
Here’s the simplest example, just a simple zip file download:
send_file '/path/to.zip'
Sending a JPG to be displayed inline requires specification of the MIME content-type:
send_file '/path/to.jpg',
type: 'image/jpeg',
disposition: 'inline'
This will show a 404 HTML page in the browser. We append a charset declaration to the MIME type information:
send_file '/path/to/404.html,
type: 'text/html; charset=utf-8',
status: 404
How about streaming an FLV file to a browser-based Flash video player?
send_file @video_file.path,
filename: video_file.title + '.flv',
type: 'video/x-flv',
disposition: 'inline'
Regardless of how you do it, you may wonder why you would need a mechanism to send files to the browser anyway, since it already has one built in—requesting files from the public directory. Well, many times a web application will front files that need to be protected from public access. (It’s a common requirement for membership-based adult websites.)
4.9 Variants
New to Rails 4.1, Action Pack variants add the ability to render different HTML, JSON, and XML templates based on some criteria. To illustrate, assuming we have an application that requires specific templates to be rendered for iPhone devices only, we can set a request variant in abefore_action callback.
1 classApplicationController < ActionController::Base
2 before_action :set_variant
3
4 protected
5
6 def set_variant
7 request.variant = :mobile if request.user_agent =~ /iPhone/i
8 end
9 end
Note
Note that request.variant can be set based on any arbitrary condition, such as the existence of certain request headers, subdomain, current user, API version, etc.
Next, in a controller action, we can explicitly respond to variants like any other format. This includes the ability to execute code specific to the format by supplying a block to the declaration.
1 classPostsController < ApplicationController
2 def index
3 ...
4 respond_to do |format|
5 format.html do |html|
6 html.mobile do # renders app/views/posts/index.html+mobile.haml
7 @mobile_only_variable = true
8 end
9 end
10 end
11 end
12 end
By default, if no respond_to block is declared within your action, Action Pack will automatically render the correct variant template if one exists in your views directory.
Variants are a powerful new feature in Action Pack that can be utilized for more than just rendering views based on a user agent. Since a variant can be set based on any condition, it can be utilized for a variety of use cases, such as rolling out features to a certain group of application users, or even A/B testing a template.
4.10 Conclusion
In this chapter, we covered some concepts at the very core of how Rails works: the dispatcher and how controllers render views. Importantly, we covered the use of controller action callbacks, which you will use constantly, for all sorts of purposes. The Action Controller API is fundamental knowledge, which you need to understand well along your way to becoming an expert Rails programmer.
Moving on, we’ll leave Action Pack and head over to the other major component API of Rails: Active Record.