Processing of Requests

In the previous section, we worked out how Action Dispatch routes an incoming request to the appropriate code in your application. Now let’s see what happens inside that code.

Action Methods

When a controller object processes a request, it looks for a public instance method with the same name as the incoming action. If it finds one, that method is invoked. If it doesn’t find one and the controller implements method_missing, that method is called, passing in the action name as the first parameter and an empty argument list as the second. If no method can be called, the controller looks for a template named after the current controller and action. If found, this template is rendered directly. If none of these things happens, an AbstractController::ActionNotFound error is generated.

Controller Environment

The controller sets up the environment for actions (and, by extension, for the views that they invoke). Many of these methods provide direct access to information contained in the URL or request:

action_name

The name of the action currently being processed.

cookies

The cookies associated with the request. Setting values into this object stores cookies on the browser when the response is sent. Rails support for sessions is based on cookies. We discuss sessions in Rails Sessions.

headers

A hash of HTTP headers that will be used in the response. By default, Cache-Control is set to no-cache. You might want to set Content-Type headers for special-purpose applications. Note that you shouldn’t set cookie values in the header directly—use the cookie API to do this.

params

A hash-like object containing request parameters (along with pseudoparameters generated during routing). It’s hash-like because you can index entries using either a symbol or a string—params[:id] and params[’id’] return the same value. Idiomatic Rails applications use the symbol form.

request

The incoming request object. It includes these attributes:

  • request_method returns the request method, one of :delete, :get, :head, :post, or :put.

  • method returns the same value as request_method except for :head, which it returns as :get because these two are functionally equivalent from an application point of view.

  • delete?, get?, head?, post?, and put? return true or false based on the request method.

  • xml_http_request? and xhr? return true if this request was issued by one of the Ajax helpers. Note that this parameter is independent of the method parameter.

  • url, which returns the full URL used for the request.

  • protocol, host, port, path, and query_string, which return components of the URL used for the request, based on the following pattern: protocol://host:port/path?query_string.

  • domain, which returns the last two components of the domain name of the request.

  • host_with_port, which is a host:port string for the request.

  • port_string, which is a :port string for the request if the port is not the default port (80 for HTTP, 443 for HTTPS).

  • ssl?, which is true if this is an SSL request; in other words, the request was made with the HTTPS protocol.

  • remote_ip, which returns the remote IP address as a string. The string may have more than one address in it if the client is behind a proxy.

  • env, the environment of the request. You can use this to access values set by the browser, such as this:

     request.​env​[​'HTTP_ACCEPT_LANGUAGE'​]
  • accepts, which is an array with Mime::Type objects that represent the MIME types in the Accept header.

  • format, which is computed based on the value of the Accept header, with Mime[:HTML] as a fallback.

  • content_type, which is the MIME type for the request. This is useful for put and post requests.

  • headers, which is the complete set of HTTP headers.

  • body, which is the request body as an I/O stream.

  • content_length, which is the number of bytes purported to be in the body.

Rails leverages a gem named Rack to provide much of this functionality. See the documentation of Rack::Request for full details.

response

The response object, filled in during the handling of the request. Normally, this object is managed for you by Rails. As we’ll see when we look at callbacks in Callbacks, we sometimes access the internals for specialized processing.

session

A hash-like object representing the current session data. We describe this in Rails Sessions.

In addition, a logger is available throughout Action Pack.

Responding to the User

Part of the controller’s job is to respond to the user. There are basically four ways of doing this:

  • The most common way is to render a template. In terms of the MVC paradigm, the template is the view, taking information provided by the controller and using it to generate a response to the browser.

  • The controller can return a string directly to the browser without invoking a view. This is fairly rare but can be used to send error notifications.

  • The controller can return nothing to the browser. This is sometimes used when responding to an Ajax request. In all cases, however, the controller returns a set of HTTP headers, because some kind of response is expected.

  • The controller can send other data to the client (something other than HTML). This is typically a download of some kind (perhaps a PDF document or a file’s contents).

A controller always responds to the user exactly one time per request. This means you should have just one call to a render, redirect_to, or send_xxx method in the processing of any request. (A DoubleRenderError exception is thrown on the second render.)

Because the controller must respond exactly once, it checks to see whether a response has been generated just before it finishes handling a request. If not, the controller looks for a template named after the controller and action and automatically renders it. This is the most common way that rendering takes place. You may have noticed that in most of the actions in our shopping cart tutorial we never explicitly rendered anything. Instead, our action methods set up the context for the view and return. The controller notices that no rendering has taken place and automatically invokes the appropriate template.

You can have multiple templates with the same name but with different extensions (for example, .html.erb, .xml.builder, and .js.erb). If you don’t specify an extension in a render request, Rails assumes html.erb.

Rendering Templates

A template is a file that defines the content of a response for our application. Rails supports three template formats out of the box: erb, which is embedded Ruby code (typically with HTML); builder, a more programmatic way of constructing XML content; and RJS, which generates JavaScript. We’ll talk about the contents of these files starting in Using Templates.

By convention, the template for action action of controller controller will be in the file app/views/controller/action.type.xxx (where type is the file type, such as html, atom, or js; and xxx is one of erb, builder or scss). The app/views part of the name is the default. You can override this for an entire application by setting this:

ActionController.prepend_view_path dir_path

The render method is the heart of all rendering in Rails. It takes a hash of options that tell it what to render and how to render it.

It’s tempting to write code in our controllers that looks like this:

 # DO NOT DO THIS
 def​ ​update
  @user = User.​find​(params[​:id​])
 if​ @user.​update​(user_params)
  render ​action: ​show
 end
  render ​template: ​​"fix_user_errors"
 end

It seems somehow natural that the act of calling render (and redirect_to) should somehow terminate the processing of an action. This is not the case. The previous code will generate an error (because render is called twice) in the case where update succeeds.

Let’s look at the render options used in the controller here (we’ll look separately at rendering in the view starting in Partial-Page Templates):

render()

With no overriding parameter, the render method renders the default template for the current controller and action. The following code will render the template app/views/blog/index.html.erb:

 class​ BlogController < ApplicationController
 def​ ​index
  render
 end
 end

So will the following (as the default behavior of a controller is to call render if the action doesn’t):

 class​ BlogController < ApplicationController
 def​ ​index
 end
 end

And so will this (because the controller will call a template directly if no action method is defined):

 class​ BlogController < ApplicationController
 end
render(text: string)

Sends the given string to the client. No template interpretation or HTML escaping is performed.

 class​ HappyController < ApplicationController
 def​ ​index
  render(​text: ​​"Hello there!"​)
 end
 end
render(inline: string, [ type: "erb"|"builder"|"scss" ], [ locals: hash] )

Interprets  string as the source to a template of the given type, rendering the results back to the client. You can use the :locals hash to set the values of local variables in the template.

The following code adds method_missing to a controller if the application is running in development mode. If the controller is called with an invalid action, this renders an inline template to display the action’s name and a formatted version of the request parameters:

 class​ SomeController < ApplicationController
 
 if​ RAILS_ENV == ​"development"
 def​ ​method_missing​(name, *args)
  render(​inline: ​​%{
  <h2>Unknown action: #{name}</h2>
  Here are the request parameters:<br/>
  <%= debug(params) %> }​)
 end
 end
 end
render(action: action_name)

Renders the template for a given action in this controller. Sometimes folks use the :action form of render when they should use redirects. See the discussion starting in Redirects, for why this is a bad idea.

 def​ ​display_cart
 if​ @cart.​empty?
  render(​action: :index​)
 else
 # ...
 end
 end

Note that calling render(:action...) does not call the action method; it simply displays the template. If the template needs instance variables, these must be set up by the method that calls the render method.

Let’s repeat this, because this is a mistake that beginners often make: calling render(:action...) does not invoke the action method. It simply renders that action’s default template.

render(template: name, [locals: hash] )

Renders a template and arranges for the resulting text to be sent back to the client. The :template value must contain both the controller and action parts of the new name, separated by a forward slash. The following code will render the template app/views/blog/short_list:

 class​ BlogController < ApplicationController
 def​ ​index
  render(​template: ​​"blog/short_list"​)
 end
 end
render(file: path)

Renders a view that may be entirely outside of your application (perhaps one shared with another Rails application). By default, the file is rendered without using the current layout. This can be overridden with layout: true.

render(partial: name, …)

Renders a partial template. We talk about partial templates in depth in Partial-Page Templates.

