Rails源码翻阅(十)在console 使用ActionController:Integration:Session
Rails源码阅读(十)在console 使用ActionController::Integration::Session
ActionController::Integration::Session
在script/console的console_app中,使用的句柄是app,返回ActionController::Integration::Session的一个实例。
可以使用的方法1:模拟请求
app的public方法:
delete
delete_via_redirect
follow_redirect!
get
get_via_redirect
head
host!
https!
https?
new
post
post_via_redirect
put
put_via_redirect
redirect?
request_via_redirect
reset!
url_for
xhr
xml_http_request
这些都是模拟http请求的。
例如get方法,模拟get请求,path参数是请求的地址等。
def get(path, parameters = nil, headers = nil) process :get, path, parameters, headers end
实验:
?> status = app.get '/login'
=> 200
process方法代码分析:
#核心代码 app = Rack::Lint.new(@application) status, headers, body = app.call(env)
从@application是ActionDispatcher的实例可以看出,启动了rack,来处理用户的get post等请求。
这段代码之前主要是rack的准备工作;
这段代码之后的代码在处理rack的返回值工作;
另外,为了配合集成测试,需要在处理请求后,可以做assert操作,这就需要能够得到请求的轨迹,也就是请求了哪个controller,哪个action,request参数,response值等,也就是:
if @controller = ActionController::Base.last_instantiation @request = @controller.request @response = @controller.response @controller.send(:set_test_assigns) #这里使用了module ProcessWithTest else # Decorate responses from Rack Middleware and Rails Metal # as an Response for the purposes of integration testing @response = Response.new @response.status = status.to_s @response.headers.replace(@headers) @response.body = @body end # Decorate the response with the standard behavior of the # TestResponse so that things like assert_response can be # used in integration tests. @response.extend(TestResponseBehavior) #response都加了这些功能
请求的返回值是:return @status
可以使用的方法2: 单元测试
Included Modules包括:
Test::Unit::Assertions
ActionController::TestCase::Assertions
ActionController::TestProcess
这样,可以在app中使用上面3个测试模块的方法。
例如:
app.assert true
app.assert false
实验:
?> app.assert true
=> nil
>> app.assert false
Test::Unit::AssertionFailedError: <false> is not true.
from /opt/ruby/lib/ruby/1.8/test/unit/assertions.rb:48:in `assert_block'
from /opt/ruby/lib/ruby/1.8/test/unit/assertions.rb:500:in `_wrap_assertion'
from /opt/ruby/lib/ruby/1.8/test/unit/assertions.rb:46:in `assert_block'
from /opt/ruby/lib/ruby/1.8/test/unit/assertions.rb:63:in `assert'
from /opt/ruby/lib/ruby/1.8/test/unit/assertions.rb:495:in `_wrap_assertion'
from /opt/ruby/lib/ruby/1.8/test/unit/assertions.rb:61:in `assert'
from (irb):97
from :0
可以使用的方法3:命名路由
初始化的源代码:
# Create and initialize a new Session instance. def initialize(app = nil) @application = app || ActionController::Dispatcher.new reset! end
这里的实例@application是ActionController::Dispatcher的实例,这个实例是rails的入口(详细看前面文)
reset!方法中除了做了些初始化的操作(例如把host设置为"www.example.com"),还包含了命名路由:
unless defined? @named_routes_configured # install the named routes in this session instance. klass = class << self; self; end #取到单例类 Routing::Routes.install_helpers(klass) #装载了命名路由 # the helpers are made protected by default--we make them public for # easier access during testing and troubleshooting. klass.module_eval { public *Routing::Routes.named_routes.helpers } #!!! @named_routes_configured = true
这样,在console中可以使用命名路由了,当然可以测试。例如:
>> app.login_path
=> "/login"
>> app.login_url
=> "http://www.example.com/login"
注意:
klass = class << self; self; end #注意这句与self.class的区别
klass.module_eval { public *Routing::Routes.named_routes.helpers } #这句真不赖
直接把命名路由弄成了共有方法,详细:
Routing::Routes.named_routes.helpers返回一个Array,*星号的作用是展开作为参数,而public正好接收这些参数,真是个好东西
>> ActionController::Routing::Routes.named_routes.helpers.class
=> Array
可以使用的方法4:属性方法
[RW] accept The Accept header to send.
[RW] application Rack application to use
[R] body The body of the last request.
[R] controller A reference to the controller instance used by the last request.
[R] cookies A map of the cookies returned by the last response, and which will be sent with the next request.
[R] headers A map of the headers returned by the last response.
[RW] host The hostname used in the last request.
[R] path The URI of the last request.
[RW] remote_addr The remote_addr used in the last request.
[R] request A reference to the request instance used by the last request.
[RW] request_count A running counter of the number of requests processed.
[R] response A reference to the response instance used by the last request.
[R] status The integer HTTP status code of the last request.
[R] status_message The status message that accompanied the status code of the last request.
例如:
?> app.path
=> "/login"
>> app.remote_addr
=> "127.0.0.1"
如何使用好这些属性和其提供的方法,还要看对每一个对象的了解。
可以使用的方法5: ActionController::Base引入的方法
[ControllerCapture, ActionController::ProcessWithTest].each do |mod| unless ActionController::Base < mod ActionController::Base.class_eval { include mod } end end
# A module used to extend ActionController::Base, so that integration tests # can capture the controller used to satisfy a request. module ControllerCapture #:nodoc: def self.included(base) base.extend(ClassMethods) base.class_eval do class << self alias_method_chain :new, :capture #重写方法,见下面【1】处调用 end end end module ClassMethods #:nodoc: mattr_accessor :last_instantiation def clear_last_instantiation! self.last_instantiation = nil end def new_with_capture(*args) controller = new_without_capture(*args) #这里调用了【1】 self.last_instantiation ||= controller #注意下这里!!! controller end end end
if @controller = ActionController::Base.last_instantiation @request = @controller.request @response = @controller.response @controller.send(:set_test_assigns) else # Decorate responses from Rack Middleware and Rails Metal # as an Response for the purposes of integration testing @response = Response.new @response.status = status.to_s @response.headers.replace(@headers) @response.body = @body end
module ProcessWithTest #:nodoc: def self.included(base) base.class_eval { attr_reader :assigns } end def process_with_test(*args) process(*args).tap { set_test_assigns } #这里用tap方法了 end private def set_test_assigns @assigns = {} (instance_variable_names - self.class.protected_instance_variables).each do |var| name, value = var[1..-1], instance_variable_get(var) @assigns[name] = value #主要作用就是设置可访问的assigns response.template.assigns[name] = value if response end end end
Yields x to the block, and then returns x. The primary purpose of this method is to “tap into” a method chain, in order to perform operations on intermediate results within the chain.