天天看点

Rails每周闲碎(三): Controller

1. Filter

    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. Or to do localization or output compression after the action has been performed. 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).

    Filter的功能很强大,包括before filter,after filter和aroud filter;filter的类型可以是一个方法,一个proc,也可以是一个类或者对象;filter还可以定制顺序,成为filter chain。

   具体详情还是请见:http://api.rubyonrails.org/classes/ActionController/Filters/ClassMethods.html

    Filter的顺序

    有时候我们会关心filter之间的顺序。比如我们定义了一个transaction filter(around filter),这个filter的作用是在非get的请求上包一个transaction,以保证数据的一致性。

    如果我们还定义了一个after filter,它会插入一些新数据或者对数据做一些变更。我们同样希望它能在transaction内,这时候filter之间的顺序就很重要。

    对同一类filter,Rails可以定制它们的顺序(请见http://api.rubyonrails.org/classes/ActionController/Filters/ClassMethods.html )。但对不同类filter之间的顺序,rails并没有提供api来定制它们的顺序。就比如上面所提到的一个around filter和一个after filter之间的顺序。

    根据观察,它们之间的顺序应该跟声明顺序有关:声明越在前的filter越在filter chain的内部。以上面那个为例,如果transaction filter先于OneAfterFilter定义:

OneBeforeFilter#before  
      TransactionFilter#before
        # run the action
      TransactionFilter#after
    OneAfterFilter#after
           

    Transaction并没有把OneAfterFilter包括在内。但这没有保证。所以在使用需要明确顺序的filter时要注意。

2. Ajax redirect?

   不要奢望用normal http response的那种方式来redirect(通过response header),ajax的机制是不一样的,看看下面这段:

   XMLHttpRequest (XHR ) is a DOM API that can be used inside a web browser scripting language , such as JavaScript , to send an HTTP or an HTTPS request directly to a web server and load the server response data directly back into the scripting language [ 1] . Once the data is within the scripting language, it is available as both an XML document, if the response was valid XML markup [ 2] , and as plain text [ 3] . The XML data can be used to manipulate the currently active document in the browser window without the need of the client loading a new web page document. Plain text data can be evaluated within the scripting language to manipulate the document, too; in the example of JavaScript, the plain text may be formatted as JSON by the web server and evaluated within JavaScript to create an object of data for use on the current DOM.

   那如何在ajax response中实现redirect?rails的代码很简单:

render :update do |page|
  page.redirect_to some_path
end
           

  背后的实现:

def redirect_to(location)
  # some other code
  record "window.location.href = #{url.inspect}"
end
           

3. ActionController::Streaming

    Methods for sending files and streams to the browser instead of rendering.

  • send_data
  • send_file

     http://guides.rubyonrails.org/action_controller_overview.html#streaming-and-file-downloads

     特别注意到上面这个页面中提及的RESTful url的例子,对于下载一个client的pdf的行为,并不需要定义一个新的action:

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 
           

   在config/initializers/mime_types.rb :注册一种mime type:

Mime::Type.register "application/pdf", :pdf 
           

   url请求:

GET /clients/1.pdf 
           

4 . render :partial => 'shared/search', locals => {:objects => ... }

   Why do we need locals ? Partial is designed for reuse , so, you donot have to have the same name instance variable in your action, and locals make the reuse possible.

5. respond_to

respond_to do |format|
      format.html # do nothing, allow Rails to render index.rhtml
      format.js # do nothing, allow Rails to render index.rjs
      format.xml { render :xml => @some_thing.to_xml }
end
           

    根据HTTP头部的Accept-Type值进行相应操作。

6.  Security

    在ApplicationController里面有一句

# See ActionController::RequestForgeryProtection for details
  # Uncomment the :secret if you're not using the cookie session store
  protect_from_forgery # :secret => 'aacb6ea3f7484b1a5b56c2d8f45121c3'
           

   这是对Cross-site request forgery 的一种防御。

   对于由rails的一些html helper方法生成的html里面(比如form_for等post相关的html),会自动加上一个hidden的参数authenticity_token:

<% form_for @user do |f| -%>  
  <%= f.text_field :username %>  
  <%= f.text_field :password -%> 
<% end -%> 
           
<form action="/users/1" method="post"> 
  <input type="hidden" value="67250ab105eb5ad10851c00a5621854a23af5489"  
     name="authenticity_token"/> 
  <!-- fields --> 
</form> 
           

   对于不能自动生成的地方,可以用form_authenticity_token方法生成 authenticity_token。

7. Access controller information in views.

    controller.controller_path => Converts the class name from something like "OneModule::TwoModule::NeatController" to "one_module/two_module/neat".

    controller.controller_name => Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat".

    controller.controller_class_name  => Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController".

    Of course, you can access this in the controller directly.

8. Parameters

   Rails对get和post请求的参数并不做区分。

   Rails对Array和Hash类的参数有很方便的支持:

GET /clients?ids[]=1&ids[]=2&ids[]=3
           
<form 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> 
           

    Prameter还有另外一种形式,routes中的parameter。这种参数出现在url中,但不跟上面query string的参数一样:

map.connect "/clients/:status"
           

    另外,我们有时候需要给每个url都加上一个默认参数:

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 
           

9. Session

   http://guides.rubyonrails.org/action_controller_overview.html#session

   介绍了几种session存储方式,以及如何配置session存储方式:

# 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 "rake db:sessions:create") 
# ActionController::Base.session_store = :active_record_store 
           

    最重要的,介绍了CookieStore这种方式:

# Your secret key for verifying cookie session data integrity. 
# If you change this key, all old sessions 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. 
ActionController::Base.session = {  
  :key => '_yourappname_session',  
  :secret => '4f50711b8f0f49572...' } 
           

    同时,提到session的获取是一种lazy load的方式,所以不需要因为性能问题在任何时候关闭。

10. Cookie

    http://guides.rubyonrails.org/action_controller_overview.html#cookies

    介绍了cookie的add和remove,跟session一样,它也可以像一个hash一样处理。

11. Verification

    http://guides.rubyonrails.org/action_controller_overview.html#verification

class LoginsController < ApplicationController 
  verify :params => [:username, :password],  
           :render => {:action => "new"},  
           :add_flash => {  :error => "Username and password required to log in"  },  
            :only => :create # Run only for the "create" action 
end 
           

12. Rescue

   匹配exception和错误页面。

   http://guides.rubyonrails.org/action_controller_overview.html#rescue

13. head

     returns a response that has no content (merely headers): http://api.rubyonrails.org/classes/ActionController/Base.html#M000660

继续阅读