`
fantaxy025025
  • 浏览: 1250205 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类

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
  
其中ControllerCapture,主要作用注释写了已经:
    # 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
用处请看process方法内的一处:
          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
 
ProcessWithTest如下,主要作用就是设置可访问的assigns
  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

附:
Tap方法:

 

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.

>> (1..10).tap { |x| puts "original: #{x.inspect}" }.to_a.
?>     tap    { |x| puts "array: #{x.inspect}" }.
?>     select { |x| x%2 == 0 }.
?>     tap    { |x| puts "evens: #{x.inspect}" }.
?>     map    { |x| x*x }.
?>     tap    { |x| puts "squares: #{x.inspect}" }
original: 1..10
array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens: [2, 4, 6, 8, 10]
squares: [4, 16, 36, 64, 100]
=> [4, 16, 36, 64, 100]



====完毕====
+
+
+
+




分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics