Remove all Business Logic from your Controllers and put it in the model. Your Controllers are only responsible for mapping between URLs (including other HTTP Request data), coordinating with your Models and your Views, and channeling that back to an HTTP response. Along the way, Controllers may do Access Control, but little more. These instructions are precise, but following them requires intuition and subtle reasoning. | ||
--Nick Kallen, Pivotal Labs http://www.pivotalblabs.com/articles/2007/07/16/the-controller-formula |
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. Yes, there are 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 first, 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 pull information out of the database (generally through the use of the ActiveRecord
interface), and they make that information 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. Views and controllers, however, are more tightly coupled. They share a lot of information, mainly through instance variables. That means that 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: filters and streaming.
Rails is used to build web-based applications, so before anything else happens, and for anything that does happen, a web server—Apache, Lighttpd, Nginx, and so on—handles a request. The server then forwards that request to the Rails application, where it is handled by the dispatcher.
As the request is handled, the server passes off some information to the dispatcher, principally
The request URI (http://localhost:3000/timesheets/show/3, or whatever)
The CGI environment (bindings of CGI parameter names to values)
The dispatcher’s job is to
Figure out which controller is involved in the request
Figure out which action should be executed
Load the appropriate controller file, which will contain a Ruby class definition for a controller class (TimesheetsController
, for example)
Create an instance of the controller class
Tell that instance to execute the appropriate action
All of this happens quickly, behind the scenes. It’s unlikely that you would ever need to dig into the source code for the dispatcher; 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.
For instructional purposes let’s trigger the Rails dispatching mechanism manually. It will give you a good feel for the flow of program control in Rails.
We’ll do this little exercise from the ground up, starting with a new Rails application:
$ rails dispatch_me
Now, create a single controller, with an index
action:
$ cd dispatch_me/ $ ruby ./script/generate controller demo index
If you look at the controller you just generated, in app/controllers/demo_controller.rb
, you’ll see that it has an index
action:
class DemoController < ApplicationController def index end end
There’s also a view template file, app/views/demo/index.rhtml
, corresponding to the action, and also created automatically, courtesy of the generate
script. That template file contains some placeholder language. Just to see things more clearly, let’s replace it with something we’ll definitely recognize when we see it again. Delete the lines in index.rhtml
and enter the following:
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. Type ruby script/console
from a command prompt:
$ ruby script/console Loading development environment. >>
We’re now inside the beating heart of a Rails application and it’s waiting to receive instructions.
There are a pair of environment variables that would normally be set by the web server passing the request to the Rails dispatcher. Since we’re going to be invoking the dispatcher manually, we have to set those environment variables manually:
>> ENV['REQUEST_URI'] = "/demo/index" => "/demo/index" >> ENV['REQUEST_METHOD'] = "get" => "get"
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 web server.
Here’s the command:
>> Dispatcher.dispatch
And here’s the response from the Rails application:
Content-Type: text/html; charset=utf-8 Set-Cookie: _dispatch_me_session_id=336c1302296ab4fa1b0d838d; path=/ Status: 200 OK Cache-Control: no-cache Content-Length: 7 Hello!
We’ve executed the dispatch
class method of the Ruby class Dispatcher
, 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. And that’s exactly what happens. Have a look in the public
subdirectory of dispatch_me
(or any other Rails application). Among other things, you’ll see these dispatcher files:
$ ls dispatch.* dispatch.cgi dispatch.fcgi dispatch.rb
Every time a Rails request comes in, the web server hands control to one of those files. Which file depends on the exact server configuration. Ultimately, they all do the same thing: They call Dispatcher.dispatch
, just as you did from the console.
You can follow the trail of bread crumbs even further, if you look at public/.htaccess
and your server configuration. 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.
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:
class DemoController < ApplicationController end
Don’t delete app/views/demo/index.rhtml
, and then try the console exercise (Dispatcher.dispatch
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 ActiveRecord
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.
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.rhtml
. 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:
class DemoController < 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. In this case, that means app/views/
demo
/index
.rhtml
.
In other words, every controller action has an implicit render
command in it. And render
is actually a real method. You could write the preceding example like this:
def index render :template => "demo/index" end
You don’t have to, though, because it’s assumed that that’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 that could simply be assumed by convention.
The render
command, however, does more than just provide a way of telling Rails to do what it was going to do anyway.
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.
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:
class EventController < ActionController::Base def new # This (empty) action renders the new.rhtml template, which # contains the form for inputting information about the new # event record and is not actually needed. end def create # This method processes the form input. The input is available via # the params hash, in the nested hash hanging off the :event key. @event = Event.new(params[:event]) if @event.save flash[:notice] = "Event created!" redirect_to :controller => "main" # ignore this line for now else render :action => "new" # doesn't execute the new method! end end end
On failure, that is, if @event.save
does not return true
, we render the "new"
template, new.rhtml
, again. Assuming new.rhtml
has been written correctly, this will automatically include the display of error information embedded in the new (but unsaved) Event
object, @event
.
Note that the template itself, new.rhtml
, doesn’t “know” that it’s 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.
In a similar fashion, if you are rendering a template for a different action, it is possible to render any template in the system by calling render
with either a :template
or :file
option pointing to the desired template file.
The :template
option takes a path relative to the template root (app/views
, unless you changed it, which would be extremely unusual), whereas :file
takes an absolute filesystem path.
Admittedly, the :template
option is rarely used by the majority of Rails developers.
render :template => "abuse/report" # renders app/views/abuse/report.rhtml render :file => "/railsapps/myweb/app/views/templates/common.rhtml"
Another option is to render a partial template (usually referred to simply as a “partial”). In general, usage of partial templates allows you to organize your template code into small files, which helps you to avoid clutter and encourages you to break your template code up into reusable modules.
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, “ActionView
.”
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.
One common use case for inline rendering, and probably the only reason it was introduced to begin with, is when using one of the Rails AJAX view helpers, such as auto_complete_result
(covered in Chapter 12, “Ajax on Rails”).
render :inline => "<%= auto_complete_result(@headings, 'name') %>"
Rails treats the inline code exactly as if it were a view template.
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'
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.
JSON[1] 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. ActiveRecord
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.to_json
ActiveRecord
also has built-in support for conversion to XML, as in the following example:
render :xml => @record.to_xml
We cover XML-related topics like this one extensively in Chapter 15, “XML and ActiveResource
.”
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.)
render :nothing => true, :status => 401 # Unauthorized
It’s worth noting that, as illustrated in this snippet, render :nothing => true
is often used in conjunction with an HTTP status code (as covered in the next section, “Rendering Options”).
Most calls to the render
method accept additional options. Here they are in alphabetical order.
All content flying around the web is associated with a MIME type[2]. 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.
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, “ActionView
.” The :layout
option allows you to specify whether you want a layout template to be rendered or not.
The HTTP protocol includes many standard status codes[3] 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 2.1 demonstrates a couple of codes that I’ve occasionally found useful in my day-to-day Rails programming.
Table 2.1. Common HTTP Status Codes
Status Code | Description |
---|---|
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 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 their authentication and/or authorization will fail. Assuming using a Basic or Digest HTTP Authentication scheme, when that happens you should probably return a |
403 Forbidden The server understood the request, but is refusing to fulfill it. | I like to use 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 def index return render :nothing => true, :status => 403 unless logged_in? @favorites = current_user.favorites.find(:all) end |
404 Not Found The server cannot find the resource you requested. | You may choose to use 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 |
503 Service Unavailable The server is temporarily unavailable. | The 503 code comes in very handy when taking a site down for maintenance, particularly when upgrading RESTful web services. One of this book’s reviewers, Susan Potter, shares the following suggestion:
|
The life cycle of a Rails application is divided into requests. Every time there’s a new request, we’re starting again.
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 initiating a new one.
Look again at the example of the form-handling create
method:
def create @event = Event.new(params[:event]) if @event.save flash[:notice] = "Event created!" redirect_to :controller => "main" else render :action => "new" end 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 (not specified, but that’s the default) of the main controller.
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. Why not just render the main/index.rhtml
template?
if @event.save flash[:notice] = "Event created!" render :controller => "main", :action => "index" ...
The result of this would be that main/index.rhtml
template would, indeed, be rendered. But there are some pitfalls. For instance, let’s say that the main/index
action looks like this:
def index @events = Event.find(:all) end
If you render the index.rhtml
from the event/create
action, the main/index
action will not be executed. So @events
won’t be initialized. That means that index.rhtml
will blow up, because (presumably) it’s planning to make use of @events
:
<h1>Schedule Manager</h1>
<p>Here are your current events:</p>
<% @events.each do |event| %>
some kind of display HTML would go here
<% end %>
That’s why you have to redirect to main/index
, instead of just borrowing its template. The redirect_to
command clears the decks: It creates a new request, triggers a new action, and starts from scratch in deciding what to render.
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,” here, 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.
Filters enable controllers to run shared pre- and post-processing code for its actions. These filters can be used to do authentication, caching, or auditing before the intended action is performed. Filter methods are macro-style, 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_filter :require_authentication
As with many other macro-style methods in Rails, you can pass as many symbols as you want to the filter method:
before_filter :security_scan, :audit, :compress
Or you can break them out into separate lines, like this:
before_filter :security_scan before_filter :audit before_filter :compress
In contrast to the somewhat similar callback methods of ActiveRecord
, you can’t implement a filter method on a controller by adding a method named before_filter
or after_filter
.
You should make your filter methods protected
or private
; otherwise, they might be callable as public actions on your controller (via the default route).
Importantly, filters have access to the request, response, and all the instance variables set by other filters in the chain or by the action (in the case of after
filters). Filters can set instance variables to be used by the requested action, and often do so.
Controller inheritance hierarchies share filters downward. Your average Rails application has an ApplicationController
from which all other controllers inherit, so if you wanted to add filters that are always run no matter what, that would be the place to do so.
class ApplicationController < ActionController::Base after_filter :compress
Subclasses can also add and/or skip already defined filters without affecting the superclass. For example, consider the two related classes in Listing 2.1, and how they interact.
Example 2.1. A Pair of Cooperating before
Filters
class BankController < ActionController::Base before_filter :audit private def audit # record this controller's actions and parameters in an audit log end end class VaultController < BankController before_filter :verify_credentials private def verify_credentials # make sure the user is allowed into the vault end 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 the verify_credentials
method, because that’s the order in which the filters were specified. (Filters 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 return false
for whatever reason, verify_credentials
and the requested action are never called. This is called halting the filter chain and when it happens, if you look in your development log, you’ll see a message to the effect that such-and-such a filter halted request processing.
A filter can take one of three forms: method reference (symbol), external class, or inline method (proc). The first is by far the most common and works by referencing a protected or private method somewhere in the inheritance hierarchy of the controller. In the bank example in Listing 2.1, both BankController
and VaultController
use this form.
Using an external class makes for more easily reused generic filters, such as output compression. External filter classes are implemented by having a static filter method on any class and then passing this class to the filter method, as in Listing 2.2.
The self.filter
method of the Filter
class is passed the controller instance it is filtering, which gives it access to all aspects of the controller and can manipulate them as it sees fit.
The inline method (using a block parameter to the filter method) can be used to quickly do something small that doesn’t require a lot of explanation, or just as a quick test. It works like this:
class WeblogController < ActionController::Base before_filter {|controller| false if controller.params["stop"]} end
As you can see, the block expects to be passed the controller after it has assigned the request to the internal variables. This means that the block has access to both the request and response objects complete with convenience methods for params, session, template, and assigns. Note that the inline method doesn’t strictly have to be a block—any object that responds to call
such as a Proc
or an Method
object will do.
Around
filters behave a little differently than normal before
and after
filters with regard to filter types. The section dedicated to around_filters
elaborates on the topic.
Using before_filter
and after_filter
appends the specified filters to the existing chain. That‘s usually just fine, but sometimes you care more about the order in which the filters are executed. When that’s the case, you can use prepend_before_filter
and prepend_after_filter
. Filters added by these methods will be put at the beginning of their respective chain and executed before the rest, like the example in Listing 2.3.
The filter chain for the CheckoutController
is now :ensure_items_in_cart
, :ensure_items_in_stock
, :verify_open_shop
. So if either of the ensure
filters returns false
, we’ll never get around to seeing if the shop is open or not; the filter chain will be halted.
You may pass multiple filter arguments of each type as well as a filter block. If a block is given, it is treated as the last argument.
Around
filters wrap an action, executing code both before and after the action that they wrap. They may be declared as method references, blocks, or objects responding to filter or to both before
and after
.
To use a method as an around_filter
, pass a symbol naming the Ruby method. Use yield
(or block.call
) within the method to run the action.
For example, Listing 2.4 has an around
filter that logs exceptions (not that you need to do anything like this in your application; it’s just an example).
To use a block as an around_filter
, pass a block taking as args both the controller and the action block. You can’t call yield
directly from an around_filter
block; explicitly call the action block instead:
around_filter do |controller, action| logger.debug "before #{controller.action_name}" action.call logger.debug "after #{controller.action_name}" end
To use a filter object with around_filter
, pass an object responding to :filter
or both :before
and :after
. With a filter method, yield to the block like this:
around_filter BenchmarkingFilter class BenchmarkingFilter def self.filter(controller, &block) Benchmark.measure(&block) end end
A filter object with before
and after
methods is peculiar in that you must explicitly return true
from the before
method if you want the after
method to run.
around_filter Authorizer class Authorizer # This will run before the action. Returning false aborts the action def before(controller) if user.authorized? return true else redirect_to login_url return false end end def after(controller) # runs after the action only if the before returned true end end
Declaring a filter on a base class conveniently applies to its subclasses, but sometimes a subclass should skip some of the filters it inherits from a superclass:
class ApplicationController < ActionController::Base before_filter :authenticate around_filter :catch_exceptions end class SignupController < ApplicationController skip_before_filter :authenticate end class ProjectsController < ApplicationController skip_filter :catch_exceptions end
Filters may be limited to specific actions by declaring the actions to include or exclude. Both options accept single actions (like :only => :index
) or arrays of actions (:except => [:foo, :bar]
).
class Journal < ActionController::Base before_filter :authorize, :only => [:edit, :delete] around_filter :except => :index do |controller, action_block| results = Profiler.run(&action_block) controller.response.sub! "</body>", "#{results}</body>" end private def authorize # Redirect to login unless authenticated. end end
The before_filter
and around_filter
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 before, all you have to do to halt the filter chain is to return false
from the filter. Calling render
or redirect_to
will also halt the filter chain.
After
filters will not be executed if the filter chain is halted. Around
filters halt the request unless the action block is called.
If an around
filter returns before yielding, it is effectively halting the chain and any after
filters will not be run.
If a before
filter returns false, the second half of any around
filters will still run, but the action method itself will not run, and neither will any after
filters.
It’s a little-known fact that Rails has some built-in support for streaming binary content back to the browser, instead of rendering view templates. Streaming comes in handy whenever you need to send dynamically generated files to the browser (e.g., images, PDF files) and Rails supports it with two methods in the ActionController::Streaming module
: send_data
and send_file
.
One of these methods is useful, but the other one should not be used under almost any circumstance. Let’s cover the useful one first.
The send_data
method allows you to send textual or binary data 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.
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'
.
Creating a download of a dynamically generated tarball might look like this:
send_data generate_tgz('dir'), :filename => 'dir.tgz'
Listing 2.5 has an example of sending a dynamic image to the browser—it’s a partial implementation of a captcha system, used to prevent malicious bots from abusing your web application.
Example 2.5. A Captcha Controller Using RMagick
and send_data
require 'RMagick' class CaptchaController < ApplicationController def image # create an RMagic canvas and render difficult to read text on it ... image = canvas.flatten_images image.format = "JPG" # send it to the browser send_data(image.to_blob, :disposition => 'inline', :type => 'image/jpg') end end
The send_file
method streams a file 4096 bytes at a time down to the client. The API docs say, “This way the whole file doesn’t need to be read into memory at once, which makes it feasible to send even very large files.”
Unfortunately, that isn’t true. When you use send_file
in a Rails app that runs on Mongrel, which most people do nowadays, the whole file is indeed read into memory! Therefore, using send_file
to send big files will give you big headaches. The following section discusses how you can get your web server to serve files directly.
In case you do decide to use send_file
(and don’t say I didn’t warn you), here are the options that it understands:
: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).
:stream
specifies whether to send the file to the user agent as it is read (true
) or to read the entire file before sending (false
). Defaults to true
.
:buffer_size
specifies size (in bytes) of the buffer used to stream the file. Defaults to 4096.
: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).
Most of these options are processed and set on the response object by the private method send_file_headers!
of the ActionController::Streaming
module, so if you’re using the web server to send files, you might want to crack open the Rails source code and take a look at it. There’s also a lot more to read about the other Content-* HTTP headers[5] if you’d like to provide the user with more information that Rails doesn’t natively support (such as Content-Description
).
Finally, be aware that the document may be cached by proxies and browsers. The Pragma and Cache-Control headers declare how the file may be cached by intermediaries. They default to require clients to validate with the server before releasing cached responses.[6]
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'
The solution to the memory-consumption problems inherent to send_file
is to leverage functionality provided by Apache, Lighttpd, and Nginx that allows you to serve files directly from the web server, even if they’re not in a public document directory. The technique works by setting a special HTTP request header with the path to the file you want the web server to send along to the client.
Here’s how you do it in Apache and Lighttpd:
response.headers['X-Sendfile'] = path
And here’s how you do it with Nginx:
response.headers['X-Accel-Redirect'] = path
In both cases, you want to end your controller action method by telling Rails to not bother sending anything, since the web server will handle it.
render :nothing => true
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, lots of times a web application will front files that need to be protected from public access.[7] (That’s practically every porn site in existence!)
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 filters, which you will use constantly, for all sorts of purposes. The ActionController
API is fundamental knowledge, which you need to understand well along your way to becoming an expert Rails programmer.
Moving on, we’ll continue with another subject that is closely related to the dispatcher and controllers, in fact it’s how Rails figures out how to respond to requests: the Routing system.
1. | For more information on JSON go to http://www.json.org/. |
2. | MIME is specified in five RFC documents, so it is much more convenient to point you to a rather good description of MIME provided by Wikipedia at http://en.wikipedia.org/wiki/MIME. |
3. | For a full list of HTTP status codes, consult the spec at http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html. |
4. | Heiko Webers has the best write-up about sanitizing filenames at http://www.rorsecurity.info/2007/03/27/working-with-files-in-rails/. |
5. | See the official spec at http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html. |
6. | See http://www.mnot.net/cache_docs/ for an overview of web caching. |
7. | Ben Curtis writes up an excellent approach to securing downloads at http://www.bencurtis.com/archives/2006/11/serving-protected-downloads-with-rails/. |
3.145.191.134