Chapter 2. Working with Controllers

 

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.

The Dispatcher: Where It All Begins

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.

Request Handling

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.

Getting Intimate with the Dispatcher

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.

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:

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.

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.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.

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.

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:

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.

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 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"

Rendering a Partial Template

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.”

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.

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.

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'

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.

:json

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

:xml

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.”

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.)

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”).

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[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.

: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, “ActionView.” The :layout option allows you to specify whether you want a layout template to be rendered or not.

:status

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 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 their 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 The server understood the request, but is refusing to fulfill it.

I like to use 403 in conjunction with a short render :text message in situations where the client has requested a resource that is not 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.find(:all)
end

404 Not Found

The server cannot find the resource you requested.

You may choose to use 404 when a resource of a specific given ID does not exist in your 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 acts_as_paranoid and we know that the resource used to exist in the past, we could respond with 410 Gone.

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:

For my projects, I create a stub Rails application that responds with a 503 for each valid type of request that comes in. Clients of my services are usually services themselves or other applications, so this helps client developers that consume my web services know that this is a temporary blip and should be due to scheduled maintenance (and a good reminder for them to check the emails I sent them over the weekend instead of ignoring them).

Redirecting

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.

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,” 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

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.

Filter Inheritance

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.

Filter Types

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.

Filter Classes

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.

Example 2.2. An Output Compression Filter

class OutputCompressionFilter
  def self.filter(controller)
    controller.response.body = compress(controller.response.body)
  end
end

class NewspaperController < ActionController::Base
  after_filter OutputCompressionFilter
end

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.

Inline Filter Method

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.

Filter Chain Ordering

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.

Example 2.3. An Example of Prepending before Filters

class ShoppingController < ActionController::Base
  before_filter :verify_open_shop

class CheckoutController < ShoppingController
  prepend_before_filter :ensure_items_in_cart, :ensure_items_in_stock

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

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).

Example 2.4. An around Filter to Log Exceptions

around_filter :catch_exceptions

private

  def catch_exceptions
    yield
  rescue => exception
    logger.debug "Caught exception! #{exception}"
    raise
  end

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

Filter Chain Skipping

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

Filter Conditions

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

Filter Chain Halting

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.

Streaming

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.

send_data(data, options = {})

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.

Options for send_data

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'.

Usage Examples

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

send_file(path, options = {})

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.

Options for send_file

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]

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'

Letting the Web Server Send Files

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!)

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 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.

References

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/.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.84.169