render(nothing: true)

Returns nothing—sends an empty body to the browser.

render(xml: stuff)

Renders stuff as text, forcing the content type to be application/xml.

render(json: stuff, [callback: hash] )

Renders stuff as JSON, forcing the content type to be application/json. Specifying :callback will cause the result to be wrapped in a call to the named callback function.

render(:update) do |page| ... end

Renders the block as an RJS template, passing in the page object.

 render(​:update​) ​do​ |page|
  page[​:cart​].​replace_html​ ​partial: ​​'cart'​, ​object: ​@cart
  page[​:cart​].​visual_effect​ ​:blind_down​ ​if​ @cart.​total_items​ == 1
 end

All forms of render take optional :status, :layout, and :content_type parameters. The :status parameter provides the value used in the status header in the HTTP response. It defaults to "200 OK". Do not use render with a 3xx status to do redirects; Rails has a redirect method for this purpose.

The :layout parameter determines whether the result of the rendering will be wrapped by a layout. (We first came across layouts in Iteration C2: Adding a Page Layout. We’ll look at them in depth starting in Reducing Maintenance with Layouts and Partials.) If the parameter is false, no layout will be applied. If set to nil or true, a layout will be applied only if there is one associated with the current action. If the :layout parameter has a string as a value, it will be taken as the name of the layout to use when rendering. A layout is never applied when the :nothing option is in effect.

The :content_type parameter lets you specify a value that will be passed to the browser in the Content-Type HTTP header.

Sometimes it is useful to be able to capture what would otherwise be sent to the browser in a string. The render_to_string method takes the same parameters as render but returns the result of rendering as a string—the rendering is not stored in the response object and so will not be sent to the user unless you take some additional steps.

Calling render_to_string does not count as a real render. You can invoke the real render method later without getting a DoubleRender error.

Sending Files and Other Data

We’ve looked at rendering templates and sending strings in the controller. The third type of response is to send data (typically, but not necessarily, file contents) to the client.

 send_data(data, options​​)

Sends a data stream to the client. Typically the browser will use a combination of the content type and the disposition, both set in the options, to determine what to do with this data.

 def​ ​sales_graph
 png_data = Sales.​plot_for​(Date.​today​.​month​)
 send_data(png_data, ​type: ​​"image/png"​, ​disposition: ​​"inline"​)
 end

The options are:

:disposition (string)

Suggests to the browser that the file should be displayed inline (option inline) or downloaded and saved (option attachment, the default).

:filename string

A suggestion to the browser of the default filename to use when saving this data.

:status (string)

The status code (defaults to "200 OK").

:type (string)

The content type, defaulting to application/octet-stream.

:url_based_filename boolean

If true and :filename is not set, this option prevents Rails from providing the basename of the file in the Content-Disposition header. Specifying the basename of the file is necessary in order to make some browsers handle i18n filenames correctly.

A related method is send_file, which sends the contents of a file to the client.

 send_file(path, options​​)

Sends the given file to the client. The method sets the Content-Length, Content-Type, Content-Disposition, and Content-Transfer-Encoding headers.

:buffer_size (number)

The amount sent to the browser in each write if streaming is enabled (:stream is true).

:disposition (string)

Suggests to the browser that the file should be displayed inline (option inline) or downloaded and saved (option attachment, the default).

:filename (string)

A suggestion to the browser of the default filename to use when saving the file. If not set, defaults to the filename part of path.

:status string

The status code (defaults to "200 OK").

:stream (true or false)

If false, the entire file is read into server memory and sent to the client. Otherwise, the file is read and written to the client in :buffer_size chunks.

:type (string)

The content type, defaulting to application/octet-stream.

You can set additional headers for either send_ method by using the headers attribute in the controller:

 def​ ​send_secret_file
  send_file(​"/files/secret_list"​)
  headers[​"Content-Description"​] = ​"Top secret"
 end

We show how to upload files starting in Uploading Files to Rails Applications.

Redirects

An HTTP redirect is sent from a server to a client in response to a request. In effect, it says, “I’m done processing this request, and you should go here to see the results.” The redirect response includes a URL that the client should try next along with some status information saying whether this redirection is permanent (status code 301) or temporary (307). Redirects are sometimes used when web pages are reorganized; clients accessing pages in the old locations will get referred to the page’s new home. More commonly, Rails applications use redirects to pass the processing of a request off to some other action.

Redirects are handled behind the scenes by web browsers. Normally, the only way you’ll know that you’ve been redirected is a slight delay and the fact that the URL of the page you’re viewing will have changed from the one you requested. This last point is important—as far as the browser is concerned, a redirect from a server acts pretty much the same as having an end user enter the new destination URL manually.

Redirects turn out to be important when writing well-behaved web applications. Let’s look at a basic blogging application that supports comment posting. After a user has posted a comment, our application should redisplay the article, presumably with the new comment at the end.

It’s tempting to code this using logic such as the following:

 class​ BlogController
 def​ ​display
  @article = Article.​find​(params[​:id​])
 end
 
 def​ ​add_comment
  @article = Article.​find​(params[​:id​])
  comment = Comment.​new​(params[​:comment​])
  @article.​comments​ << comment
 if​ @article.​save
  flash[​:note​] = ​"Thank you for your valuable comment"
 else
  flash[​:note​] = ​"We threw your worthless comment away"
 end
 # DON'T DO THIS
  render(​action: ​​'display'​)
 end
 end

The intent here was clearly to display the article after a comment has been posted. To do this, the developer ended the add_comment method with a call to render(action:'display'). This renders the display view, showing the updated article to the end user. But think of this from the browser’s point of view. It sends a URL ending in blog/add_comment and gets back an index listing. As far as the browser is concerned, the current URL is still the one that ends in blog/add_comment. This means that if the user hits Refresh or Reload (perhaps to see whether anyone else has posted a comment), the add_comment URL will be sent again to the application. The user intended to refresh the display, but the application sees a request to add another comment. In a blog application, this kind of unintentional double entry is inconvenient. In an online store, it can get expensive.

In these circumstances, the correct way to show the added comment in the index listing is to redirect the browser to the display action. We do this using the Rails redirect_to method. If the user subsequently hits Refresh, it will simply reinvoke the display action and not add another comment.

 def​ ​add_comment
  @article = Article.​find​(params[​:id​])
  comment = Comment.​new​(params[​:comment​])
  @article.​comments​ << comment
 if​ @article.​save
  flash[​:note​] = ​"Thank you for your valuable comment"
 else
  flash[​:note​] = ​"We threw your worthless comment away"
 end
» redirect_to(​action: ​​'display'​)
 end

Rails has a lightweight yet powerful redirection mechanism. It can redirect to an action in a given controller (passing parameters), to a URL (on or off the current server), or to the previous page. Let’s look at these three forms in turn:

redirect_to(action: ..., options…)

Sends a temporary redirection to the browser based on the values in the options hash. The target URL is generated using url_for, so this form of redirect_to has all the smarts of Rails routing code behind it.

redirect_to(path)

Redirects to the given path. If the path does not start with a protocol (such as http://), the protocol and port of the current request will be prepended. This method does not perform any rewriting on the URL, so it should not be used to create paths that are intended to link to actions in the application (unless you generate the path using url_for or a named route URL generator).

 def​ ​save
  order = Order.​new​(params[​:order​])
 if​ order.​save
  redirect_to ​action: ​​"display"
 else
  session[​:error_count​] ||= 0
  session[​:error_count​] += 1
 if​ session[​:error_count​] < 4
  self.​notice​ = ​"Please try again"
 else
 # Give up -- user is clearly struggling
  redirect_to(​"/help/order_entry.html"​)
 end
 end
 end
redirect_to(:back)

Redirects to the URL given by the HTTP_REFERER header in the current request.

 def​ ​save_details
 unless​ params[​:are_you_sure​] == ​'Y'
  redirect_to(​:back​)
 else
 # ...
 end
 end

By default all redirections are flagged as temporary (they will affect only the current request). When redirecting to a URL, it’s possible you might want to make the redirection permanent. In that case, set the status in the response header accordingly:

 headers[​"Status"​] = ​"301 Moved Permanently"
 redirect_to(​"http://my.new.home"​)

Because redirect methods send responses to the browser, the same rules apply as for the rendering methods—you can issue only one per request.

So far, we have been looking at requests and responses in isolation. Rails also provides a number of mechanisms that span requests.

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

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