Francis's Octopress Blog

A blogging framework for hackers.

Action Controller Overview

Action Controller Overview

Controllers

Action Controller Overview
This guide covers how controllers work and how they fit into the request cycle in your application. It includes sessions, filters, and cookies, data streaming, and dealing with exceptions raised by a request, among other topics.
Rails Routing from the Outside In
This guide covers the user-facing features of Rails routing. If you want to understand how to use routing in your own Rails applications, start here.

Action Controller Overview

In this guide you will learn how controllers work and how they fit into the request cycle in your application. After reading this guide, you will be able to:

在这个gudie中你将会学习到controllers是怎样工作的以及它们在你的应用程序中是怎用配合完成request cycle

  • Follow the flow of a request through a controller

跟随requset流向一个controller

  • Understand why and how to store data in the session or cookies

明白在会话和cookies中为什么以及怎样存储data

  • Work with filters to execute code during request processing

使用filters来工作在request过程中执行code

  • Use Action Controller’s built-in HTTP authentication

使用Action Controller的内置的HTTP认证

  • Stream data directly to the user’s browser

Stream data直接(到)用户的浏览器

  • Filter sensitive parameters so they do not appear in the application’s log

过滤敏感参数使得它们不会在应用程序的log中出现

  • Deal with exceptions that may be raised during request processing

处理可能在request进程之间抛出的意外

1 What Does a Controller Do?

Action Controller is the C in MVC. After routing has determined which controller to use for a request, your controller is responsible for making sense of the request and producing the appropriate output. Luckily, Action Controller does most of the groundwork for you and uses smart conventions to make this as straightforward as possible. Action ControllerMVC中的C。在routing已经决定了对于一个request使用哪个controller,你的controller负责请求的实际意图并且产生合适的输出。幸运的是,Action Controller为了做了大多数的基础工作并且使用智能方便的(方式)使得C能够尽可能的直接干脆。

For most conventional RESTful applications, the controller will receive the request (this is invisible to you as the developer), fetch or save data from a model and use a view to create HTML output. If your controller needs to do things a little differently, that’s not a problem, this is just the most common way for a controller to work.

对于大多数的传统的RESTful应用程序,controller将会收到请求(这对于开发者你来说是无形的),从一个model刷新或者savee数据或者使用一个视图来创建HTML输出。如果你的controller需要做些略微不同的事情,这不是问题,这仅仅是controller在工作中大多数通常的方式。

A controller can thus be thought of as a middle man between models and views. It makes the model data available to the view so it can display that data to the user, and it saves or updates data from the user to the model.

一个controller能够这样,作为在modelsviews之间的中间人。它使得model数据可以用户view因此它可以显示这些数据给用户,同时它从用户保存或者更新数据到model

For more details on the routing process, see RailsRoutingfromtheOutsideIn.

2 Methods and Actions

A controller is a Ruby class which inherits from ApplicationController and has methods just like any other class. When your application receives a request, the routing will determine which controller and action to run, then Rails creates an instance of that controller and runs the method with the same name as the action.

一个controller是一个Ruby类它继承至ApplicationController同时像其他类一样它也有方法。当你的应用程序收到一个requestrouting将会决定那个controlleraction会运行,然后Rails创建一个controller的实例同时运行与action名字相同的方法。

class ClientsController < ApplicationController

def new

end

end

As an example, if a user goes to /clients/new in your application to add a new client, Rails will create an instance of ClientsController and run the new method. Note that the empty method from the example above could work just fine because Rails will by default render the new.html.erb view unless the action says otherwise. The new method could make available to the view a @client instance variable by creating a new Client:

正如一个例子,如果一个user导航至/clients/new 在你的应用程序中添加一个new clientRails将会创建一个ClientsController的实例并且运行new方法。注意例子中空的方法完全可以工作因为Rails将会默认render名称为new.html.erb的视图除非action指定了另外的。new方法通过创建一个新的Client将使得可以查看@client实例变量。

def new

@client = Client.new

end

The Layouts&RenderingGuide explains this in more detail.

ApplicationController inherits from ActionController::Base, which defines a number of helpful methods. This guide will cover some of these, but if you’re curious to see what’s in there, you can see all of them in the API documentation or in the source itself.

ApplicationController继承至ActionController::Base,器定义了很多helpful方法。这个教材将会涵盖这些,但是如果你好奇与其中有些什么,你可以在API文档中或者他的源代码中看到所有的方法。

Only public methods are callable as actions. It is a best practice to lower the visibility of methods which are not intended to be actions, like auxiliary methods or filters.

对于action只有公共的方法才是可调用的。它是低敏感度方法的最好实践,而这些对actions没有义务,像辅助方法或者过滤器。

3 Parameters

You will probably want to access data sent in by the user or other parameters in your controller actions. There are two kinds of parameters possible in a web application. The first are parameters that are sent as part of the URL, called query string parameters. The query string is everything after “?” in the URL. The second type of parameter is usually referred to as POST data. This information usually comes from an HTML form which has been filled in by the user. It’s called POST data because it can only be sent as part of an HTTP POST request. Rails does not make any distinction between query string parameters and POST parameters, and both are available in the params hash in your controller:

在你的controller action中你可能想访问用户或者其他参数发送的数据。一个web应用程序中这里可能有两种参数。第一种是作为URL一部分发送的参数,叫做query string parameters(字符串查询参数)。查询字符串是在URL?”后面的所有字符。第二种类型的参数通常被简称为POST data。这类信息通常来至于一个被用户填写的HTML表单。它被称为POST data因为它仅能通过HTTP POST 请求的一部分发送。Rails在字符串查询参数和POST data参数之间不做任何区分,它们在你的controllerparams字典中都是可用的。

class ClientsController < ActionController::Base

This action uses query string parameters because it gets run

by an HTTP GET request, but this does not make any difference

to the way in which the parameters are accessed. The URL for

this action would look like this in order to list activated

clients: /clients?status=activated

def index

if params[:status] == “activated”

@clients = Client.activated

else

@clients = Client.unactivated

end

end

 

This action uses POST parameters. They are most likely coming

from an HTML form which the user has submitted. The URL for

this RESTful request will be “/clients”, and the data will be

sent as part of the request body.

def create

@client = Client.new(params[:client])

if @client.save

redirect_to @client

else

This line overrides the default rendering behavior, which

would have been to render the “create” view.

render :action => “new”

end

end

end

3.1 Hash and Array Parameters

The params hash is not limited to one-dimensional keys and values. It can contain arrays and (nested) hashes. To send an array of values, append an empty pair of square brackets “[]” to the key name:

params hash字典不限于一维的关键字和值。它能够包含数组和(嵌套)字典。要发送一个数组值,添加一个空的[]”key name

GET /clients?ids[]=1&ids[]=2&ids[]=3

The actual URL in this example will be encoded as/clients?ids%5b%5d=1&ids%5b%5d=2&ids%5b%5d=3as[and]are not allowed in URLs. Most of the time you don’t have to worry about this because the browser will take care of it for you, and Rails will decode it back when it receives it, but if you ever find yourself having to send those requests to the server manually you have to keep this in mind.但是如果你发现你自己不得不手动的发送这些请求到服务器你必须注意。

The value of params[:ids] will now be [“1”, “2”, “3”]. Note that parameter values are always strings; Rails makes no attempt to guess or cast the type.

To send a hash you include the key name inside the brackets:

<form accept-charset=“UTF-8” action=“/clients” method=“post”>

<input type=“text” name=“client[name]” value=“Acme” />

<input type=“text” name=“client[phone]” value=“12345” />

<input type=“text” name=“client[address][postcode]” value=“12345” />

<input type=“text” name=“client[address][city]” value=“Carrot City” />

</form>

When this form is submitted, the value of params[:client] will be {“name” =>Acme,phone=>12345,address=> {“postcode” =>12345,city=>Carrot City}}. Note the nested hash in params[:client][:address].

Note that the params hash is actually an instance of HashWithIndifferentAccess from Active Support, which acts like a hash that lets you use symbols and strings interchangeably互换as keys.

3.2 JSON/XML parameters

If you’re writing a web service application, you might find yourself more comfortable on accepting parameters in JSON or XML format. Rails will automatically convert your parameters into params hash, which you’ll be able to access like you would normally do with form data.

如果你打算写一个web服务程序,你可能会发现使用JSON或者XML格式接收参数会更加舒服。Rails将会自动的转换你的参数到params hash字典中,其中你将可以像正常表单数据那样接收数据。

So for example, if you are sending this JSON parameter:

{ "company": { "name": "acme", "address": "123 Carrot Street" } }

You’ll get params[:company] as { :name =>acme,address=>123 Carrot Street}.

Also, if you’ve turned on config.wrap_parameters in your initializer or calling wrap_parameters in your controller, you can safely omit the root element in the JSON/XML parameter. The parameters will be cloned and wrapped in the key according to your controller’s name by default. So the above parameter can be written as:

同样,如果你已经打开config.wrap_parameters在你的controller中初始化或者调用wrap_parameters,在JSON/XML参数中你可以安全的忽略root element。参数将会克隆和包装在key默认会依照你的controllername。因此完整的参数可以这样写:

{ "name": "acme", "address": "123 Carrot Street" }

And assume假设that youre sending the data to CompaniesController, it would then be wrapped in :company key like this:

{ :name => “acme”, :address => “123 Carrot Street”, :company => { :name => “acme”, :address => “123 Carrot Street” }}

You can customize the name of the key or specific parameters you want to wrap by consulting the APIdocumentation

3.3 Routing Parameters

The params hash will always contain the :controller and :action keys, but you should use the methods controller_name and action_name instead to access these values. Any other parameters defined by the routing, such as :id will also be available. As an example, consider a listing of clients where the list can show either active or inactive clients. We can add a route which captures the :status parameter in a “pretty” URL:

params hash字典总会包含关键字:controller and :action,但是你应该使用方法controller_name and action_name作为替代访问这些值。也有一些其他的值被routing定义,例如:id也是可以的。例如,思考一个clients的列表能够显示active或者inactive client。我们可以添加一个route它会捕获在aprettyURL(一个漂亮的URL)中的:status参数:

match ‘/clients/:status’ => ‘clients#index’, :foo => “bar”

In this case, when a user opens the URL /clients/active, params[:status] will be set to “active”. When this route is used, params[:foo] will also be set to “bar” just like it was passed in the query string. In the same way params[:action] will contain “index”.

3.4 default_url_options

You can set global default parameters that will be used when generating URLs with default_url_options. To do this, define a method with that name in your controller:

class ApplicationController < ActionController::Base

The options parameter is the hash passed in to ‘url_for’

def default_url_options(options)

{:locale => I18n.locale}

end

end

These options will be used as a starting-point when generating URLs, so it’s possible they’ll be overridden by url_for. Because this method is defined in the controller, you can define it on ApplicationController so it would be used for all URL generation, or you could define it on only one controller for all URLs generated there.

4 Session会话

Your application has a session for each user in which you can store small amounts of data that will be persisted between requests. The session is only available in the controller and the view and can use one of a number of different storage mechanisms:

你的应用程序对每一位用户都有一个会话通过它你可以存储少量的数据这会将request区分开来。

  • CookieStore – Stores everything on the client.
  • DRbStore – Stores the data on a DRb server.
  • MemCacheStore – Stores the data in a memcache.
  • ActiveRecordStore – Stores the data in a database using Active Record.

All session stores use a cookie to store a unique ID for each session (you must use a cookie, Rails will not allow you to pass the session ID in the URL as this is less secure).

每个会话使用一个cookie存储并且每个会话都有一个独特的ID(你必须使用一个cookieRails不允许你在URL中传送会话ID因为这样不安全)。

For most stores this ID is used to look up the session data on the server, e.g. in a database table. There is one exception, and that is the default and recommended session store – the CookieStore – which stores all session data in the cookie itself (the ID is still available to you if you need it). This has the advantage of being very lightweight and it requires zero setup in a new application in order to use the session. The cookie data is cryptographically signed to make it tamper-proof, but it is not encrypted, so anyone with access to it can read its contents but not edit it (Rails will not accept it if it has been edited).

大多数(时候)存储这个ID是用来在server上面查找session data,例如,存放在一个数据库table中。这里有一个例外,并且这是默认以及推荐的session存储-CookieStore-它存储所有的session数据在它的cookie中(如果你需要ID同样是可用的)。这里的优势是十分轻量级并且在一个新应用程序中使用session是零安装。cookie数据以加密签名的方式以防篡改,但是它没有将内容译成迷文,因此任何人访问它都可以阅读它的内容但是不能编辑它(Rails将不会接受被编辑过的cookie)。

The CookieStore can store around 4kB of datamuch less than the othersbut this is usually enough. Storing large amounts of data in the session is discouraged no matter which session store your application uses. You should especially avoid storing complex objects (anything other than basic Ruby objects, the most common example being model instances) in the session, as the server might not be able to reassemble them between requests, which will result in an error.

CookieStore能够存储大约4KB的数据——远少于其他——但是这通常足够。在你应用程序使用的session中,存储大量的数据是无比泄气的。你应该尤其避免存储复杂的对象(任何其他的Ruby基本对象,最普遍的例子是model实例)在session,因为server可能不能在不同的请求间重组它们,结果它将得到错误。

Read more about session storage in the SecurityGuide.

If you need a different session storage mechanism, you can change it in the config/initializers/session_store.rb file:

如果你需要一个不同的session存储机制,你可以在这里更改config/initializers/session_store.rb

Use the database for sessions instead of the cookie-based default,

which shouldn’t be used to store highly confidential information

(create the session table with “script/rails g session_migration”)

YourApp::Application.config.session_store :active_record_store

Rails sets up a session key (the name of the cookie) when signing the session data. These can also be changed in config/initializers/session_store.rb:

Rails当注册session data的时候会设置一个session key(cookie的名字)。也可以在这里修改config/initializers/session_store.rb

Be sure to restart your server when you modify this file.

 

YourApp::Application.config.session_store :cookie_store, :key => ‘_your_app_session’

You can also pass a :domain key and specify the domain name for the cookie:

你也可以通过一个:domain关键字指定cookie的域名:

Be sure to restart your server when you modify this file.

 

YourApp::Application.config.session_store :cookie_store, :key => ‘_your_app_session’, :domain => “.example.com”

Rails sets up (for the CookieStore) a secret key密钥used for signing the session data. This can be changed in config/initializers/secret_token.rb

Be sure to restart your server when you modify this file.

 

Your secret key for verifying the integrity of signed cookies.

If you change this key, all old signed cookies will become invalid!

Make sure the secret is at least 30 characters and all random,

no regular words or you’ll be exposed to dictionary attacks.

YourApp::Application.config.secret_token = ‘49d3f3de9ed86c74b94ad6bd0…’

Changing the secret when using the CookieStore will invalidate废除all existing sessions.

当使用CookieStore的时候改变密钥将会废除所有存在的sessions

4.1 Accessing the Session

In your controller you can access the session through the session instance method.

在你的controller你可以访问session通过session实例方法。

Sessions are lazily loaded. If you dont access sessions in your actions code, they will not be loaded. Hence因此you will never need to disable sessions, just not accessing them will do the job.

Session values are stored using key/value pairs like a hash:

session的值使用key/value对存储就像一个hash字典:

class ApplicationController < ActionController::Base

 

private

 

Finds the User with the ID stored in the session with the key

:current_user_id This is a common way to handle user login in

a Rails application; logging in sets the session value and

logging out removes it.

def current_user

@_current_user ||= session[:current_user_id] &&

User.find_by_id(session[:current_user_id])

end

end

To store something in the session, just assign it to the key like a hash:

要存储一些信息在session中,仅仅需要将其指派给key就像一个hash字典一样:

class LoginsController < ApplicationController

“Create” a login, aka “log the user in”

def create

if user = User.authenticate(params[:username], params[:password])

Save the user ID in the session so it can be used in

subsequent requests

session[:current_user_id] = user.id

redirect_to root_url

end

end

end

To remove something from the session, assign that key to be nil:

session从移除一些信息,分派那个keynil

class LoginsController < ApplicationController

“Delete” a login, aka “log the user out”

def destroy

Remove the user id from the session

@_current_user = session[:current_user_id] = nil

redirect_to root_url

end

end

To reset the entire session, use reset_session.从值整个session,使用reset_session

4.2 The Flash

The flash is a special part of the session which is cleared with each request. This means that values stored there will only be available in the next request, which is useful for storing error messages etc. It is accessed in much the same way as the session, like a hash. Let’s use the act of logging out as an example. The controller can send a message which will be displayed to the user on the next request:

flashsession中是一个特殊的部分它它清楚于每个(下一次)请求。这里的意思是存储的值仅仅在下一个请求中是可用的,这在存储错误信息等是非常有用的。它的访问方式与session的访问方式有着很大的相同之处,就像(访问)一个hash字典那样。让我们使用登出动作作为一个例子。controller会发送一个消息这个消息在下一个请求的时候将会显示给用户:

class LoginsController < ApplicationController

def destroy

session[:current_user_id] = nil

flash[:notice] = “You have successfully logged out”

redirect_to root_url

end

end

Note it is also possible to assign a flash message as part of the redirection.

redirect_to root_url, :notice => “You have successfully logged out”

The destroy action redirects to the application’s root_url, where the message will be displayed. Note that it’s entirely up to the next action to decide what, if anything, it will do with what the previous action put in the flash. It’s conventional to display eventual errors or notices from the flash in the application’s layout:

destroy动作重定向到应用程序的root_url,在这里消息(刚添加的)将会被显示。注意:它完全取决于next action来决定做什么,如果有(next action),它将会完成previous action放入flash中的信息。

<html>

<!— <head/> —>

<body>

<% if flash[:notice] %>

<p><%= flash[:notice] %></p>

<% end %>

<% if flash[:error] %>

<p><%= flash[:error] %></p>

<% end %>

<!— more content —>

</body>

</html>

This way, if an action sets an error or a notice message, the layout will display it automatically

通过这种方式,如果action设定一条error或者通知消息,laout将会自动的显示它。

If you want a flash value to be carried over to another request, use the keep method:

如果你想一个flash值转接到另一个request,使用keep方法:

class MainController < ApplicationController

Let’s say this action corresponds to root_url, but you want

all requests here to be redirected to UsersController#index.

If an action sets the flash and redirects here, the values

would normally be lost when another redirect happens, but you

can use ‘keep’ to make it persist for another request.

def index

Will persist all flash values.

flash.keep

 

You can also use a key to keep only some kind of value.

flash.keep(:notice)

redirect_to users_url

end

end

4.2.1 flash.now

By default, adding values to the flash will make them available to the next request, but sometimes you may want to access those values in the same request. For example, if the create action fails to save a resource and you render the new template directly, that’s not going to result in a new request, but you may still want to display a message using the flash. To do this, you can use flash.now in the same way you use the normal flash:

默认情况,添加到flash中的值在next request中是可用的,但是有时你可以想在同样的request中访问这些值。例如,如果create action保存resource失败并且你直接render new template,这样在一个newrequest中没有得到result,但是你仍然希望使用flash显示消息。这样做,你可以就像使用flash那样使用flash.now

class ClientsController < ApplicationController

def create

@client = Client.new(params[:client])

if @client.save

else

flash.now[:error] = “Could not save client”

render :action => “new”

end

end

end

5 Cookies

Your application can store small amounts of data on the client — called cookies — that will be persisted across requests and even sessions. Rails provides easy access to cookies via the cookies method, which — much like the session — works like a hash:

你的应用程序可以存储少量dataclient——它被称之为cookies——它会保持访问的请求以及曾经sessionsRails提供简单的方式来访问cookies通过cookies方法,就像session——hash字典那样工作(存储data):

class CommentsController < ApplicationController

def new

Auto-fill the commenter’s name if it has been stored in a cookie

@comment = Comment.new(:name => cookies[:commenter_name])

end

 

def create

@comment = Comment.new(params[:comment])

if @comment.save

flash[:notice] = “Thanks for your comment!”

if params[:remember_name]#:remember_name这里相当于用户的的反馈是否记住name

Remember the commenter’s name.

cookies[:commenter_name] = @comment.name

else

Delete cookie for the commenter’s name cookie, if any.

cookies.delete(:commenter_name)

end

redirect_to @comment.article

else

render :action => “new”

end

end

end

Note that while for session values you set the key to nil, to delete a cookie value you should use cookies.delete(:key).

6 Rendering xml and json data

ActionController makes it extremely easy to render xml or json data. If you generate a controller using scaffold then your controller would look something like this.

ActionController使得render xml或者json数据相当简单。如果你使用scaffold创建一个controller那么你的controller看起来将会像这样:

class UsersController < ApplicationController

def index

@users = User.all

respond_to do |format|

format.html # index.html.erb

format.xml { render :xml => @users}

format.json { render :json => @users}

end

end

end

Notice that in the above case code is render :xml => @users and not render :xml => @users.to_xml. That is because if the input is not string then rails automatically invokes to_xml .

注意在render中的完整示例代码是render :xml => @users并不是render :xml => @users.to_xml,那是因为如果输入的不是string那么rails自动调用to_xml

7 Filters过滤器

Filters are methods that are run before, after or “around” a controller action.

Filters是一个方法它运行在controller action之前或者伴随着controller action

Filters are inherited, so if you set a filter on ApplicationController, it will be run on every controller in your application.

Filters是可继承的,因此如果你在ApplicationController 设置了一个filter,他将会在你的应用程序的每个controller中运行。

Before filters may halt the request cycle. A common before filter is one which requires that a user is logged in for an action to be run. You can define the filter method this way:

filters之前可能会停止request周期。一个通常情况在filter之前的是用户logged inrequires使之对应的action被运行。你可以这样定义filter方法:

class ApplicationController < ActionController::Base

before_filter :require_login

 

private

 

def require_login

unless logged_in?

flash[:error] = “You must be logged in to access this section”

redirect_to new_login_url # halts request cycle

end

end

 

The logged_in? method simply returns true if the user is logged

in and false otherwise. It does this by “booleanizing” the

current_user method we created previously using a double ! operator.

Note that this is not common in Ruby and is discouraged unless you

really mean to convert something into true or false.

def logged_in?

!!current_user

end

end

The method simply stores an error message in the flash and redirects to the login form if the user is not logged in. If a before filter renders or redirects, the action will not run. If there are additional filters scheduled to run after that filter they are also cancelled.

这个方法简单的存储一个错误消息在flash中并且重定向到login表单如果用户没有登录。如果是一个在filter之前的renderes或者重定向,这个action将不会运行。如果这里有额外附加的filters计划运行并且在这个filter之后,它们也将取消(执行)。

In this example the filter is added to ApplicationController and thus all controllers in the application inherit it. This will make everything in the application requiretheusertobeloggedininordertouseit. For obvious reasons (the user wouldn’t be able to log in in the first place!), not all controllers or actions should require this. You can prevent this filter from running before particular actions with skip_before_filter:

明显的原因(在第一次登录的时候不能登录),并不是所有的controllers或者actions需要登录,你可以使用skip_before_filter 在部分action运行之前阻止这个filter

class LoginsController < ApplicationController

skip_before_filter :require_login, :only => [:new, :create]

end

Now, the LoginsController’s new and create actions will work as before without requiring the user to be logged in. The :only option is used to only skip this filter for these actions, and there is also an :except option which works the other way. These options can be used when adding filters too, so you can add a filter which only runs for selected actions in the first place.

现在,LoginsControllernewcreate action工作之前不再需要用户已经登录站点来。:only选项使用于仅仅在这些action中略过filter,同样这里也有:except选项一相反的方式工作。这些选项也可以在添加filters的时候使用,因此你可以添加一个filter其仅仅在选择的action第一次运行的时候执行(忽略)。

7.1 After Filters and Around Filters

In addition to before filters, you can also run filters after an action has been executed, or both before and after.

除了在filters之前,你也可以在action被执行之后运行filters,或者在之前和过后都执行。

After filters are similar to before filters, but because the action has already been run they have access to the response data that’s about to be sent to the client. Obviously, after filters cannot stop the action from running.

After filters(执行action)和before filters类似,但是因为action已经被运行,这些filter会访问response data并且,response data会全部发送给client。明显的,after filters不能够停止action的运行。

Around filters are responsible for running their associated actions by yielding, similar to how Rack middlewares work.

Around filters能够通过yielding运行(Around filters执行的action的)关联的action,类似于Rack middlewares工作方式。

For example, in a website where changes have an approval workflow an administrator could be able to preview them easily, just apply them within a transaction:

例如,在一个website更改操作有一个批准工作流程,管理员可以很容易预览这些内容,仅仅在一个transaction中就可以实现这个功能的应用:

class ChangesController < ActionController::Base

around_filter :wrap_in_transaction, :only => :show

 

private

 

def wrap_in_transaction

ActiveRecord::Base.transaction do

begin

yield

ensure

raise ActiveRecord::Rollback

end

end

end

end

Note that an around filter wraps also rendering. In particular, if in the example above the view itself reads from the database via a scope or whatever, it will do so within the transaction and thus present the data to preview.

注意around filter同样也会render。特别的是,如果在上述例子中,view自己通过一个scpoe或者其他从数据库中读取(数据),他将会在transaction中实现approval workflow并且将当前数据预览呈现。

They can choose not to yield and build the response themselves, in which case the action is not run.

他们(admin)可以选择不yield并且建立他们自己的response,这样(更改的)action将不会运行。

7.2 Other Ways to Use Filters 过滤器的其他使用方式

While the most common way to use filters is by creating private methods and using *_filter to add them, there are two other ways to do the same thing.

即使通常使用过滤器的方式是创建私有的方法并且使用*_filteraround_filter :wrap_in_transaction, :only => :show)来添加他们,这里有两种其他的方法来做同样的事情。

The first is to use a block directly with the *_filter methods. The block receives the controller as an argument, and the require_login filter from above could be rewritten to use a block:

第一种方法是使用一个*_filter methodsblock(块)。这个block接收controller为一个参数,,并且从above(上下文中的上文)could be rewritten to use a block#这个例子中也是第一次登录无法完成,没有忽略登录action

class ApplicationController < ActionController::Base

before_filter do |controller|

redirect_to new_login_url unless controller.send(:logged_in?)

end

end

Note that the filter in this case uses send because the logged_in? method is private and the filter is not run in the scope of the controller. This is not the recommended way to implement this particular filter, but in more simple cases it might be useful.

注意这个例子中的filter使用的是send因为logged_in?方法是私有的并且filter并不在controller的范围内运行。这里并不是实施这个特别的filter的推荐的方式,但是在很多简单的情况下这可能会有用。

The second way is to use a class (actually, any object that responds to the right methods will do) to handle the filtering. This is useful in cases that are more complex and can not be implemented in a readable and reusable way using the two other methods. As an example, you could rewrite the login filter again to use a class:

第二种方式是使用一个class(实际上,响应正确方法的任何对象都是可以的)来处理filtering。这在非常复杂的情况下,在只读不能implemented,重复使用两种其他方法是非常有用的。在例子中,你可以使用一个class来再次重写login filter

class ApplicationController < ActionController::Base

before_filter LoginFilter

end

 

class LoginFilter

def self.filter(controller)

unless controller.send(:logged_in?)

controller.flash[:error] = “You must be logged in”

controller.redirect_to controller.new_login_url

end

end

end

Again, this is not an ideal example for this filter, because it’s not run in the scope of the controller but gets the controller passed as an argument. The filter class has a class method filter which gets run before or after the action, depending on if it’s a before or after filter. Classes used as around filters can also use the same filter method, which will get run in the same way. The method must yield to execute the action. Alternatively, it can have both a before and an after method that are run before and after the action.

同样的,对于这种filter这不是一个(好)主意,因为它在controller的范围中不会运行但是获取controller为一个参数。filter 类有一个类方法filter,它在before或者after action运行,这依赖于它是一个before还是after filter。使用around filters的类同样也可以使用filter 方法,其也会以相同的方式运行。(filter)方法必须yield to 执行的action。另外,它也可以同时有beforeafter方法在before after action中运行。

8 Request Forgery Protection伪造请求保护

Cross-site request forgery is a type of attack in which a site tricks a user into making requests on another site, possibly adding, modifying or deleting data on that site without the user’s knowledge or permission.

跨站伪造请求是一种典型的攻击方式,在这种攻击方式中,一个网站诱骗用户发送请求到另一个网站,可能在这个站点中添加,修改或者删除数据并没有用户的信息或者权限。

The first step to avoid this is to make sure all “destructive” actions (create, update and destroy) can only be accessed with non-GET requests. If you’re following RESTful conventions you’re already doing this. However, a malicious site can still send a non-GET request to your site quite easily, and that’s where the request forgery protection comes in. As the name says, it protects from forged requests.

避免这样的事情发生的第一步是确保所有的destructive”(破坏性)actions(create, update and destroy)只能接受non-GET请求。如果你遵循RESTful公约你已经这样做了。然而,一个恶意的网站仍然可以轻易的发送一个non-GET请求到你的站点,到这里就该伪造请求保护(request forgery protection)出场了。正如名字所说,它防护伪造请求(对你站点的攻击)。

If you generate a form like this:如果你像下面这样生成表单:

<%= form_for @user do |f| %>

<%= f.text_field :username %>

<%= f.text_field :password %>

<% end %>

You will see how the token gets added as a hidden field:

你将会看到令牌是怎样作为一个hidden field添加:

<form accept-charset=“UTF-8” action=“/users/1” method=“post”>

<input type=“hidden”

value=“67250ab105eb5ad10851c00a5621854a23af5489”

name=“authenticity_token”/>

<!— fields —>

</form>

Rails adds this token to every form that’s generated using the formhelpers, so most of the time you don’t have to worry about it. If you’re writing a form manually or need to add the token for another reason, it’s available through the method form_authenticity_token:

Rails为每个使用form helpers生成的form添加这样的令牌。因此大多数时间你不需要担心这样的问题。如果你打算手动编写一个form或者因为其他的原因需要添加令牌,这也是可以的通过form_authenticity_token方法来实现:

form_authenticity_token生成一个有效的认证令牌。这在Rails不会自动添加的地方非常有用,比如在定制Ajax调用的时候。

The SecurityGuide has more about this and a lot of other security-related issues that you should be aware of when developing a web application.

SecurityGuide 有更多的关于这些以及许多其他安全相关的问题(的解决方法)你在开发一个web application的时候应该保持清醒。

9 The Request and Response Objects

In every controller there are two accessor methods pointing to the request and the response objects associated with the request cycle that is currently in execution. The request method contains an instance of AbstractRequest and the response method returns a response object representing what is going to be sent back to the client.

在每个controller有两个指向与目前正在执行的请求周期相关的request response 对象的访问方法。request方法包含一个AbstractRequest的实例以及response方法返回一个response对象表示什么打算发送回client

9.1 The request Object

The request object contains a lot of useful information about the request coming in from the client. To get a full list of the available methods, refer to the APIdocumentation. Among the properties that you can access on this object are:

request对象包含很多来自发出请求的client的有用信息。要得到可用方法的完整列表,参考APIdocumentation。其中你可以在这个对象中访问的属性有:

 

Property of request Purpose
host The hostname used for this request.
domain(n=2) The hostname’s first n segments, starting from the right (the TLD).
format The content type requested by the client.
method The HTTP method used for the request.
get?, post?, put?, delete?, head? Returns true if the HTTP method is GET/POST/PUT/DELETE/HEAD.
headers Returns a hash containing the headers associated with the request.
port The port number (integer) used for the request.
protocol Returns a string containing the protocol used plus “://”, for example “http://”.
query_string The query string part of the URL, i.e., everything after “?”.
remote_ip The IP address of the client.
url The entire URL used for the request.

 

9.1.1 path_parameters, query_parameters, and request_parameters

Rails collects all of the parameters sent along with the request in the params hash, whether they are sent as part of the query string or the post body. The request object has three accessors that give you access to these parameters depending on where they came from. The query_parameters hash contains parameters that were sent as part of the query string while the request_parameters hash contains parameters sent as part of the post body. The path_parameters hash contains parameters that were recognized by the routing as being part of the path leading to this particular controller and action.

Rails收集所有的parametersparams hash字典中于request一起发送,无论是作为query 字符串的一部分发送还是在post body中发送。request有三个访问器提供给你访问这些参数这依赖于它来自哪里。query_parameters hash字典包含作为查询字符串的一部分发送的参数,而request_parameters包含作为post body的一个部分发送的参数。path_parameters hash字典包含被routing组织作为指向特殊的controlleraction的参数。

9.2 The response Object

The response object is not usually used directly, but is during the execution of the action and rendering of the data that is being sent back to the user, but sometimes – like in an after filter – it can be useful to access the response directly. Some of these accessor methods also have setters, allowing you to change their values.

response object通常并不直接使用,但是它在action的执行与rendering的数据被发送回给用户期间建立,但是有的时候——像在一个after filter——它在直接访问responseobject)会非常有用。这些访问器中的一些methods也有setters,允许你改变它们的值。

 

Property of response Purpose
body This is the string of data being sent back to the client. This is most often HTML.
status The HTTP status code for the response, like 200 for a successful request or 404 for file not found.
location The URL the client is being redirected to, if any.
content_type The content type of the response.
charset The character set being used for the response. Default is “utf-8”.
headers Headers used for the response.

 

9.2.1 Setting Custom Headers

If you want to set custom headers for a response then response.headers is the place to do it. The headers attribute is a hash which maps header names to their values, and Rails will set some of them automatically. If you want to add or change a header, just assign it to response.headers this way:

response.headers[“Content-Type”] = “application/pdf”

10 HTTP Authentications

Rails comes with two built-in HTTP authentication mechanisms:

  • Basic Authentication
  • Digest Authentication

10.1 HTTP Basic Authentication

HTTP basic authentication is an authentication scheme that is supported by the majority of browsers and other HTTP clients. As an example, consider an administration section which will only be available by entering a username and a password into the browser’s HTTP basic dialog window. Using the built-in authentication is quite easy and only requires you to use one method, http_basic_authenticate_with.

基于HTTP的认证是一个认证结构它被主流的浏览器以及其他的HTTP clients支持。作为一个例子,思考一个管理员section它仅仅可以输入usernamepassword到浏览器的基于HTTP对话框窗口。使用内建的认证是十分简单的仅仅需要你使用一个方法,http_basic_authenticate_with

class AdminController < ApplicationController

http_basic_authenticate_with :name => “humbaba”, :password => “5baa61e4”

end

With this in place, you can create namespaced controllers that inherit from AdminController. The filter will thus be run for all actions in those controllers, protecting them with HTTP basic authentication.

通过将这段代码,你可以创建继承至AdminControllernamespaced controller。过滤器将自动对这些controllers中的所有的action运行这样的(认证),使用基于HTTP的认证来保护它们。

##in demo

class PostsController < ApplicationController

http_basic_authenticate_with :name => “test”, :password => “123456”, :except => :index

end

10.2 HTTP Digest Authentication

HTTP digest authentication is superior to the basic authentication as it does not require the client to send an unencrypted password over the network (though HTTP basic authentication is safe over HTTPS). Using digest authentication with Rails is quite easy and only requires using one method, authenticate_or_request_with_http_digest.

HTTP 精简认证优于基础认证因为它不需要client发送一个未加密的穿过网络(即使HTTP基础的认证比HTTPS更安全)。在Rails中使用 digest authentication相当容易并且仅仅需要使用一个方法,authenticate_or_request_with_http_digest

class AdminController < ApplicationController

USERS = { “lifo” => “world” }

 

before_filter :authenticate

 

private

 

def authenticate

authenticate_or_request_with_http_digest do |username|

USERS[username]

end

end

end

As seen in the example above, the authenticate_or_request_with_http_digest block takes only one argument – the username. And the block returns the password. Returning false or nil from the authenticate_or_request_with_http_digest will cause authentication failure.

正如上面看到例子, authenticate_or_request_with_http_digest代码块仅仅获取一个参数——username。并且blockdo…end之间的block)返回password。从 authenticate_or_request_with_http_digest返回false或者nil将会导致认证失败。

11 Streaming and File Downloads

Sometimes you may want to send a file to the user instead of rendering an HTML page. All controllers in Rails have the send_data and the send_file methods, which will both stream data to the client. send_file is a convenience method that lets you provide the name of a file on the disk and it will stream the contents of that file for you.

有时你可能希望发送一个文件给用户代替rendering一个HTML页面。Rails中的所有controllers都有send_datasend_file方法,它们都将会(将文件)以流数据形式发送给clientsend_file是一个方便的方法它让你提供在disk上的一个文件的name并且它将会把那个文件的内容以stream的形式发送给你。

To stream data to the client, use send_data:

require “prawn”

class ClientsController < ApplicationController

Generates a PDF document with information on the client and

returns it. The user will get the PDF as a file download.

def download_pdf

client = Client.find(params[:id])

send_data generate_pdf(client),

:filename => “#{client.name}.pdf”,

:type => “application/pdf”

end

 

private

 

def generate_pdf(client)

Prawn::Document.new do

text client.name, :align => :center

text “Address: #{client.address}”

text “Email: #{client.email}”

end.render

end

end

The download_pdf action in the example above will call a private method which actually generates the PDF document and returns it as a string. This string will then be streamed to the client as a file download and a filename will be suggested to the user. Sometimes when streaming files to the user, you may not want them to download the file. Take images, for example, which can be embedded into HTML pages. To tell the browser a file is not meant to be downloaded, you can set the :disposition option to “inline”. The opposite and default value for this option is “attachment”.

上面例子中的 download_pdf action将会call一个私有的方法这个方法实际上创建PDF文档并且将其作为一个string形式返回。这个string将会随后以stream的形式到client作为一个文件下载并且一个文件名将会建议给用户。提到图形,例如,它可以被嵌入HTML页面。要告诉浏览器一个文件不是(用来)被下载的,你可以设置:disposition选项为‘inline’。这个选项相反的和默认的值是”attachment”

11.1 Sending Files

If you want to send a file that already exists on disk, use the send_file method.

如果你想发送一个已经在硬盘中存在的文件,使用send_file方法。

class ClientsController < ApplicationController

Stream a file that has already been generated and stored on disk.

def download_pdf

client = Client.find(params[:id])

send_file(“#{Rails.root}/files/clients/#{client.id}.pdf”,

:filename => “#{client.name}.pdf”,

:type => “application/pdf”)

end

end

This will read and stream the file 4kB at the time, avoiding loading the entire file into memory at once. You can turn off streaming with the :stream option or adjust the block size with the :buffer_size option.

这将会readstream文件一次4kB,避免一次将整个文件导入内存。你可以用:stream选项关闭流传送使或者设置适当的block size通过:buffer_size选项。

Be careful when using data coming from the client (params, cookies, etc.) to locate the file on disk, as this is a security risk that might allow someone to gain access to files they are not meant to see.

在使用来自client的数据的时候要小心(params,cookies,etc.)到(服务器)本地硬盘中的文件,因为这是一个安全风险,可能会允许有人获得并不意味着他们看到的文件。

 

It is not recommended that you stream static files through Rails if you can instead keep them in a public folder on your web server. It is much more efficient to let the user download the file directly using Apache or another web server, keeping the request from unnecessarily going through the whole Rails stack. Although if you do need the request to go through Rails for some reason, you can set the :x_sendfile option to true, and Rails will let the web server handle sending the file to the user, freeing up the Rails process to do other things. Note that your web server needs to support the X-Sendfile header for this to work.

并不推荐你stream静态文件通过Rails如果作为替代你可以存放他们在你的服务器的一个公共的文件夹中。直接使用Apache或者其他web服务器让用户下载这些文件更加有效,避免不必要的request通过整体的Rails堆栈。即使如果因为某些原因你需要request通过Rails,你可以设置:x_sendfile option to trueRails将会让web server handle发送这些文件给用户,释放Rails进程去做其他的事情。注意你的web server需要支持 X-Sendfile header来做这个工作。

11.2 RESTful Downloads

While send_data works just fine, if you are creating a RESTful application having separate actions for file downloads is usually not necessary. In REST terminology, the PDF file from the example above can be considered just another representation of the client resource. Rails provides an easy and quite sleek way of doing “RESTful downloads”. Here’s how you can rewrite the example so that the PDF download is a part of the show action, without any streaming:

send_data工作良好,如果你正在创建的一个RESTful应用程序有单独的actions来做file下载通常是不需要的。在REST术语中,来自例子中的PDF文件完全可以被认为仅仅另一种client资源的代表。Rails提供一个容易和相当光滑的方式“RESTful downloads”来做这些。这里重写例子以便于PDF下载是show action的一部分,没有任何的streaming

class ClientsController < ApplicationController

The user can request to receive this resource as HTML or PDF.

def show

@client = Client.find(params[:id])

 

respond_to do |format|

format.html

format.pdf { render :pdf => generate_pdf(@client) }

end

end

end

In order for this example to work, you have to add the PDF MIME type to Rails. This can be done by adding the following line to the file config/initializers/mime_types.rb:

为了是这个例子能够工作,你必须添加 PDF MIME typeRails。可以添加下面的代码行到 config/initializers/mime_types.rb

Mime::Type.register “application/pdf”, :pdf

Configuration files are not reloaded on each request, so you have to restart the server in order for their changes to take effect.

Now the user can request to get a PDF version of a client just by adding “.pdf” to the URL:

GET /clients/1.pdf

12 Parameter Filtering

Rails keeps a log file for each environment in the log folder. These are extremely useful when debugging what’s actually going on in your application, but in a live application you may not want every bit of information to be stored in the log file. You can filter certain request parameters from your log files by appending them to config.filter_parameters in the application configuration. These parameters will be marked [FILTERED] in the log.

Rail对于每个环境保留一个log文件在log文件夹中。这些文件相当有用,在当你调试的时候他们是在你应用程序中实际运行的记录,但是在一个live的应用程序中你可以不希望每bit的信息都被存储在log文件中。你可以从你的log文件中过滤某些request parameters,通过添加它们到 config.filter_parametersapplication configurationThese parameters will be marked [FILTERED] in the log.

config.filter_parameters << :password

13 Rescue挽救

Most likely your application is going to contain bugs or otherwise throw an exception that needs to be handled. For example, if the user follows a link to a resource that no longer exists in the database, Active Record will throw the ActiveRecord::RecordNotFound exception.

就像你的应用程序包含某些bugs或者另一方面抛出一个异常需要被处理。例如,如果用户follows一个link到一个在数据库中并不存在的resourece,Active Record将会抛出 ActiveRecord::RecordNotFound异常。

Rails’ default exception handling displays a “500 Server Error” message for all exceptions. If the request was made locally, a nice traceback and some added information gets displayed so you can figure out what went wrong and deal with it. If the request was remote Rails will just display a simple “500 Server Error” message to the user, or a “404 Not Found” if there was a routing error or a record could not be found. Sometimes you might want to customize how these errors are caught and how they’re displayed to the user. There are several levels of exception handling available in a Rails application:

Rails的默认异常处理方式是对于所有的异常显示一个“500 Server Error”消息。如果request(相关的定制)在服务器被生成,一个漂亮的traceback以及添加一些信息来显示使得你可以指出什么地方出错了并且怎样处理它。如果requestremote Rails将会仅仅显示一个简单的“500 Server Error” 消息给用户,或者如果这里有一个routing错误或者一个记录不能被发现“404 Not Found”将会发送给用户。有时你可以能希望定制这些错误怎样引起以及为什么它们被显示给用户。在Rails应用程序中这里有一些级别的异常处理可用:

13.1 The Default 500 and 404 Templates

By default a production application will render either a 404 or a 500 error message. These messages are contained in static HTML files in the public folder, in 404.html and 500.html respectively. You can customize these files to add some extra information and layout, but remember that they are static; i.e. you can’t use RHTML or layouts in them, just plain HTML.

通过一个默认生成的应用程序将会render一个404或者500错误消息。这些消息被包含在public文件夹的静态HTML文件中,分别在 404.html and 500.html。你可以定制这些文件来添加一些其他的信息和layout,但是记住他们是静态的;i.e. you can’t use RHTML or layouts in them, 仅仅是纯HTML

13.2 rescue_from

If you want to do something a bit more elaborate when catching errors, you can use rescue_from, which handles exceptions of a certain type (or multiple types) in an entire controller and its subclasses.

如果你想做一些事情在catching 错误的时候做多一点的阐述,你可以使用 rescue_from,其处理一种明确的异常类型(或者多种)在整个controller以及它的子类中。

When an exception occurs which is caught by a rescue_from directive, the exception object is passed to the handler. The handler can be a method or a Proc object passed to the :with option. You can also use a block directly instead of an explicit Proc object.

当一个异常发生其会直接引发 rescue_from,这个exception对象被传递给handler。这个handler可以是一个方法或者一个被传递给:with选项的Proc对象。你也可以直接使用一个block替代准确的Proc对象。

Here’s how you can use rescue_from to intercept all ActiveRecord::RecordNotFound errors and do something with them.

这里是你如何使用 rescue_from来截取所有的ActiveRecord::RecordNotFound errors并且为其做一些事情。

class ApplicationController < ActionController::Base

rescue_from ActiveRecord::RecordNotFound, :with => :record_not_found

 

private

 

def record_not_found

render :text => “404 Not Found”, :status => 404

end

end

Of course, this example is anything but elaborate and doesn’t improve on the default exception handling at all, but once you can catch all those exceptions you’re free to do whatever you want with them. For example, you could create custom exception classes that will be thrown when a user doesn’t have access to a certain section of your application:

当然,这个例子的所有事情仅仅只是阐述以及没有改善默认的异常处理,但是一旦你可以catch所有的异常你可以自由的做你想做的。例如,你可以创建定制的异常类它将在用户访问没有访问权限的应用程序的某些部分时候抛出:

class ApplicationController < ActionController::Base

rescue_from User::NotAuthorized, :with => :user_not_authorized

 

private

 

def user_not_authorized

flash[:error] = “You don’t have access to this section.”

redirect_to :back

end

end

 

class ClientsController < ApplicationController

Check that the user has the right authorization to access clients.

before_filter :check_authorization

 

Note how the actions don’t have to worry about all the auth stuff.

def edit

@client = Client.find(params[:id])

end

 

private

 

If the user is not authorized, just throw the exception.

def check_authorization

raise User::NotAuthorized unless current_user.admin?

end

end

Certain exceptions are only rescuable from the ApplicationController class, as they are raised before the controller gets initialized and the action gets executed. See Pratik Naik’s article on the subject for more information.

14 Force HTTPS protocol强制HTTPS协议

Sometime you might want to force a particular controller to only be accessible via an HTTPS protocol for security reason. Since Rails 3.1 you can now use force_ssl method in your controller to enforce that:

有时候因为安全原因你可能希望强制特定的cotroller仅仅在HTTPS协议下是可访问的。从Rails 3.1你可以在你的controller使用 force_ssl来强制执行:

class DinnerController

force_ssl

end

Just like the filter, you could also passing :only and :except to enforce the secure connection only to specific actions.

class DinnerController

force_ssl :only => :cheeseburger

or

force_ssl :except => :cheeseburger

end

Please note that if you found yourself adding force_ssl to many controllers, you may found yourself wanting to force the whole application to use HTTPS instead. In that case, you can set the config.force_ssl in your environment file.

请注意如果你发现你自己的添加 force_ssl到许多controllers,你可能发现你自己希望强制整个应用程序使用HTTPS替代。因为这样的原因,你可以在你的环境文件中设置config.force_ssl

controller filter guide rails ruby