diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2009-09-30 22:27:02 +0100 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2009-09-30 22:27:02 +0100 |
commit | dd2779e1b83b4d867d47dd286ec0c919f5df12a9 (patch) | |
tree | 6e52ea0a329c24429f4d1d41b065e082f0ed6baa /actionpack/lib | |
parent | 329b14aa8fdd291a00d17ba12c2e0ab4c3a157cc (diff) | |
parent | 420004e030e96f2ace6e27fd622c90ee9e986677 (diff) | |
download | rails-dd2779e1b83b4d867d47dd286ec0c919f5df12a9.tar.gz rails-dd2779e1b83b4d867d47dd286ec0c919f5df12a9.tar.bz2 rails-dd2779e1b83b4d867d47dd286ec0c919f5df12a9.zip |
Merge commit 'mainstream/master'
Diffstat (limited to 'actionpack/lib')
27 files changed, 589 insertions, 321 deletions
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 634206e065..6702cb47f8 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -1,10 +1,12 @@ module ActionController autoload :Base, "action_controller/base" + autoload :Benchmarking, "action_controller/metal/benchmarking" autoload :ConditionalGet, "action_controller/metal/conditional_get" + autoload :Helpers, "action_controller/metal/helpers" autoload :HideActions, "action_controller/metal/hide_actions" + autoload :Layouts, "action_controller/metal/layouts" autoload :Metal, "action_controller/metal" autoload :Middleware, "action_controller/middleware" - autoload :Layouts, "action_controller/metal/layouts" autoload :RackConvenience, "action_controller/metal/rack_convenience" autoload :Rails2Compatibility, "action_controller/metal/compatibility" autoload :Redirector, "action_controller/metal/redirector" @@ -12,17 +14,18 @@ module ActionController autoload :RenderOptions, "action_controller/metal/render_options" autoload :Rescue, "action_controller/metal/rescuable" autoload :Responder, "action_controller/metal/responder" + autoload :Session, "action_controller/metal/session" autoload :Testing, "action_controller/metal/testing" autoload :UrlFor, "action_controller/metal/url_for" - autoload :Session, "action_controller/metal/session" - autoload :Helpers, "action_controller/metal/helpers" # Ported modules # require 'action_controller/routing' autoload :Caching, 'action_controller/caching' autoload :Dispatcher, 'action_controller/dispatch/dispatcher' - autoload :Integration, 'action_controller/testing/integration' + autoload :Integration, 'action_controller/deprecated/integration_test' + autoload :IntegrationTest, 'action_controller/deprecated/integration_test' autoload :MimeResponds, 'action_controller/metal/mime_responds' + autoload :PerformanceTest, 'action_controller/deprecated/performance_test' autoload :PolymorphicRoutes, 'action_controller/routing/generation/polymorphic_routes' autoload :RecordIdentifier, 'action_controller/record_identifier' autoload :Resources, 'action_controller/routing/resources' @@ -42,7 +45,6 @@ module ActionController autoload :Cookies, 'action_controller/metal/cookies' autoload :ActionControllerError, 'action_controller/metal/exceptions' - autoload :SessionRestoreError, 'action_controller/metal/exceptions' autoload :RenderError, 'action_controller/metal/exceptions' autoload :RoutingError, 'action_controller/metal/exceptions' autoload :MethodNotAllowed, 'action_controller/metal/exceptions' diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index f5bd0a00a1..5338a70104 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -14,6 +14,7 @@ module ActionController include ActionController::Layouts include ActionController::ConditionalGet include ActionController::RackConvenience + include ActionController::Benchmarking # Legacy modules include SessionManagement diff --git a/actionpack/lib/action_controller/deprecated/integration_test.rb b/actionpack/lib/action_controller/deprecated/integration_test.rb new file mode 100644 index 0000000000..86336b6bc4 --- /dev/null +++ b/actionpack/lib/action_controller/deprecated/integration_test.rb @@ -0,0 +1,2 @@ +ActionController::Integration = ActionDispatch::Integration +ActionController::IntegrationTest = ActionDispatch::IntegrationTest diff --git a/actionpack/lib/action_controller/deprecated/performance_test.rb b/actionpack/lib/action_controller/deprecated/performance_test.rb new file mode 100644 index 0000000000..fcf47d31a7 --- /dev/null +++ b/actionpack/lib/action_controller/deprecated/performance_test.rb @@ -0,0 +1 @@ +ActionController::PerformanceTest = ActionDispatch::PerformanceTest diff --git a/actionpack/lib/action_controller/dispatch/dispatcher.rb b/actionpack/lib/action_controller/dispatch/dispatcher.rb index ba316b9e63..e04da42637 100644 --- a/actionpack/lib/action_controller/dispatch/dispatcher.rb +++ b/actionpack/lib/action_controller/dispatch/dispatcher.rb @@ -7,15 +7,6 @@ module ActionController cattr_accessor :prepare_each_request self.prepare_each_request = false - cattr_accessor :router - self.router = Routing::Routes - - cattr_accessor :middleware - self.middleware = ActionDispatch::MiddlewareStack.new do |middleware| - middlewares = File.join(File.dirname(__FILE__), "middlewares.rb") - middleware.instance_eval(File.read(middlewares), middlewares, 1) - end - class << self def define_dispatcher_callbacks(cache_classes) unless cache_classes @@ -24,7 +15,7 @@ module ActionController # Development mode callbacks ActionDispatch::Callbacks.before_dispatch do |app| - ActionController::Dispatcher.router.reload + ActionController::Routing::Routes.reload end ActionDispatch::Callbacks.after_dispatch do @@ -58,7 +49,8 @@ module ActionController :to => ActionDispatch::Callbacks def new - @@middleware.build(@@router) + # DEPRECATE Rails application fallback + Rails.application end end end diff --git a/actionpack/lib/action_controller/dispatch/middlewares.rb b/actionpack/lib/action_controller/dispatch/middlewares.rb deleted file mode 100644 index 5641b3cb8d..0000000000 --- a/actionpack/lib/action_controller/dispatch/middlewares.rb +++ /dev/null @@ -1,16 +0,0 @@ -use "Rack::Lock", :if => lambda { - !ActionController::Base.allow_concurrency -} - -use "ActionDispatch::ShowExceptions", lambda { ActionController::Base.consider_all_requests_local } -use "ActionDispatch::Callbacks", lambda { ActionController::Dispatcher.prepare_each_request } - -# TODO: Redirect global exceptions somewhere? -# use "ActionDispatch::Rescue" - -use lambda { ActionController::Base.session_store }, - lambda { ActionController::Base.session_options } - -use "ActionDispatch::ParamsParser" -use "Rack::MethodOverride" -use "Rack::Head" diff --git a/actionpack/lib/action_controller/metal/benchmarking.rb b/actionpack/lib/action_controller/metal/benchmarking.rb new file mode 100644 index 0000000000..d4cb1e122d --- /dev/null +++ b/actionpack/lib/action_controller/metal/benchmarking.rb @@ -0,0 +1,92 @@ +require 'benchmark' + +module ActionController #:nodoc: + # The benchmarking module times the performance of actions and reports to the logger. If the Active Record + # package has been included, a separate timing section for database calls will be added as well. + module Benchmarking #:nodoc: + extend ActiveSupport::Concern + + module ClassMethods + # Log and benchmark the workings of a single block and silence whatever logging that may have happened inside it + # (unless <tt>use_silence</tt> is set to false). + # + # The benchmark is only recorded if the current level of the logger matches the <tt>log_level</tt>, which makes it + # easy to include benchmarking statements in production software that will remain inexpensive because the benchmark + # will only be conducted if the log level is low enough. + def benchmark(title, log_level = Logger::DEBUG, use_silence = true) + if logger && logger.level == log_level + result = nil + ms = Benchmark.ms { result = use_silence ? silence { yield } : yield } + logger.add(log_level, "#{title} (#{('%.1f' % ms)}ms)") + result + else + yield + end + end + end + + protected + def render(*args, &block) + if logger + if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected? + db_runtime = ActiveRecord::Base.connection.reset_runtime + end + + render_output = nil + @view_runtime = Benchmark.ms { render_output = super } + + if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected? + @db_rt_before_render = db_runtime + @db_rt_after_render = ActiveRecord::Base.connection.reset_runtime + @view_runtime -= @db_rt_after_render + end + + render_output + else + super + end + end + + private + def process_action(*args) + if logger + ms = [Benchmark.ms { super }, 0.01].max + logging_view = defined?(@view_runtime) + logging_active_record = Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected? + + log_message = 'Completed in %.0fms' % ms + + if logging_view || logging_active_record + log_message << " (" + log_message << view_runtime if logging_view + + if logging_active_record + log_message << ", " if logging_view + log_message << active_record_runtime + ")" + else + ")" + end + end + + log_message << " | #{response.status}" + log_message << " [#{complete_request_uri rescue "unknown"}]" + + logger.info(log_message) + response.headers["X-Runtime"] = "%.0f" % ms + else + super + end + end + + def view_runtime + "View: %.0f" % @view_runtime + end + + def active_record_runtime + db_runtime = ActiveRecord::Base.connection.reset_runtime + db_runtime += @db_rt_before_render if @db_rt_before_render + db_runtime += @db_rt_after_render if @db_rt_after_render + "DB: %.0f" % db_runtime + end + end +end diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb index d0811254cb..b9d23da3e0 100644 --- a/actionpack/lib/action_controller/metal/exceptions.rb +++ b/actionpack/lib/action_controller/metal/exceptions.rb @@ -2,9 +2,6 @@ module ActionController class ActionControllerError < StandardError #:nodoc: end - class SessionRestoreError < ActionControllerError #:nodoc: - end - class RenderError < ActionControllerError #:nodoc: end diff --git a/actionpack/lib/action_controller/testing/process.rb b/actionpack/lib/action_controller/testing/process.rb index 2fccf01040..bbc7f3c8f9 100644 --- a/actionpack/lib/action_controller/testing/process.rb +++ b/actionpack/lib/action_controller/testing/process.rb @@ -1,78 +1,7 @@ -require 'action_dispatch' -require 'rack/session/abstract/id' require 'active_support/core_ext/object/conversions' +require "rack/test" module ActionController #:nodoc: - class TestRequest < ActionDispatch::TestRequest #:nodoc: - def initialize(env = {}) - super - - self.session = TestSession.new - self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => ActiveSupport::SecureRandom.hex(16)) - end - - def assign_parameters(controller_path, action, parameters) - parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action) - extra_keys = ActionController::Routing::Routes.extra_keys(parameters) - non_path_parameters = get? ? query_parameters : request_parameters - parameters.each do |key, value| - if value.is_a? Fixnum - value = value.to_s - elsif value.is_a? Array - value = ActionController::Routing::PathSegment::Result.new(value) - end - - if extra_keys.include?(key.to_sym) - non_path_parameters[key] = value - else - path_parameters[key.to_s] = value - end - end - - params = self.request_parameters.dup - - %w(controller action only_path).each do |k| - params.delete(k) - params.delete(k.to_sym) - end - - data = params.to_query - @env['CONTENT_LENGTH'] = data.length.to_s - @env['rack.input'] = StringIO.new(data) - end - - def recycle! - @formats = nil - @env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ } - @env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ } - @env['action_dispatch.request.query_parameters'] = {} - end - end - - class TestResponse < ActionDispatch::TestResponse - def recycle! - @status = 200 - @header = {} - @writer = lambda { |x| @body << x } - @block = nil - @length = 0 - @body = [] - @charset = nil - @content_type = nil - - @request = @template = nil - end - end - - class TestSession < ActionDispatch::Session::AbstractStore::SessionHash #:nodoc: - DEFAULT_OPTIONS = ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS - - def initialize(session = {}) - replace(session.stringify_keys) - @loaded = true - end - end - # Essentially generates a modified Tempfile object similar to the object # you'd get from the standard library CGI module in a multipart # request. This means you can use an ActionController::TestUploadedFile @@ -87,75 +16,6 @@ module ActionController #:nodoc: TestUploadedFile = Rack::Test::UploadedFile module TestProcess - def self.included(base) - # Executes a request simulating GET HTTP method and set/volley the response - def get(action, parameters = nil, session = nil, flash = nil) - process(action, parameters, session, flash, "GET") - end - - # Executes a request simulating POST HTTP method and set/volley the response - def post(action, parameters = nil, session = nil, flash = nil) - process(action, parameters, session, flash, "POST") - end - - # Executes a request simulating PUT HTTP method and set/volley the response - def put(action, parameters = nil, session = nil, flash = nil) - process(action, parameters, session, flash, "PUT") - end - - # Executes a request simulating DELETE HTTP method and set/volley the response - def delete(action, parameters = nil, session = nil, flash = nil) - process(action, parameters, session, flash, "DELETE") - end - - # Executes a request simulating HEAD HTTP method and set/volley the response - def head(action, parameters = nil, session = nil, flash = nil) - process(action, parameters, session, flash, "HEAD") - end - end - - def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET') - # Sanity check for required instance variables so we can give an - # understandable error message. - %w(@controller @request @response).each do |iv_name| - if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil? - raise "#{iv_name} is nil: make sure you set it in your test's setup method." - end - end - - @request.recycle! - @response.recycle! - @controller.response_body = nil - @controller.formats = nil - @controller.params = nil - - @html_document = nil - @request.env['REQUEST_METHOD'] = http_method - - parameters ||= {} - @request.assign_parameters(@controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters) - - @request.session = ActionController::TestSession.new(session) unless session.nil? - @request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash - - @controller.request = @request - @controller.params.merge!(parameters) - build_request_uri(action, parameters) - Base.class_eval { include Testing } - @controller.process_with_new_base_test(@request, @response) - @response - end - - def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil) - @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' - @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') - returning __send__(request_method, action, parameters, session, flash) do - @request.env.delete 'HTTP_X_REQUESTED_WITH' - @request.env.delete 'HTTP_ACCEPT' - end - end - alias xhr :xml_http_request - def assigns(key = nil) assigns = {} @controller.instance_variable_names.each do |ivar| @@ -182,16 +42,6 @@ module ActionController #:nodoc: @response.redirect_url end - def build_request_uri(action, parameters) - unless @request.env['REQUEST_URI'] - options = @controller.__send__(:rewrite_options, parameters) - options.update(:only_path => true, :action => action) - - url = ActionController::UrlRewriter.new(@request, parameters) - @request.request_uri = url.rewrite(options) - end - end - def html_document xml = @response.content_type =~ /xml$/ @html_document ||= HTML::Document.new(@response.body, false, xml) @@ -249,7 +99,6 @@ module ActionController #:nodoc: temporary_routes = ActionController::Routing::RouteSet.new ActionController::Routing.module_eval { const_set :Routes, temporary_routes } - ActionController::Dispatcher.router = temporary_routes yield temporary_routes ensure @@ -257,7 +106,6 @@ module ActionController #:nodoc: ActionController::Routing.module_eval { remove_const :Routes } end ActionController::Routing.const_set(:Routes, real_routes) if real_routes - ActionController::Dispatcher.router = ActionController::Routing::Routes end end end diff --git a/actionpack/lib/action_controller/testing/test_case.rb b/actionpack/lib/action_controller/testing/test_case.rb index b66a4c15ff..178e3477a6 100644 --- a/actionpack/lib/action_controller/testing/test_case.rb +++ b/actionpack/lib/action_controller/testing/test_case.rb @@ -1,7 +1,77 @@ require 'active_support/test_case' -require 'action_controller/testing/process' +require 'rack/session/abstract/id' module ActionController + class TestRequest < ActionDispatch::TestRequest #:nodoc: + def initialize(env = {}) + super + + self.session = TestSession.new + self.session_options = TestSession::DEFAULT_OPTIONS.merge(:id => ActiveSupport::SecureRandom.hex(16)) + end + + def assign_parameters(controller_path, action, parameters = {}) + parameters = parameters.symbolize_keys.merge(:controller => controller_path, :action => action) + extra_keys = ActionController::Routing::Routes.extra_keys(parameters) + non_path_parameters = get? ? query_parameters : request_parameters + parameters.each do |key, value| + if value.is_a? Fixnum + value = value.to_s + elsif value.is_a? Array + value = ActionController::Routing::PathSegment::Result.new(value) + end + + if extra_keys.include?(key.to_sym) + non_path_parameters[key] = value + else + path_parameters[key.to_s] = value + end + end + + params = self.request_parameters.dup + + %w(controller action only_path).each do |k| + params.delete(k) + params.delete(k.to_sym) + end + + data = params.to_query + @env['CONTENT_LENGTH'] = data.length.to_s + @env['rack.input'] = StringIO.new(data) + end + + def recycle! + @formats = nil + @env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ } + @env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ } + @env['action_dispatch.request.query_parameters'] = {} + end + end + + class TestResponse < ActionDispatch::TestResponse + def recycle! + @status = 200 + @header = {} + @writer = lambda { |x| @body << x } + @block = nil + @length = 0 + @body = [] + @charset = nil + @content_type = nil + + @request = @template = nil + end + end + + class TestSession < ActionDispatch::Session::AbstractStore::SessionHash #:nodoc: + DEFAULT_OPTIONS = ActionDispatch::Session::AbstractStore::DEFAULT_OPTIONS + + def initialize(session = {}) + replace(session.stringify_keys) + @loaded = true + end + end + # Superclass for ActionController functional tests. Functional tests allow you to # test a single controller action per test method. This should not be confused with # integration tests (see ActionController::IntegrationTest), which are more like @@ -105,6 +175,73 @@ module ActionController class TestCase < ActiveSupport::TestCase include TestProcess + # Executes a request simulating GET HTTP method and set/volley the response + def get(action, parameters = nil, session = nil, flash = nil) + process(action, parameters, session, flash, "GET") + end + + # Executes a request simulating POST HTTP method and set/volley the response + def post(action, parameters = nil, session = nil, flash = nil) + process(action, parameters, session, flash, "POST") + end + + # Executes a request simulating PUT HTTP method and set/volley the response + def put(action, parameters = nil, session = nil, flash = nil) + process(action, parameters, session, flash, "PUT") + end + + # Executes a request simulating DELETE HTTP method and set/volley the response + def delete(action, parameters = nil, session = nil, flash = nil) + process(action, parameters, session, flash, "DELETE") + end + + # Executes a request simulating HEAD HTTP method and set/volley the response + def head(action, parameters = nil, session = nil, flash = nil) + process(action, parameters, session, flash, "HEAD") + end + + def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil) + @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' + @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') + returning __send__(request_method, action, parameters, session, flash) do + @request.env.delete 'HTTP_X_REQUESTED_WITH' + @request.env.delete 'HTTP_ACCEPT' + end + end + alias xhr :xml_http_request + + def process(action, parameters = nil, session = nil, flash = nil, http_method = 'GET') + # Sanity check for required instance variables so we can give an + # understandable error message. + %w(@controller @request @response).each do |iv_name| + if !(instance_variable_names.include?(iv_name) || instance_variable_names.include?(iv_name.to_sym)) || instance_variable_get(iv_name).nil? + raise "#{iv_name} is nil: make sure you set it in your test's setup method." + end + end + + @request.recycle! + @response.recycle! + @controller.response_body = nil + @controller.formats = nil + @controller.params = nil + + @html_document = nil + @request.env['REQUEST_METHOD'] = http_method + + parameters ||= {} + @request.assign_parameters(@controller.class.name.underscore.sub(/_controller$/, ''), action.to_s, parameters) + + @request.session = ActionController::TestSession.new(session) unless session.nil? + @request.session["flash"] = ActionController::Flash::FlashHash.new.update(flash) if flash + + @controller.request = @request + @controller.params.merge!(parameters) + build_request_uri(action, parameters) + Base.class_eval { include Testing } + @controller.process_with_new_base_test(@request, @response) + @response + end + include ActionDispatch::Assertions # When the request.remote_addr remains the default for testing, which is 0.0.0.0, the exception is simply raised inline @@ -186,5 +323,16 @@ module ActionController def rescue_action_in_public! @request.remote_addr = '208.77.188.166' # example.com end + + private + def build_request_uri(action, parameters) + unless @request.env['REQUEST_URI'] + options = @controller.__send__(:rewrite_options, parameters) + options.update(:only_path => true, :action => action) + + url = ActionController::UrlRewriter.new(@request, parameters) + @request.request_uri = url.rewrite(options) + end + end end end diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 5bcd2143a3..38aaa6146e 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -31,18 +31,24 @@ module ActionDispatch autoload :Request, 'action_dispatch/http/request' autoload :Response, 'action_dispatch/http/response' autoload :StatusCodes, 'action_dispatch/http/status_codes' + autoload :Utils, 'action_dispatch/http/utils' autoload :Callbacks, 'action_dispatch/middleware/callbacks' + autoload :MiddlewareStack, 'action_dispatch/middleware/stack' autoload :ParamsParser, 'action_dispatch/middleware/params_parser' autoload :Rescue, 'action_dispatch/middleware/rescue' autoload :ShowExceptions, 'action_dispatch/middleware/show_exceptions' - autoload :MiddlewareStack, 'action_dispatch/middleware/stack' + autoload :Static, 'action_dispatch/middleware/static' - autoload :HTML, 'action_controller/vendor/html-scanner' autoload :Assertions, 'action_dispatch/testing/assertions' + autoload :Integration, 'action_dispatch/testing/integration' + autoload :IntegrationTest, 'action_dispatch/testing/integration' + autoload :PerformanceTest, 'action_dispatch/testing/performance_test' autoload :TestRequest, 'action_dispatch/testing/test_request' autoload :TestResponse, 'action_dispatch/testing/test_response' + autoload :HTML, 'action_controller/vendor/html-scanner' + module Http autoload :Headers, 'action_dispatch/http/headers' end diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index e457450059..3e3b473178 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -3,7 +3,7 @@ require 'active_support/core_ext/module/delegation' module ActionDispatch # :nodoc: # Represents an HTTP response generated by a controller action. One can use - # an ActionController::Response object to retrieve the current state + # an ActionDispatch::Response object to retrieve the current state # of the response, or customize the response. An Response object can # either represent a "real" HTTP response (i.e. one that is meant to be sent # back to the web browser) or a test response (i.e. one that is generated @@ -18,14 +18,14 @@ module ActionDispatch # :nodoc: # Nevertheless, integration tests may want to inspect controller responses in # more detail, and that's when Response can be useful for application # developers. Integration test methods such as - # ActionController::Integration::Session#get and - # ActionController::Integration::Session#post return objects of type + # ActionDispatch::Integration::Session#get and + # ActionDispatch::Integration::Session#post return objects of type # TestResponse (which are of course also of type Response). # # For example, the following demo integration "test" prints the body of the # controller response to the console: # - # class DemoControllerTest < ActionController::IntegrationTest + # class DemoControllerTest < ActionDispatch::IntegrationTest # def test_print_root_path_to_console # get('/') # puts @response.body diff --git a/actionpack/lib/action_dispatch/http/utils.rb b/actionpack/lib/action_dispatch/http/utils.rb new file mode 100644 index 0000000000..e04a39935e --- /dev/null +++ b/actionpack/lib/action_dispatch/http/utils.rb @@ -0,0 +1,20 @@ +module ActionDispatch + module Utils + # TODO: Pull this into rack core + # http://github.com/halorgium/rack/commit/feaf071c1de743fbd10bc316830180a9af607278 + def parse_config(config) + if config =~ /\.ru$/ + cfgfile = ::File.read(config) + if cfgfile[/^#\\(.*)/] + opts.parse! $1.split(/\s+/) + end + inner_app = eval "Rack::Builder.new {( " + cfgfile + "\n )}.to_app", + nil, config + else + require config + inner_app = Object.const_get(::File.basename(config, '.rb').capitalize) + end + end + module_function :parse_config + end +end diff --git a/actionpack/lib/action_dispatch/middleware/callbacks.rb b/actionpack/lib/action_dispatch/middleware/callbacks.rb index 2f86a382c2..56d6da1706 100644 --- a/actionpack/lib/action_dispatch/middleware/callbacks.rb +++ b/actionpack/lib/action_dispatch/middleware/callbacks.rb @@ -2,7 +2,7 @@ module ActionDispatch class Callbacks include ActiveSupport::NewCallbacks - define_callbacks :call, :terminator => "result == false", :scope => :kind + define_callbacks :call, :terminator => "result == false", :rescuable => true define_callbacks :prepare, :scope => :name # Add a preparation callback. Preparation callbacks are run before every @@ -25,10 +25,6 @@ module ActionDispatch set_callback(:call, :before, *args, &block) end - def self.around(*args, &block) - set_callback(:call, :around, *args, &block) - end - def self.after(*args, &block) set_callback(:call, :after, *args, &block) end @@ -36,7 +32,6 @@ module ActionDispatch class << self # DEPRECATED alias_method :before_dispatch, :before - alias_method :around_dispatch, :around alias_method :after_dispatch, :after end diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index a8768633cc..c5c06f74a2 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -2,6 +2,9 @@ require 'rack/utils' module ActionDispatch module Session + class SessionRestoreError < StandardError #:nodoc: + end + class AbstractStore ENV_SESSION_KEY = 'rack.session'.freeze ENV_SESSION_OPTIONS_KEY = 'rack.session.options'.freeze @@ -19,7 +22,7 @@ module ActionDispatch def session_id ActiveSupport::Deprecation.warn( - "ActionController::Session::AbstractStore::SessionHash#session_id " + + "ActionDispatch::Session::AbstractStore::SessionHash#session_id " + "has been deprecated. Please use request.session_options[:id] instead.", caller) @env[ENV_SESSION_OPTIONS_KEY][:id] end @@ -62,7 +65,7 @@ module ActionDispatch def data ActiveSupport::Deprecation.warn( - "ActionController::Session::AbstractStore::SessionHash#data " + + "ActionDispatch::Session::AbstractStore::SessionHash#data " + "has been deprecated. Please use #to_hash instead.", caller) to_hash end @@ -98,7 +101,7 @@ module ActionDispatch # Note that the regexp does not allow $1 to end with a ':' $1.constantize rescue LoadError, NameError => const_error - raise ActionController::SessionRestoreError, "Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: #{const_error.message} [#{const_error.class}])\n" + raise ActionDispatch::SessionRestoreError, "Session contains objects whose class definition isn't available.\nRemember to require the classes for all objects kept in the session.\n(Original exception: #{const_error.message} [#{const_error.class}])\n" end retry diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb new file mode 100644 index 0000000000..d7e88a54e4 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -0,0 +1,44 @@ +require 'rack/utils' + +module ActionDispatch + class Static + FILE_METHODS = %w(GET HEAD).freeze + + def initialize(app, root) + @app = app + @file_server = ::Rack::File.new(root) + end + + def call(env) + path = env['PATH_INFO'].chomp('/') + method = env['REQUEST_METHOD'] + + if FILE_METHODS.include?(method) + if file_exist?(path) + return @file_server.call(env) + else + cached_path = directory_exist?(path) ? "#{path}/index" : path + cached_path += ::ActionController::Base.page_cache_extension + + if file_exist?(cached_path) + env['PATH_INFO'] = cached_path + return @file_server.call(env) + end + end + end + + @app.call(env) + end + + private + def file_exist?(path) + full_path = File.join(@file_server.root, ::Rack::Utils.unescape(path)) + File.file?(full_path) && File.readable?(full_path) + end + + def directory_exist?(path) + full_path = File.join(@file_server.root, ::Rack::Utils.unescape(path)) + File.directory?(full_path) && File.readable?(full_path) + end + end +end diff --git a/actionpack/lib/action_controller/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 791f936f51..2c4a3a356d 100644 --- a/actionpack/lib/action_controller/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -2,9 +2,11 @@ require 'stringio' require 'uri' require 'active_support/test_case' require 'active_support/core_ext/object/metaclass' -require 'rack/test' -module ActionController +# TODO: Remove circular dependency on ActionController +require 'action_controller/testing/process' + +module ActionDispatch module Integration #:nodoc: module RequestHelpers # Performs a GET request with the given parameters. @@ -21,7 +23,7 @@ module ActionController # # This method returns an Response object, which one can use to # inspect the details of the response. Furthermore, if this method was - # called from an ActionController::IntegrationTest object, then that + # called from an ActionDispatch::IntegrationTest object, then that # object's <tt>@response</tt> instance variable will point to the same # response object. # @@ -166,8 +168,8 @@ module ActionController attr_accessor :request_count # Create and initialize a new Session instance. - def initialize(app = nil) - @app = app || ActionController::Dispatcher.new + def initialize(app) + @app = app reset! end @@ -191,11 +193,11 @@ module ActionController unless defined? @named_routes_configured # install the named routes in this session instance. klass = metaclass - Routing::Routes.install_helpers(klass) + ActionController::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 } + klass.module_eval { public *ActionController::Routing::Routes.named_routes.helpers } @named_routes_configured = true end end @@ -293,7 +295,7 @@ module ActionController "SERVER_PORT" => https? ? "443" : "80", "HTTPS" => https? ? "on" : "off" } - UrlRewriter.new(ActionDispatch::Request.new(env), {}) + ActionController::UrlRewriter.new(ActionDispatch::Request.new(env), {}) end end @@ -323,6 +325,10 @@ module ActionController end module Runner + def app + @app + end + # Reset the current session. This is useful for testing multiple sessions # in a single test case. def reset! @@ -352,7 +358,7 @@ module ActionController # can use this method to open multiple sessions that ought to be tested # simultaneously. def open_session(app = nil) - session = Integration::Session.new(app) + session = Integration::Session.new(app || self.app) # delegate the fixture accessors back to the test instance extras = Module.new { attr_accessor :delegate, :test_result } @@ -474,5 +480,21 @@ module ActionController # end class IntegrationTest < ActiveSupport::TestCase include Integration::Runner + + @@app = nil + + def self.app + # DEPRECATE Rails application fallback + # This should be set by the initializer + @@app || (defined?(Rails.application) && Rails.application.new) || nil + end + + def self.app=(app) + @@app = app + end + + def app + super || self.class.app + end end end diff --git a/actionpack/lib/action_controller/testing/performance_test.rb b/actionpack/lib/action_dispatch/testing/performance_test.rb index d88180087d..b1ed9d31f4 100644 --- a/actionpack/lib/action_controller/testing/performance_test.rb +++ b/actionpack/lib/action_dispatch/testing/performance_test.rb @@ -1,14 +1,14 @@ require 'active_support/testing/performance' require 'active_support/testing/default' -module ActionController +module ActionDispatch # An integration test that runs a code profiler on your test methods. # Profiling output for combinations of each test method, measurement, and # output format are written to your tmp/performance directory. # # By default, process_time is measured and both flat and graph_html output # formats are written, so you'll have two output files per test method. - class PerformanceTest < ActionController::IntegrationTest + class PerformanceTest < ActionDispatch::IntegrationTest include ActiveSupport::Testing::Performance include ActiveSupport::Testing::Default end diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb index c35982e075..6d019023ce 100644 --- a/actionpack/lib/action_dispatch/testing/test_response.rb +++ b/actionpack/lib/action_dispatch/testing/test_response.rb @@ -1,6 +1,6 @@ module ActionDispatch - # Integration test methods such as ActionController::Integration::Session#get - # and ActionController::Integration::Session#post return objects of class + # Integration test methods such as ActionDispatch::Integration::Session#get + # and ActionDispatch::Integration::Session#post return objects of class # TestResponse, which represent the HTTP response results of the requested # controller actions. # diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 332743d55b..8a7a870b99 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -26,8 +26,10 @@ module ActionView # 47 hrs, 59 mins, 29 secs <-> 29 days, 23 hrs, 59 mins, 29 secs # => [2..29] days # 29 days, 23 hrs, 59 mins, 30 secs <-> 59 days, 23 hrs, 59 mins, 29 secs # => about 1 month # 59 days, 23 hrs, 59 mins, 30 secs <-> 1 yr minus 1 sec # => [2..12] months - # 1 yr <-> 2 yrs minus 1 secs # => about 1 year - # 2 yrs <-> max time or date # => over [2..X] years + # 1 yr <-> 1 yr, 3 months # => about 1 year + # 1 yr, 3 months <-> 1 yr, 9 months # => over 1 year + # 1 yr, 9 months <-> 2 yr minus 1 sec # => almost 2 years + # 2 yrs <-> max time or date # => (same rules as 1 yr) # # With <tt>include_seconds</tt> = true and the difference < 1 minute 29 seconds: # 0-4 secs # => less than 5 seconds @@ -43,17 +45,18 @@ module ActionView # distance_of_time_in_words(from_time, 50.minutes.from_now) # => about 1 hour # distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute # distance_of_time_in_words(from_time, from_time + 15.seconds, true) # => less than 20 seconds - # distance_of_time_in_words(from_time, 3.years.from_now) # => over 3 years + # distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years # distance_of_time_in_words(from_time, from_time + 60.hours) # => about 3 days # distance_of_time_in_words(from_time, from_time + 45.seconds, true) # => less than a minute # distance_of_time_in_words(from_time, from_time - 45.seconds, true) # => less than a minute # distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute # distance_of_time_in_words(from_time, from_time + 1.year + 3.days) # => about 1 year - # distance_of_time_in_words(from_time, from_time + 4.years + 9.days + 30.minutes + 5.seconds) # => over 4 years + # distance_of_time_in_words(from_time, from_time + 3.years + 6.months) # => over 3 years + # distance_of_time_in_words(from_time, from_time + 4.years + 9.days + 30.minutes + 5.seconds) # => about 4 years # # to_time = Time.now + 6.years + 19.days - # distance_of_time_in_words(from_time, to_time, true) # => over 6 years - # distance_of_time_in_words(to_time, from_time, true) # => over 6 years + # distance_of_time_in_words(from_time, to_time, true) # => about 6 years + # distance_of_time_in_words(to_time, from_time, true) # => about 6 years # distance_of_time_in_words(Time.now, Time.now) # => less than a minute # def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false, options = {}) @@ -81,12 +84,21 @@ module ActionView when 2..44 then locale.t :x_minutes, :count => distance_in_minutes when 45..89 then locale.t :about_x_hours, :count => 1 when 90..1439 then locale.t :about_x_hours, :count => (distance_in_minutes.to_f / 60.0).round - when 1440..2879 then locale.t :x_days, :count => 1 - when 2880..43199 then locale.t :x_days, :count => (distance_in_minutes / 1440).round + when 1440..2529 then locale.t :x_days, :count => 1 + when 2530..43199 then locale.t :x_days, :count => (distance_in_minutes.to_f / 1440.0).round when 43200..86399 then locale.t :about_x_months, :count => 1 - when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes / 43200).round - when 525600..1051199 then locale.t :about_x_years, :count => 1 - else locale.t :over_x_years, :count => (distance_in_minutes / 525600).round + when 86400..525599 then locale.t :x_months, :count => (distance_in_minutes.to_f / 43200.0).round + else + distance_in_years = distance_in_minutes / 525600 + minute_offset_for_leap_year = (distance_in_years / 4) * 1440 + remainder = ((distance_in_minutes - minute_offset_for_leap_year) % 525600) + if remainder < 131400 + locale.t(:about_x_years, :count => distance_in_years) + elsif remainder < 394200 + locale.t(:over_x_years, :count => distance_in_years) + else + locale.t(:almost_x_years, :count => distance_in_years + 1) + end end end end diff --git a/actionpack/lib/action_view/locale/en.yml b/actionpack/lib/action_view/locale/en.yml index c82cd07ec2..84d94fd700 100644 --- a/actionpack/lib/action_view/locale/en.yml +++ b/actionpack/lib/action_view/locale/en.yml @@ -91,6 +91,9 @@ over_x_years: one: "over 1 year" other: "over {{count}} years" + almost_x_years: + one: "almost 1 year" + other: "almost {{count}} years" prompts: year: "Year" month: "Month" diff --git a/actionpack/lib/action_view/render/rendering.rb b/actionpack/lib/action_view/render/rendering.rb index b0b75918b7..0cab035ede 100644 --- a/actionpack/lib/action_view/render/rendering.rb +++ b/actionpack/lib/action_view/render/rendering.rb @@ -89,6 +89,7 @@ module ActionView def _render_text(text, layout, options) text = layout.render(self, options[:locals]) { text } if layout + text end # This is the API to render a ViewContext's template from a controller. @@ -105,7 +106,7 @@ module ActionView def _render_template(template, layout = nil, options = {}, partial = nil) logger && logger.info do - msg = "Rendering #{template.identifier}" + msg = "Rendering #{template.inspect}" msg << " (#{options[:status]})" if options[:status] msg end @@ -123,7 +124,7 @@ module ActionView if layout @_layout = layout.identifier - logger.info("Rendering template within #{layout.identifier}") if logger + logger.info("Rendering template within #{layout.inspect}") if logger content = layout.render(self, locals) {|*name| _layout_for(*name) } end content diff --git a/actionpack/lib/action_view/template/error.rb b/actionpack/lib/action_view/template/error.rb index 6e5093c5bd..aa21606f76 100644 --- a/actionpack/lib/action_view/template/error.rb +++ b/actionpack/lib/action_view/template/error.rb @@ -32,7 +32,7 @@ module ActionView def sub_template_message if @sub_templates "Trace of template inclusion: " + - @sub_templates.collect { |template| template.identifier }.join(", ") + @sub_templates.collect { |template| template.inspect }.join(", ") else "" end diff --git a/actionpack/lib/action_view/template/handlers/builder.rb b/actionpack/lib/action_view/template/handlers/builder.rb index ba0d17b5af..5f381f7bf0 100644 --- a/actionpack/lib/action_view/template/handlers/builder.rb +++ b/actionpack/lib/action_view/template/handlers/builder.rb @@ -6,7 +6,7 @@ module ActionView self.default_format = Mime::XML def compile(template) - require 'active_support/vendor/builder' + require 'builder' "xml = ::Builder::XmlMarkup.new(:indent => 2);" + "self.output_buffer = xml.target!;" + template.source + diff --git a/actionpack/lib/action_view/template/template.rb b/actionpack/lib/action_view/template/template.rb index 80c1bab7d5..0f64c23649 100644 --- a/actionpack/lib/action_view/template/template.rb +++ b/actionpack/lib/action_view/template/template.rb @@ -8,7 +8,7 @@ module ActionView class Template extend TemplateHandlers attr_reader :source, :identifier, :handler, :mime_type, :formats, :details - + def initialize(source, identifier, handler, details) @source = source @identifier = identifier @@ -25,7 +25,7 @@ module ActionView @formats << :html if format == :js @details[:formats] = Array.wrap(format.to_sym) end - + def render(view, locals, &block) ActiveSupport::Orchestra.instrument(:render_template, :identifier => identifier) do method_name = compile(locals, view) @@ -39,7 +39,7 @@ module ActionView raise TemplateError.new(self, view.assigns, e) end end - + # TODO: Figure out how to abstract this def variable_name @variable_name ||= identifier[%r'_?(\w+)(\.\w+)*$', 1].to_sym @@ -49,76 +49,83 @@ module ActionView def counter_name @counter_name ||= "#{variable_name}_counter".to_sym end - + # TODO: kill hax def partial? @details[:partial] end - private + def inspect + if defined?(Rails.root) + identifier.sub("#{Rails.root}/", '') + else + identifier + end + end - def compile(locals, view) - method_name = build_method_name(locals) - - return method_name if view.respond_to?(method_name) - - locals_code = locals.keys.map! { |key| "#{key} = local_assigns[:#{key}];" }.join + private + def compile(locals, view) + method_name = build_method_name(locals) - code = @handler.call(self) - if code.sub!(/\A(#.*coding.*)\n/, '') - encoding_comment = $1 - elsif defined?(Encoding) && Encoding.respond_to?(:default_external) - encoding_comment = "#coding:#{Encoding.default_external}" - end + return method_name if view.respond_to?(method_name) - source = <<-end_src - def #{method_name}(local_assigns) - old_output_buffer = output_buffer;#{locals_code};#{code} - ensure - self.output_buffer = old_output_buffer - end - end_src + locals_code = locals.keys.map! { |key| "#{key} = local_assigns[:#{key}];" }.join - if encoding_comment - source = "#{encoding_comment}\n#{source}" - line = -1 - else - line = 0 - end + code = @handler.call(self) + if code.sub!(/\A(#.*coding.*)\n/, '') + encoding_comment = $1 + elsif defined?(Encoding) && Encoding.respond_to?(:default_external) + encoding_comment = "#coding:#{Encoding.default_external}" + end - begin - ActionView::CompiledTemplates.module_eval(source, identifier, line) - method_name - rescue Exception => e # errors from template code - if logger = (view && view.logger) - logger.debug "ERROR: compiling #{method_name} RAISED #{e}" - logger.debug "Function body: #{source}" - logger.debug "Backtrace: #{e.backtrace.join("\n")}" + source = <<-end_src + def #{method_name}(local_assigns) + old_output_buffer = output_buffer;#{locals_code};#{code} + ensure + self.output_buffer = old_output_buffer + end + end_src + + if encoding_comment + source = "#{encoding_comment}\n#{source}" + line = -1 + else + line = 0 end - raise ActionView::TemplateError.new(self, {}, e) + begin + ActionView::CompiledTemplates.module_eval(source, identifier, line) + method_name + rescue Exception => e # errors from template code + if logger = (view && view.logger) + logger.debug "ERROR: compiling #{method_name} RAISED #{e}" + logger.debug "Function body: #{source}" + logger.debug "Backtrace: #{e.backtrace.join("\n")}" + end + + raise ActionView::TemplateError.new(self, {}, e) + end end - end - class LocalsKey - @hash_keys = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = {} } } + class LocalsKey + @hash_keys = Hash.new {|h,k| h[k] = Hash.new {|h,k| h[k] = {} } } - def self.get(*locals) - @hash_keys[*locals] ||= new(klass, format, locale) - end + def self.get(*locals) + @hash_keys[*locals] ||= new(klass, format, locale) + end - attr_accessor :hash - def initialize(klass, format, locale) - @hash = locals.hash - end + attr_accessor :hash + def initialize(klass, format, locale) + @hash = locals.hash + end - alias_method :eql?, :equal? - end + alias_method :eql?, :equal? + end - def build_method_name(locals) - # TODO: is locals.keys.hash reliably the same? - @method_names[locals.keys.hash] ||= - "_render_template_#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_") - end + def build_method_name(locals) + # TODO: is locals.keys.hash reliably the same? + @method_names[locals.keys.hash] ||= + "_render_template_#{@identifier.hash}_#{__id__}_#{locals.keys.hash}".gsub('-', "_") + end end end diff --git a/actionpack/lib/action_view/template/text.rb b/actionpack/lib/action_view/template/text.rb index 9f12e5e0a8..f7d0df5ba0 100644 --- a/actionpack/lib/action_view/template/text.rb +++ b/actionpack/lib/action_view/template/text.rb @@ -1,6 +1,5 @@ module ActionView #:nodoc: class TextTemplate < String #:nodoc: - def initialize(string, content_type = Mime[:html]) super(string.to_s) @content_type = Mime[content_type] || content_type @@ -10,14 +9,28 @@ module ActionView #:nodoc: {:formats => [@content_type.to_sym]} end - def identifier() self end - - def render(*) self end - - def mime_type() @content_type end + def identifier + self + end + + def inspect + 'inline template' + end + + def render(*args) + self + end - def formats() [mime_type] end + def mime_type + @content_type + end - def partial?() false end + def formats + [mime_type] + end + + def partial? + false + end end end diff --git a/actionpack/lib/action_view/test_case.rb b/actionpack/lib/action_view/test_case.rb index c2ccd1d3a5..441f462bc9 100644 --- a/actionpack/lib/action_view/test_case.rb +++ b/actionpack/lib/action_view/test_case.rb @@ -1,4 +1,5 @@ require 'active_support/test_case' +require 'action_controller/testing/test_case' module ActionView class Base @@ -23,12 +24,52 @@ module ActionView end class TestCase < ActiveSupport::TestCase + class TestController < ActionController::Base + attr_accessor :request, :response, :params + + def self.controller_path + '' + end + + def initialize + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + + @params = {} + end + end + include ActionDispatch::Assertions include ActionController::TestProcess include ActionView::Context + include ActionController::PolymorphicRoutes + include ActionController::RecordIdentifier + + include ActionView::Helpers + include ActionController::Helpers + class_inheritable_accessor :helper_class - @@helper_class = nil + attr_accessor :controller, :output_buffer, :rendered + + setup :setup_with_controller + def setup_with_controller + @controller = TestController.new + @output_buffer = '' + @rendered = '' + + self.class.send(:include_helper_modules!) + make_test_case_available_to_view! + end + + def render(options = {}, local_assigns = {}, &block) + @rendered << output = _view.render(options, local_assigns, &block) + output + end + + def protect_against_forgery? + false + end class << self def tests(helper_class) @@ -48,41 +89,75 @@ module ActionView rescue NameError nil end - end - include ActionView::Helpers - include ActionController::PolymorphicRoutes - include ActionController::RecordIdentifier - - setup :setup_with_helper_class - - def setup_with_helper_class - if helper_class && !self.class.ancestors.include?(helper_class) - self.class.send(:include, helper_class) + def helper_method(*methods) + # Almost a duplicate from ActionController::Helpers + methods.flatten.each do |method| + _helpers.module_eval <<-end_eval + def #{method}(*args, &block) # def current_user(*args, &block) + _test_case.send(%(#{method}), *args, &block) # test_case.send(%(current_user), *args, &block) + end # end + end_eval + end end - self.output_buffer = '' + private + def include_helper_modules! + helper(helper_class) if helper_class + include _helpers + end end - class TestController < ActionController::Base - attr_accessor :request, :response, :params + private + def make_test_case_available_to_view! + test_case_instance = self + _helpers.module_eval do + define_method(:_test_case) { test_case_instance } + private :_test_case + end + end - def initialize - @request = ActionController::TestRequest.new - @response = ActionController::TestResponse.new + def _view + view = ActionView::Base.new(ActionController::Base.view_paths, _assigns, @controller) + view.class.send :include, _helpers + view + end - @params = {} + # Support the selector assertions + # + # Need to experiment if this priority is the best one: rendered => output_buffer + def response_from_page_or_rjs + HTML::Document.new(rendered.blank? ? output_buffer : rendered).root + end + + EXCLUDE_IVARS = %w{ + @output_buffer + @fixture_cache + @method_name + @_result + @loaded_fixtures + @test_passed + @view + } + + def _instance_variables + instance_variables - EXCLUDE_IVARS end - end - protected - attr_accessor :output_buffer + def _assigns + _instance_variables.inject({}) do |hash, var| + name = var[1..-1].to_sym + hash[name] = instance_variable_get(var) + hash + end + end - private def method_missing(selector, *args) - controller = TestController.new - return controller.__send__(selector, *args) if ActionController::Routing::Routes.named_routes.helpers.include?(selector) - super + if ActionController::Routing::Routes.named_routes.helpers.include?(selector) + @controller.__send__(selector, *args) + else + super + end end end end |