rails敏捷开发《5》.doc_第1页
rails敏捷开发《5》.doc_第2页
rails敏捷开发《5》.doc_第3页
rails敏捷开发《5》.doc_第4页
rails敏捷开发《5》.doc_第5页
已阅读5页,还剩73页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第22章ActionController和RailsAction Controller and Rails 在前一章里,我们己经看到ActionController如何将用户提交的请求路由到应用程序中适当的代码。现在我们来看看代码内部发生了什么。22.1 Action方法Action Methods当控制器对象处理请求时,它会查找与“被请求的action”同名的public实例方法。如果找到,即调用此方法;如果找不到,但控制器实现了method_missing(),则调用后者,并传入action名称作为第一个(也是唯一的)参数。如果没有任何方法可以调用,控制器就寻找名称匹配的视图模板;如果找到,就直接渲染该模板。如果以上所有条件都不符合,控制器就会报告“Unknown Action”的错误。默认情况下,控制器中任何public的方法都可以作为action方法被调用。如果你希望某些方法不被作为action调用,可以将其声明为protected或者private。如果出于某些原因必须把某个方法声明为public,但又不想让它作为action被调用,也可以使用hide_action()方法将它藏起来。class OrderController ApplicationController def create_order order = Order.new(params:order) if check_credit(order) order.save else # . end end hide_action :check_credit def check_credit(order) # . endend如果你因为想要在多个控制器之间共享某些方法而使用hide_action()方法,请考虑将这些方法放到单独的库中你的控制器可能包含太多应用逻辑了。控制器环境Controller Environment控制器为action(以及它们所调用的视图)设置了一个环境。很多方法可以直接获取URL或请求中的信息。action_name当前进行处理的action名称。cookies与请求相关的cookie。当应答被送回浏览器时,这个对象中设置的值就会作为cookie保存到客户机。在第434页我们还会详细讨论cookie的问题。headers一个类似于hash的对象,代表HTTP头信息,该信息将被用于应答。默认状态下,Cache-Control值被设置为no_cache。在某些特殊的应用中,你可能需要设置Content-Type的值。请注意,不要在头信息中直接设置cookie,而应该用cookie API来设置。params一个类似于hash的对象,其中存放着请求参数(以及路由生成的伪参数)。之所以说它“类似于hash”,因为你可以用符号或者字符串来查找其中的内容params:id和paramsid会返回同样的值。(习惯上采用符号来获取参数值。)request进入控制器的请求对象,其中有用的属性包括:l request_method,返回客户端访问所使用的请求方法,可能的值包括:delete、:get、:head、:post和:put。l method返回值除了:head外,和request_method是相同的,而:head的返回值和:get相同,因为从应用角度来看,两种实现的功能是一样的。l delete?、get?、head?、post?和put?,根据当前请求所使用的方法返回true或false。l xml_http_request?和xhr?,如果请求来自Ajax辅助方法,则返回true。这个属性与method属性值无关。l url返回请求的完整URL。l protocol,host,port,path,和 query_string分别返回与URL中的相应部分,其匹配模式是:protocol:/host:port/path?query_string。l domain,返回请求地址中域名部分的最后两段。l host_with_port,返回请求对应的host:port格式的字符串。l port_string,如果端口号不是默认端口,返回:port格式的字符串。 l ssl?, 如果使用SSL请求,则返回值为true;也就是说,请求使用的是HTTPS协议。l remote_ip,以字符串的形式返回客户端的IP地址。如果客户端位于代理服务器之后,该字符串中可能包含多个IP地址。l path_without_extension path_without_format_and_extension, format_and_extension和relative_path返回路径的对应部分。l env,请求的环境。你可以通过这个属性访问到浏览器设置的环境变量值,例如:request.envHTTP_ACCEPT_LANGUAGE l accepts,请求中accepts的MIME类型。l format,请求中accepts的MIME类型。如果没有指定格式,则会使用第一个可用类型。l mime_type,与扩展名相关的MIME类型。l content_type,请求的MIME类型。对put和post请求有用。l headers,完整的HTTP头。l body, 请求的实体部分,是一个I/O流。l content_length,实体的长度。class BlogController show end render :template = fix_user_errorsend看起来很自然:调用render(或者redirect to)就应该终止action的处理。但事实并非如此。如果update_attributes的返回值为true,这段代码就会导致一个错误,因为render被调用了两次。我们来看看如何在控制器中使用不同的渲染选项(在第509页会单独介绍如何在视图中进行渲染)。render()在没有任何参数的情况下,render()方法会渲染当前action的默认模板。譬如说,下列代码会渲染app/views/blog/index.html.erb模板。class BlogController ApplicationController def index render endend下列代码的效果也是一样(如果没有显式调用render()方法,在action方法执行结束后控制器也会自动调用它)。class BlogController ApplicationController def index endend下列代码的效果也一样(如果没有定义合适的action方法,控制器会直接调用对应的模板)。class BlogController string)将指定的字符串发送给客户端,不做任何模板解释或HTML转码。class HappyController Hello there! ) endendrender(:inline =string, :type =erb|builder|rjs , :locals =hash )把string内容作为指定类型的模板源代码进行渲染,并将结果发送给客户端。如果提供了:locals参数,其中的内容会被用于给模板中的局部变量设值。下列代码在控制器中定义了method_missing()方法(仅当应用程序在开发模式下运行时有效)。如果调用控制器时指定的action名称无效,method_missing()会渲染一个内联的模板,将action的名称和格式化之后的请求参数显示出来。class SomeController % Unknown action: #name Here are the request parameters: ) end endendrender(:action =action_name)渲染当前控制器中指定的action所对应的模板。常有人误用这一特性:他们真正需要的可能是重定向,而不是渲染另一个action至于说为什么这样做不好,请看第431页的讨论。def display_cart if cart.empty? render(:action = :index) else # . endend请注意,调用render(:action)并不会调用后一个action方法,只是把模板渲染出来。如果模板需要某些实例变量,必须在调用render的方法内设置所有实例变量。再重申一遍,因为这是一个新手常犯的错误:调用render(:action)不会调用后一个action方法它只是渲染后者的默认模板。render(:file =path, :use_full_path =true|false, :locals =hash)渲染指定路径(其中必须包含文件扩展名)的模板文件。默认情况下,这里输入的路径应该是模板文件的绝对路径;但如果:use_full_path选项为true,就会从当前的视图模板根路径开始,根据输入路径去寻找视图模板。如前所述,应用程序的模板根路径是可以在配置中指定的(参见第232页的介绍)。如果指定了:local选项,其中的值会被用于给模板中的局部变量赋值render(:template =name, :locals =hash )渲染一个模板,并将结果发送给客户端。:template选项的值必须同时包含控制器和action的名字,两部分之间以斜线分隔。譬如说,下列代码会渲染名为app/views/blog/short _list的模板。class BlogController blog/short_list ) endendrender(:partial =name, .)渲染一个局部模板(partial template)。在第509页我们会深入介绍局部模板。render(:nothing = true)什么都不返回也就是说,返回一片空白给浏览器。render(:xml =stuff )把传入的参数当作文本来渲染,强制使用application/xml内容类型。render(:json =stuff, callback =hash )把传入的参数当JSON来渲染,强制使用application/xml内容类型。指定:callback 会将结果用指定的回调函数封装起来。render(:update) do |page| . end以RJS模板形式渲染一个代码块,并将页面对象传给代码块作为参数。render(:update) do |page| page:cart.replace_html :partial = cart , :object = cart page:cart.visual_effect :blind_down if cart.total_items = 1end在render()方法的各种用法中,你都可以使用:status,:layout和:content_type参数。:status参数用于设置HTTP应答的状态头信息,默认值为“200 OK”。不要在render()方法中把状态值设为3xx以达到重定向的效果,Rails提供了redirect()方法。:layout参数定义了在哪个布局模板中渲染结果(在第89页我们就看到了布局模板,在第505页还会详细介绍)。如果该参数的值为false,则不使用任何布局模板;如果值为nil或true,则只有在当前action有默认的布局模板与其对应时才使用该布局模板;如果:layout参数的值是一个字符串,该字符串会被作为布局模板的名称,Rails会查找具有该名称的布局模板并在其中渲染结果。如果使用了:nothing选项,则不会使用任何布局模板。:content_type参数让你可以指定Content-Type HTTP头信息的值,并将其传递给浏览器。有时候你会希望知道到底哪些东西被送到了用户的浏览器上,render_to_string()方法可以帮你这个忙。该方法接受的参数与render()一样,唯一的区别是它会以字符串的形式返回渲染的结果该方法不会把渲染结果放进代表应答的response对象,因此客户端也得不到渲染结果。调用render_to_string不会引发真正的渲染,你可以随后再调用一次render方法,而不会招致DoubleRender错误。发送文件和其他数据Sending Files and Other Data我们己经看到了如何在控制器中渲染模板、如何直接把字符串发送给客户端。第三种应答方式就是直接把数据(通常但并不一定是文件的内容)发送给客户端。send_data发送一个二进制数据流给客户端。send_data(data, options.)将一个数据流发送给客户端。通常浏览器需要同时知道内容的类型和处理方式(两者都可以在选项中指定),以便决定如何处理这些数据。def sales_graph png_data = Sales.plot_for(Date.today.month) send_data(png_data, :type = image/png , :disposition = inline )end选项::dispositionstring建议直接在浏览器中显示该文件(“inline” )或下载保存(attachment”,默认值):filenamestring当浏览器尝试将数据保存为文件时,该选项的值将被用作默认文件名:statusstringHTTP响应状态码(默认值为“200 OK”):typestring内容的类型,默认值是application/octet-stream:url_based_filenameboolean如果为true,并且:filename没有设置,可以防止Rails提供Content-Disposition头信息中的文件的文件名部分(不带路径和扩展名)。为了让一些浏览器能正确处理i18n的文件名,须设置此项。send_file将一个文件的内容发送给客户端If true and :filename is not set, prevents Rails from providing thebasename of the file in the Content-Dispostion header. Specifyingthis is necessary in order to make some browsers to handlei18n filenames correctlysend_file(path, options.)将指定的文件发送给客户端,方法内部会设置Content-Length、Content-Type、Content-Disposition和Content-Transfer-Encoding等HTTP头信息。:buffersizestring如果开启了数据流功能(:stream为true ),每次写入浏览器缓冲区的数据量:dispositionstring建议直接在浏览器中显示该文件(inline)或下载保存该文件(attachment,默认值):filenamestring当浏览器尝试将数据保存为文件时,该选项的值将被用作默认文件名。如果没有设置,path参数中文件名的部分将被用作默认值:statusstringHTTP响应状态码(默认值为“200 OK”):streamtrue或false如果为false,整个文件将被读入服务器的内存中,然后再发送给客户端;否则,将按照:buffer_size的大小一边读取文件、一边向客户端发送数据:typestring内容的类型,默认值是application/octet-stream使用上述两个send方法时,都可以通过控制器的headers属性设置额外的HTTP头信息。def send_secret_file send_file(/files/secret_list ) headersContent-Description = Top secretend在501页,我们还会介绍如何上传文件。重定向Redirects当服务器响应一个请求时,可以向客户端发送一个HTTP重定向命令,这就等于告诉客户端:“我不知道怎么处理这个请求,但别的某个URL知道。”重定向命令包含了一个URL地址,客户端应该尝试访问该地址。此外重定向命令中还包含了一些状态信息,代表该重定向是永久的(状态码301)还是暂时的(307)。在重新组织网页时人们经常会使用重定向,这样当用户访问旧的地址时就会被指引到新的地址上来。Rails应用中经常会用重定向将请求委派给另一个action来处理。web浏览器会在幕后处理重定向,只是因为出现略微的延迟,以及URL发生了变化,你才会知道有重定向发生了后一点很重要,因为有了浏览器的参与,重定向在服务器这边看起来就等效于最终用户手动输入了一个新的地址。如果你想要编写一个行为良好的web应用,重定向是很重要的工具。不妨想象一个简单的blog应用,其中支持用户评论。在用户提交了评论之后,我们的应用程序应该重新显示被评论的文章,并且将新加的评论显示在最后。于是,你可能很自然地写出这样的代码:class BlogController def display article = Article.find(params:id) end def add_comment article = Article.find(params:id) comment = Comment.new(params:comment) ments display ) endend想法很清晰:发布评论之后再次显示文章。为了实现这一目的,开发者在add_comment()方法中调用render(:action=display),于是这个action就会渲染display视图,将更新之后的文章展现给最终用户。但是请从浏览器的角度来考虑这个做法:它发送请求到blog/add_comment,并最终停留在这个地址,却得到一个文章浏览页面。这也就是说,如果用户点击“刷新”按钮(也许是为了看看有没有别人发表评论),浏览器就会再次访问add_comment这个地址。用户只是想刷新显示页面,应用程序得到的请求却是“添加一条评论”。在一个blog应用中,这个问题也许还不是特别严重,只不过是添加几条重复的评论而己;但如果你开发的是一个网上商店,麻烦可能就大了。在这种情况下,正确的做法应该是在添加评论之后重定向到display这个action,以显示文章和新增加的评论。用Rails提供的redirect to()方法就可以达到这一效果。如果用户稍后又点击了“刷新”按钮,那也只是重新调用display这个action,而不会再添加新的评论。def add_comment article = Article.find(params:id) comment = Comment.new(params:comment) ments display)endRails提供了简单而强大的重定向机制。你可以将请求重定向到指定的action(并且将参数传递过去),也可以重定向到任何URL(不论它是否位于当前的服务器上),还可以重定向到用户所访问的前一个页面。下面我们依次看看这三种重定向的方式。redirect_to重定向到一个action。redirect_to(:action = ., options.)根据options(这是一个hash)中的值发送一个暂时性的重定向指令给浏览器。目标URL是用url_for()方法生成的,所以像这样使用redirect_to()将得到Rails路由机制的所有好处。(关于路由机制,请看第392页第20.2节,“请求的路由”。)redirect_to重定向到一个URL。redirect_to(path)重定向到指定的路径。如果路径不以协议字符串(例如“http:/”)开头,则会沿用当前请求的协议和端口号。这个方法不做任何URL改写,所以请不要用它来导向应用程序内部的action(除非用url_for方法或者具名路由URL生成器来生成路径)。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 flash:notice = Please try again else # Give up - user is clearly struggling redirect_to(/help/order_entry.html ) end endendredirect_to返回前一个页面。redirect_to(:back)重定向到当前请求的HTTP_REFERER头信息所指定的URL。def save_details unless params:are_you_sure = Y redirect_to(:back) else . endend默认状态下,所有的重定向都会被标记为暂时性的(只对当前请求有效)。你也可以将重定向标记为永久性的,只需要设置对应的应答头信息即可。headersStatus = 301 Moved Permanentlyredirect_to(http:/my.new.home )因为重定向会发送应答给浏览器,所以就像渲染结果一样,针对每次请求最多只能做一次重定向。22.2 Cookie和SessionCookies and Sessions借助cookie,web应用可以从浏览器那里得到类似于hash的功能:你可以把具名的字符串保存在客户端浏览器中,需要时再从后续请求中取出cookie的值。这一特性非常重要,因为HTTP协议本身是无状态的,cookie则提供了一种突破这一限制的途径,让web应用能够跨越多次请求维护特定客户端的状态数据。Rails对cookie进行了抽象,开发者只需要使用一个简单便利的接口即可。控制器中的cookies属性是一个类似于hash的对象,它对cookie协议进行了封装。当接收到请求时,cookies对象会根据浏览器发送的cookie名称和值进行初始化。Action方法可以随时向cookies对象中添加新的名/值对,这些新的cookie数据都会在请求处理结束之后发送给浏览器,应用程序在处理后续的请求时就可以访问到这些数据(cookie数据必须满足一些约束条件,我们稍后就会介绍)。下面就是一个简单的Rails控制器,其中保存了一个cookie,然后重定向到另一个action。请记住,重定向的过程需要到浏览器那里走一个来回,因此应用程序会新建一个控制器对象来处理后续的请求。新的action得到了浏览器送来的cookie值,然后将其显示出来。e1/cookies/cookie1/app/controllers/cookies_controller.rbclass CookiesController action_two end def action_two cookie_value = cookies:the_time render(:text = The cookie says it is #cookie_value ) endendcookie的值必须是字符串,Rails不会帮你做任何隐式转型。如果你传入别的类型,可能会得到一个模糊不清的错误,其中包含“private method gsub called”之类的信息。浏览器会给每个cookie加上一些附加信息:有效期、有效路径,以及适用的域名等。如果你给cookiesname赋上一个值从而创建一个cookie,就会得到这些选项的默认值:cookie对整个网站有效、永不过期,并且适用于创建cookie的域名。当然,你也可以传入一个hash来覆盖这些默认值。(在下面的例子中,我们用了#days.from_now这个漂亮的扩展来得到一个Fixnum对象,详情请看第243页第15章中的Active Support”一节。)cookies:marsupial = :value = wombat , :expires = 30.days.from_now, :path = /store 可用的选项包括:domain、:expires、:path、:secure和:value。:domain和:path选项指定了cookie的有效范围如果后续请求的地址位于cookie的有效域名中,并且路径又是位于cookie的有效路径下,那么浏览器就会把cookie发送给服务器。:expire选项设置了cookie的生命时间限制。:expire选项的值可以是一个绝对时间,如果客户机时间超过该时间 这是一个绝对时间,当cookie创建时就会设置进去。如果需要指定cookie在用户的最后一次请求之后多少分钟过期,你可以在每次请求之后重设cookie,也可以更好的办法是把cookie过期时间保存在session数据中,然后在那里更新。,浏览器就会将cookie删除;该选项的值也可以是一个空字符串,此时浏览器就只把cookie保存在内存中,在会话结束之后便将其删除。如果没有指定过期时间,cookie就是永久有效的。最后,:secure选项会告诉浏览器:只有当使用https:/发送请求时,才可以将cookie发回服务器。cookie很适合用于在浏览器上保存一些短小的字符串,但并不适于保存大量结构化的数据你应该用session来保存这些东西。Cookie检测Cookie Detection使用cookie的问题在于:有些用户在浏览器中禁止了对cookie的支持,所以你需要精心设计你的应用程序,让它在不能使用cookie的情况下也同样可靠(不一定要保证全部功能都可用,但有必要保证不会丢失或破坏数据)。这并不难,但要好几个步骤。最终结果对于大多数用户来说是透明的:两个快速重定向在第一次访问站点时和session建立时。首先要在config/environment.rb文件中设置一个可以在应用中引用的SESSION_KEY常量,这个值现在是硬编码在config.action_controller.session中,我们需要重构它:Download e1/cookies/cookie2/config/environment.rb# Be sure to restart your server when you modify this file# # Uncomment below to force Rails into production mode when# you dont control web/app server and cant set it the proper way# ENVRAILS_ENV |= production# # Specifies gem version of Rails to use when vendor/rails is not presentRAILS_GEM_VERSION = 2.2.2 unless defined? RAILS_GEM_VERSION# Bootstrap the Rails environment, frameworks, and default configurationrequire File.join(File.dirname(_FILE_), boot )# Define session key as a constant SESSION_KEY = _cookie2_sessionRails:Initializer.run do |config| # . # # 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 youll be exposed to dictionary attacks. config.action_controller.session = :session_key = SESSION_KEY, :secret = cbd4061f23fe1dfeb087dd38c.888cdd186e23c9d7137606801cb7665 # .end现在还需做两件事情。首先,要给应用中需要使用cookie的action定义一个cookies_required过滤器。简单起见,我们为除过cookie测试外的其它action定义这样一个过滤器。如果你有不同的需求,请做相应调整。此方法在session已经定义时什么都不做,如果没有定义,它会试图将请求URL存放在一个新的session中并重定向到cookie测试action中。接着是定义cookies_test方法。这个方法也是直截了当的:如果没有session,就在log中记录,然后渲染一个简单模板告诉用户需要使用cookie才能正常工作。这时候如果有session,只需重定向回去(注意要提供一个默认的指向以防万一有些捣蛋鬼直接访问这个action),然后从session中删除这些信息。按照请求调整默认指向: Download e1/cookies/cookie2/app/controllers/application.rb# Filters added to this controller apply to all controllers in the application.# Likewise, all the methods added will be available for all controllers.class ApplicationController :cookies_test # See ActionController:RequestForgeryProtection for details # Uncomment the :secret if youre not using the cookie session store protect_from_forgery # :secret = cde978d90e26e7230552d3593d445759 # # See ActionController:Base for details # Uncomment this to filter the contents of submitted sensitive data parameters # from your application log (in this case, all fields with names like password). # filter_parameter_logging :password def cookies_test if request.cookiesSESSION_KEY.blank? logger.warn(* Cookies are disabled for #request.remote_ip at #Time.now ) render :template = cookies_required else redirect_to(session:return_to | :controller = store ) session:return_to = nil end end protected def cookies_required return unless request.cookiesSESSION_KEY.blank? session:return_to = request.request_uri redirect_to :action = cookies_test endend还有最后一件事情要做。虽然前面都表现的很好,但它有一个副作用:几乎所有的功能测试都会失败,因为不管以前它们是要做什么,都会重定向到cookie测试中。这个问题可以通过人为构造一个session来解决。当然,任何一个有自尊的敏捷开发人员都会创建一个针对cookie测试本身的功能测试:Download e1/cookies/cookie2/test/functional/store_controller_test.rbrequire test_helperclass StoreControllerTest ActionController:TestCase def setup request.cookiesSESSION_KEY = faux session end test existing functional tests should continue to work do get :hello assert_response :success end test when cookies are disabled a redirect results do request.cookies.delete SESSION_KEY get :hel

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论