diff options
Diffstat (limited to 'actionpack/lib')
24 files changed, 570 insertions, 226 deletions
diff --git a/actionpack/lib/abstract_controller/base.rb b/actionpack/lib/abstract_controller/base.rb index 8c7cec3561..c95b9a4097 100644 --- a/actionpack/lib/abstract_controller/base.rb +++ b/actionpack/lib/abstract_controller/base.rb @@ -57,21 +57,11 @@ module AbstractController controller.public_instance_methods(true) end - # The list of hidden actions. Defaults to an empty array. - # This can be modified by other modules or subclasses - # to specify particular actions as hidden. - # - # ==== Returns - # * <tt>Array</tt> - An array of method names that should not be considered actions. - def hidden_actions - [] - end - # A list of method names that should be considered actions. This # includes all public instance methods on a controller, less # any internal methods (see internal_methods), adding back in # any methods that are internal, but still exist on the class - # itself. Finally, hidden_actions are removed. + # itself. # # ==== Returns # * <tt>Set</tt> - A set of all methods that should be considered actions. @@ -82,9 +72,7 @@ module AbstractController # Except for public instance methods of Base and its ancestors internal_methods + # Be sure to include shadowed public instance methods of this class - public_instance_methods(false)).uniq.map(&:to_s) - - # And always exclude explicitly hidden actions - hidden_actions.to_a + public_instance_methods(false)).uniq.map(&:to_s) methods.to_set end diff --git a/actionpack/lib/abstract_controller/callbacks.rb b/actionpack/lib/abstract_controller/callbacks.rb index 32de82780f..69f490b327 100644 --- a/actionpack/lib/abstract_controller/callbacks.rb +++ b/actionpack/lib/abstract_controller/callbacks.rb @@ -1,3 +1,5 @@ +require 'active_support/deprecation' + module AbstractController module Callbacks extend ActiveSupport::Concern @@ -28,6 +30,16 @@ module AbstractController # The basic idea is that <tt>:only => :index</tt> gets converted to # <tt>:if => proc {|c| c.action_name == "index" }</tt>. # + # Note that <tt>:only</tt> has priority over <tt>:if</tt> in case they + # are used together. + # + # only: :index, if: -> { true } # the :if option will be ignored. + # + # Note that <tt>:if</tt> has priority over <tt>:except</tt> in case they + # are used together. + # + # except: :index, if: -> { true } # the :except option will be ignored. + # # ==== Options # * <tt>only</tt> - The callback should be run only for this action # * <tt>except</tt> - The callback should be run for all actions except this action @@ -55,7 +67,11 @@ module AbstractController skip_after_action(*names) skip_around_action(*names) end - alias_method :skip_filter, :skip_action_callback + + def skip_filter(*names) + ActiveSupport::Deprecation.warn("#{callback}_filter is deprecated and will be removed in Rails 5.1. Use #{callback}_action instead.") + skip_action_callback(*names) + end # Take callback names and an optional callback proc, normalize them, # then call the block with each callback. This allows us to abstract @@ -170,14 +186,22 @@ module AbstractController set_callback(:process_action, callback, name, options) end end - alias_method :"#{callback}_filter", :"#{callback}_action" + + define_method "#{callback}_filter" do |*names, &blk| + ActiveSupport::Deprecation.warn("#{callback}_filter is deprecated and will be removed in Rails 5.1. Use #{callback}_action instead.") + send("#{callback}_action", *names, &blk) + end define_method "prepend_#{callback}_action" do |*names, &blk| _insert_callbacks(names, blk) do |name, options| set_callback(:process_action, callback, name, options.merge(:prepend => true)) end end - alias_method :"prepend_#{callback}_filter", :"prepend_#{callback}_action" + + define_method "prepend_#{callback}_filter" do |*names, &blk| + ActiveSupport::Deprecation.warn("prepend_#{callback}_filter is deprecated and will be removed in Rails 5.1. Use prepend_#{callback}_action instead.") + send("prepend_#{callback}_action", *names, &blk) + end # Skip a before, after or around callback. See _insert_callbacks # for details on the allowed parameters. @@ -186,11 +210,19 @@ module AbstractController skip_callback(:process_action, callback, name, options) end end - alias_method :"skip_#{callback}_filter", :"skip_#{callback}_action" + + define_method "skip_#{callback}_filter" do |*names, &blk| + ActiveSupport::Deprecation.warn("skip_#{callback}_filter is deprecated and will be removed in Rails 5.1. Use skip_#{callback}_action instead.") + send("skip_#{callback}_action", *names, &blk) + end # *_action is the same as append_*_action alias_method :"append_#{callback}_action", :"#{callback}_action" - alias_method :"append_#{callback}_filter", :"#{callback}_action" + + define_method "append_#{callback}_filter" do |*names, &blk| + ActiveSupport::Deprecation.warn("append_#{callback}_filter is deprecated and will be removed in Rails 5.1. Use append_#{callback}_action instead.") + send("append_#{callback}_action", *names, &blk) + end end end end diff --git a/actionpack/lib/abstract_controller/railties/routes_helpers.rb b/actionpack/lib/abstract_controller/railties/routes_helpers.rb index 568c47e43a..14b574e322 100644 --- a/actionpack/lib/abstract_controller/railties/routes_helpers.rb +++ b/actionpack/lib/abstract_controller/railties/routes_helpers.rb @@ -6,9 +6,9 @@ module AbstractController define_method(:inherited) do |klass| super(klass) if namespace = klass.parents.detect { |m| m.respond_to?(:railtie_routes_url_helpers) } - klass.send(:include, namespace.railtie_routes_url_helpers(include_path_helpers)) + klass.include(namespace.railtie_routes_url_helpers(include_path_helpers)) else - klass.send(:include, routes.url_helpers(include_path_helpers)) + klass.include(routes.url_helpers(include_path_helpers)) end end end diff --git a/actionpack/lib/abstract_controller/translation.rb b/actionpack/lib/abstract_controller/translation.rb index 02028d8e05..56b8ce895e 100644 --- a/actionpack/lib/abstract_controller/translation.rb +++ b/actionpack/lib/abstract_controller/translation.rb @@ -8,14 +8,15 @@ module AbstractController # <tt>I18n.translate("people.index.foo")</tt>. This makes it less repetitive # to translate many keys within the same controller / action and gives you a # simple framework for scoping them consistently. - def translate(*args) - key = args.first - if key.is_a?(String) && (key[0] == '.') - key = "#{ controller_path.tr('/', '.') }.#{ action_name }#{ key }" - args[0] = key + def translate(key, options = {}) + if key.to_s.first == '.' + path = controller_path.tr('/', '.') + defaults = [:"#{path}#{key}"] + defaults << options[:default] if options[:default] + options[:default] = defaults + key = "#{path}.#{action_name}#{key}" end - - I18n.translate(*args) + I18n.translate(key, options) end alias :t :translate diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 91ac7eef01..7667e469d3 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -11,6 +11,7 @@ module ActionController autoload :Caching autoload :Metal autoload :Middleware + autoload :Renderer autoload_under "metal" do autoload :Compatibility @@ -22,7 +23,6 @@ module ActionController autoload :ForceSSL autoload :Head autoload :Helpers - autoload :HideActions autoload :HttpAuthentication autoload :ImplicitRender autoload :Instrumentation diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 5cb11bc479..e6038396f9 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -206,7 +206,6 @@ module ActionController AbstractController::AssetPaths, Helpers, - HideActions, UrlFor, Redirecting, ActionView::Layouts, diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 993f8e150d..ae111e4951 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -190,11 +190,15 @@ module ActionController end def dispatch(name, request) #:nodoc: + set_request!(request) + process(name) + to_a + end + + def set_request!(request) #:nodoc: @_request = request @_env = request.env @_env['action_controller.instance'] = self - process(name) - to_a end def to_a #:nodoc: diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index b210ee3423..858870d8b8 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -51,21 +51,31 @@ module ActionController # # def show # @article = Article.find(params[:id]) - # fresh_when(etag: @article, last_modified: @article.created_at, public: true) + # fresh_when(etag: @article, last_modified: @article.updated_at, public: true) # end # # This will render the show template if the request isn't sending a matching ETag or # If-Modified-Since header and just a <tt>304 Not Modified</tt> response if there's a match. # - # You can also just pass a record where +last_modified+ will be set by calling - # +updated_at+ and the +etag+ by passing the object itself. + # You can also just pass a record. In this case +last_modified+ will be set + # by calling +updated_at+ and +etag+ by passing the object itself. # # def show # @article = Article.find(params[:id]) # fresh_when(@article) # end # - # When passing a record, you can still set whether the public header: + # You can also pass an object that responds to +maximum+, such as a + # collection of active records. In this case +last_modified+ will be set by + # calling +maximum(:updated_at)+ on the collection (the timestamp of the + # most recently updated record) and the +etag+ by passing the object itself. + # + # def index + # @articles = Article.all + # fresh_when(@articles) + # end + # + # When passing a record or a collection, you can still set the public header: # # def show # @article = Article.find(params[:id]) @@ -77,18 +87,16 @@ module ActionController # # before_action { fresh_when @article, template: 'widgets/show' } # - def fresh_when(record_or_options, additional_options = {}) - if record_or_options.is_a? Hash - options = record_or_options - options.assert_valid_keys(:etag, :last_modified, :public, :template) - else - record = record_or_options - options = { etag: record, last_modified: record.try(:updated_at) }.merge!(additional_options) + def fresh_when(object = nil, etag: object, last_modified: nil, public: false, template: nil) + last_modified ||= object.try(:updated_at) || object.try(:maximum, :updated_at) + + if etag || template + response.etag = combine_etags(etag: etag, last_modified: last_modified, + public: public, template: template) end - response.etag = combine_etags(options) if options[:etag] || options[:template] - response.last_modified = options[:last_modified] if options[:last_modified] - response.cache_control[:public] = true if options[:public] + response.last_modified = last_modified if last_modified + response.cache_control[:public] = true if public head :not_modified if request.fresh?(response) end @@ -115,7 +123,7 @@ module ActionController # def show # @article = Article.find(params[:id]) # - # if stale?(etag: @article, last_modified: @article.created_at) + # if stale?(etag: @article, last_modified: @article.updated_at) # @statistics = @article.really_expensive_call # respond_to do |format| # # all the supported formats @@ -123,8 +131,8 @@ module ActionController # end # end # - # You can also just pass a record where +last_modified+ will be set by calling - # +updated_at+ and the +etag+ by passing the object itself. + # You can also just pass a record. In this case +last_modified+ will be set + # by calling +updated_at+ and +etag+ by passing the object itself. # # def show # @article = Article.find(params[:id]) @@ -137,7 +145,23 @@ module ActionController # end # end # - # When passing a record, you can still set whether the public header: + # You can also pass an object that responds to +maximum+, such as a + # collection of active records. In this case +last_modified+ will be set by + # calling +maximum(:updated_at)+ on the collection (the timestamp of the + # most recently updated record) and the +etag+ by passing the object itself. + # + # def index + # @articles = Article.all + # + # if stale?(@articles) + # @statistics = @articles.really_expensive_call + # respond_to do |format| + # # all the supported formats + # end + # end + # end + # + # When passing a record or a collection, you can still set the public header: # # def show # @article = Article.find(params[:id]) @@ -157,8 +181,8 @@ module ActionController # super if stale? @article, template: 'widgets/show' # end # - def stale?(record_or_options, additional_options = {}) - fresh_when(record_or_options, additional_options) + def stale?(object = nil, etag: object, last_modified: nil, public: nil, template: nil) + fresh_when(object, etag: etag, last_modified: last_modified, public: public, template: template) !request.fresh?(response) end @@ -191,6 +215,24 @@ module ActionController response.cache_control.replace(:no_cache => true) end + # Cache or yield the block. The cache is supposed to never expire. + # + # You can use this method when you have a HTTP response that never changes, + # and the browser and proxies should cache it indefinitely. + # + # * +public+: By default, HTTP responses are private, cached only on the + # user's web browser. To allow proxies to cache the response, set +true+ to + # indicate that they can serve the cached response to all users. + # + # * +version+: the version passed as a key for the cache. + def http_cache_forever(public: false, version: 'v1') + expires_in 100.years, public: public + + yield if stale?(etag: "#{version}-#{request.fullpath}", + last_modified: Time.parse('2011-01-01').utc, + public: public) + end + private def combine_etags(options) etags = etaggers.map { |etagger| instance_exec(options, &etagger) }.compact diff --git a/actionpack/lib/action_controller/metal/hide_actions.rb b/actionpack/lib/action_controller/metal/hide_actions.rb deleted file mode 100644 index af36ffa240..0000000000 --- a/actionpack/lib/action_controller/metal/hide_actions.rb +++ /dev/null @@ -1,40 +0,0 @@ - -module ActionController - # Adds the ability to prevent public methods on a controller to be called as actions. - module HideActions - extend ActiveSupport::Concern - - included do - class_attribute :hidden_actions - self.hidden_actions = Set.new.freeze - end - - private - - # Overrides AbstractController::Base#action_method? to return false if the - # action name is in the list of hidden actions. - def method_for_action(action_name) - self.class.visible_action?(action_name) && super - end - - module ClassMethods - # Sets all of the actions passed in as hidden actions. - # - # ==== Parameters - # * <tt>args</tt> - A list of actions - def hide_action(*args) - self.hidden_actions = hidden_actions.dup.merge(args.map(&:to_s)).freeze - end - - def visible_action?(action_name) - not hidden_actions.include?(action_name) - end - - # Overrides AbstractController::Base#action_methods to remove any methods - # that are listed as hidden methods. - def action_methods - @action_methods ||= Set.new(super.reject { |name| hidden_actions.include?(name) }).freeze - end - end - end -end diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index a219d35b25..20afcee537 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -106,11 +106,11 @@ module ActionController end def auth_scheme(request) - request.authorization.split(' ', 2).first + request.authorization.to_s.split(' ', 2).first end def auth_param(request) - request.authorization.split(' ', 2).second + request.authorization.to_s.split(' ', 2).second end def encode_credentials(user_name, password) diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb index 545d4a7e6e..ae9d89cc8c 100644 --- a/actionpack/lib/action_controller/metal/rack_delegation.rb +++ b/actionpack/lib/action_controller/metal/rack_delegation.rb @@ -8,9 +8,15 @@ module ActionController delegate :headers, :status=, :location=, :content_type=, :status, :location, :content_type, :response_code, :to => "@_response" - def dispatch(action, request) + module ClassMethods + def build_with_env(env = {}) #:nodoc: + new.tap { |c| c.set_request! ActionDispatch::Request.new(env) } + end + end + + def set_request!(request) #:nodoc: + super set_response!(request) - super(action, request) end def response_body=(body) diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index 7bbff0450a..2d15c39d88 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -4,6 +4,17 @@ module ActionController RENDER_FORMATS_IN_PRIORITY = [:body, :text, :plain, :html] + module ClassMethods + # Documentation at ActionController::Renderer#render + delegate :render, to: :renderer + + # Returns a renderer class (inherited from ActionController::Renderer) + # for the controller. + def renderer + @renderer ||= Renderer.for(self) + end + end + # Before processing, set the request formats in current controller formats. def process_action(*) #:nodoc: self.formats = request.formats.map(&:ref).compact diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index b9a1e7d242..7facbe79aa 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -87,6 +87,11 @@ module ActionController #:nodoc: # # * <tt>:only/:except</tt> - Only apply forgery protection to a subset of actions. Like <tt>only: [ :create, :create_all ]</tt>. # * <tt>:if/:unless</tt> - Turn off the forgery protection entirely depending on the passed proc or method reference. + # * <tt>:prepend</tt> - By default, the verification of the authentication token is added to the front of the + # callback chain. If you need to make the verification depend on other callbacks, like authentication methods + # (say cookies vs oauth), this might not work for you. Pass <tt>prepend: false</tt> to just add the + # verification callback in the position of the protect_from_forgery call. This means any callbacks added + # before are run first. # * <tt>:with</tt> - Set the method to handle unverified request. # # Valid unverified request handling methods are: @@ -94,9 +99,11 @@ module ActionController #:nodoc: # * <tt>:reset_session</tt> - Resets the session. # * <tt>:null_session</tt> - Provides an empty session during request but doesn't reset it completely. Used as default if <tt>:with</tt> option is not specified. def protect_from_forgery(options = {}) + options = options.reverse_merge(prepend: true) + self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session) self.request_forgery_protection_token ||= :authenticity_token - prepend_before_action :verify_authenticity_token, options + before_action :verify_authenticity_token, options append_after_action :verify_same_origin_request end diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb new file mode 100644 index 0000000000..e8b29c5b5e --- /dev/null +++ b/actionpack/lib/action_controller/renderer.rb @@ -0,0 +1,100 @@ +require 'active_support/core_ext/hash/keys' + +module ActionController + # ActionController::Renderer allows to render arbitrary templates + # without requirement of being in controller actions. + # + # You get a concrete renderer class by invoking ActionController::Base#renderer. + # For example, + # + # ApplicationController.renderer + # + # It allows you to call method #render directly. + # + # ApplicationController.renderer.render template: '...' + # + # You can use a shortcut on controller to replace previous example with: + # + # ApplicationController.render template: '...' + # + # #render method allows you to use any options as when rendering in controller. + # For example, + # + # FooController.render :action, locals: { ... }, assigns: { ... } + # + # The template will be rendered in a Rack environment which is accessible through + # ActionController::Renderer#env. You can set it up in two ways: + # + # * by changing renderer defaults, like + # + # ApplicationController.renderer.defaults # => hash with default Rack environment + # + # * by initializing an instance of renderer by passing it a custom environment. + # + # ApplicationController.renderer.new(method: 'post', https: true) + # + class Renderer + class_attribute :controller, :defaults + # Rack environment to render templates in. + attr_reader :env + + class << self + delegate :render, to: :new + + # Create a new renderer class for a specific controller class. + def for(controller) + Class.new self do + self.controller = controller + self.defaults = { + http_host: 'example.org', + https: false, + method: 'get', + script_name: '', + 'rack.input' => '' + } + end + end + end + + # Accepts a custom Rack environment to render templates in. + # It will be merged with ActionController::Renderer.defaults + def initialize(env = {}) + @env = normalize_keys(defaults).merge normalize_keys(env) + @env['action_dispatch.routes'] = controller._routes + end + + # Render templates with any options from ActionController::Base#render_to_string. + def render(*args) + raise 'missing controller' unless controller? + + instance = controller.build_with_env(env) + instance.render_to_string(*args) + end + + private + def normalize_keys(env) + http_header_format(env).tap do |new_env| + handle_method_key! new_env + handle_https_key! new_env + end + end + + def http_header_format(env) + env.transform_keys do |key| + key.is_a?(Symbol) ? key.to_s.upcase : key + end + end + + def handle_method_key!(env) + if method = env.delete('METHOD') + env['REQUEST_METHOD'] = method.upcase + end + end + + def handle_https_key!(env) + if env.has_key? 'HTTPS' + env['HTTPS'] = env['HTTPS'] ? 'on' : 'off' + end + end + end +end diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index d30615fade..4782991463 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -494,55 +494,66 @@ module ActionController # Simulate a GET request with the given parameters. # # - +action+: The controller action to call. - # - +parameters+: The HTTP parameters that you want to pass. This may - # be +nil+, a hash, or a string that is appropriately encoded + # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+. + # - +body+: The request body with a string that is appropriately encoded # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>). # - +session+: A hash of parameters to store in the session. This may be +nil+. # - +flash+: A hash of parameters to store in the flash. This may be +nil+. # # You can also simulate POST, PATCH, PUT, DELETE, and HEAD requests with # +post+, +patch+, +put+, +delete+, and +head+. + # Example sending parameters, session and setting a flash message: + # + # get :show, + # params: { id: 7 }, + # session: { user_id: 1 }, + # flash: { notice: 'This is flash message' } # # Note that the request method is not verified. The different methods are # available to make the tests more expressive. def get(action, *args) - process(action, "GET", *args) + process_with_kwargs("GET", action, *args) end # Simulate a POST request with the given parameters and set/volley the response. # See +get+ for more details. def post(action, *args) - process(action, "POST", *args) + process_with_kwargs("POST", action, *args) end # Simulate a PATCH request with the given parameters and set/volley the response. # See +get+ for more details. def patch(action, *args) - process(action, "PATCH", *args) + process_with_kwargs("PATCH", action, *args) end # Simulate a PUT request with the given parameters and set/volley the response. # See +get+ for more details. def put(action, *args) - process(action, "PUT", *args) + process_with_kwargs("PUT", action, *args) end # Simulate a DELETE request with the given parameters and set/volley the response. # See +get+ for more details. def delete(action, *args) - process(action, "DELETE", *args) + process_with_kwargs("DELETE", action, *args) end # Simulate a HEAD request with the given parameters and set/volley the response. # See +get+ for more details. def head(action, *args) - process(action, "HEAD", *args) + process_with_kwargs("HEAD", action, *args) end - def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil) + def xml_http_request(*args) + ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc) + xhr and xml_http_request methods are deprecated in favor of + `get :index, xhr: true` and `post :create, xhr: true` + MSG + @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' - @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') - __send__(request_method, action, parameters, session, flash).tap do + @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') + __send__(*args).tap do @request.env.delete 'HTTP_X_REQUESTED_WITH' @request.env.delete 'HTTP_ACCEPT' end @@ -566,41 +577,69 @@ module ActionController # parameters and set/volley the response. # # - +action+: The controller action to call. - # - +http_method+: Request method used to send the http request. Possible values - # are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+. - # - +parameters+: The HTTP parameters. This may be +nil+, a hash, or a - # string that is appropriately encoded (+application/x-www-form-urlencoded+ - # or +multipart/form-data+). + # - +method+: Request method used to send the HTTP request. Possible values + # are +GET+, +POST+, +PATCH+, +PUT+, +DELETE+, +HEAD+. Defaults to +GET+. Can be a symbol. + # - +params+: The hash with HTTP parameters that you want to pass. This may be +nil+. + # - +body+: The request body with a string that is appropriately encoded + # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>). # - +session+: A hash of parameters to store in the session. This may be +nil+. # - +flash+: A hash of parameters to store in the flash. This may be +nil+. + # - +format+: Request format. Defaults to +nil+. Can be string or symbol. # # Example calling +create+ action and sending two params: # - # process :create, 'POST', user: { name: 'Gaurish Sharma', email: 'user@example.com' } - # - # Example sending parameters, +nil+ session and setting a flash message: - # - # process :view, 'GET', { id: 7 }, nil, { notice: 'This is flash message' } + # process :create, + # method: 'POST', + # params: { + # user: { name: 'Gaurish Sharma', email: 'user@example.com' } + # }, + # session: { user_id: 1 }, + # flash: { notice: 'This is flash message' } # # To simulate +GET+, +POST+, +PATCH+, +PUT+, +DELETE+ and +HEAD+ requests # prefer using #get, #post, #patch, #put, #delete and #head methods # respectively which will make tests more expressive. # # Note that the request method is not verified. - def process(action, http_method = 'GET', *args) + def process(action, *args) check_required_ivars - if args.first.is_a?(String) && http_method != 'HEAD' - @request.env['RAW_POST_DATA'] = args.shift + if kwarg_request?(*args) + parameters, session, body, flash, http_method, format, xhr = args[0].values_at(:params, :session, :body, :flash, :method, :format, :xhr) + else + http_method, parameters, session, flash = args + format = nil + + if parameters.is_a?(String) && http_method != 'HEAD' + body = parameters + parameters = nil + end + + if parameters.present? || session.present? || flash.present? + non_kwarg_request_warning + end + end + + if body.present? + @request.env['RAW_POST_DATA'] = body + end + + if http_method.present? + http_method = http_method.to_s.upcase + else + http_method = "GET" end - parameters, session, flash = args parameters ||= {} # Ensure that numbers and symbols passed as params are converted to # proper params, as is the case when engaging rack. parameters = paramify_values(parameters) if html_format?(parameters) + if format.present? + parameters[:format] = format + end + @html_document = nil unless @controller.respond_to?(:recycle!) @@ -622,6 +661,11 @@ module ActionController @request.session.update(session) if session @request.flash.update(flash || {}) + if xhr + @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' + @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') + end + @controller.request = @request @controller.response = @response @@ -643,6 +687,13 @@ module ActionController if flash_value = @request.flash.to_session_value @request.session['flash'] = flash_value + else + @request.session.delete('flash') + end + + if xhr + @request.env.delete 'HTTP_X_REQUESTED_WITH' + @request.env.delete 'HTTP_ACCEPT' end @response @@ -693,6 +744,38 @@ module ActionController private + def process_with_kwargs(http_method, action, *args) + if kwarg_request?(*args) + args.first.merge!(method: http_method) + process(action, *args) + else + non_kwarg_request_warning if args.present? + + args = args.unshift(http_method) + process(action, *args) + end + end + + REQUEST_KWARGS = %i(params session flash method body xhr) + def kwarg_request?(*args) + args[0].respond_to?(:keys) && ( + (args[0].key?(:format) && args[0].keys.size == 1) || + args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) } + ) + end + + def non_kwarg_request_warning + ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc) + ActionController::TestCase HTTP request methods will accept only + keyword arguments in future Rails versions. + + Examples: + + get :show, params: { id: 1 }, session: { user_id: 1 } + process :update, method: :post, params: { id: 1 } + MSG + end + def document_root_element html_document.root end diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index 63a3cbc90b..747d295261 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -69,17 +69,17 @@ module ActionDispatch end def date - if date_header = headers['Date'] + if date_header = headers[DATE] Time.httpdate(date_header) end end def date? - headers.include?('Date') + headers.include?(DATE) end def date=(utc_time) - headers['Date'] = utc_time.httpdate + headers[DATE] = utc_time.httpdate end def etag=(etag) @@ -89,6 +89,7 @@ module ActionDispatch private + DATE = 'Date'.freeze LAST_MODIFIED = "Last-Modified".freeze ETAG = "ETag".freeze CACHE_CONTROL = "Cache-Control".freeze diff --git a/actionpack/lib/action_dispatch/journey/router.rb b/actionpack/lib/action_dispatch/journey/router.rb index 2b036796ab..e9df984c86 100644 --- a/actionpack/lib/action_dispatch/journey/router.rb +++ b/actionpack/lib/action_dispatch/journey/router.rb @@ -121,6 +121,7 @@ module ActionDispatch end def match_head_routes(routes, req) + routes.delete_if { |route| route.verb == // } head_routes = match_routes(routes, req) if head_routes.empty? diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb index a4862e33aa..d176a73633 100644 --- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb +++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb @@ -1,5 +1,6 @@ require 'action_controller/metal/exceptions' require 'active_support/core_ext/module/attribute_accessors' +require 'rack/utils' module ActionDispatch class ExceptionWrapper @@ -87,8 +88,7 @@ module ActionDispatch def source_extracts backtrace.map do |trace| - file, line = trace.split(":") - line_number = line.to_i + file, line_number = extract_file_and_line_number(trace) { code: source_fragment(file, line_number), @@ -139,6 +139,13 @@ module ActionDispatch end end + def extract_file_and_line_number(trace) + # Split by the first colon followed by some digits, which works for both + # Windows and Unix path styles. + file, line = trace.match(/^(.+?):(\d+).*$/, &:captures) || trace + [file, line.to_i] + end + def expand_backtrace @exception.backtrace.unshift( @exception.to_s.split("\n") diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index a7f95150a4..59639a010e 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -80,24 +80,30 @@ module ActionDispatch include Enumerable def self.from_session_value(value) #:nodoc: - flash = case value - when FlashHash # Rails 3.1, 3.2 - new(value.instance_variable_get(:@flashes), value.instance_variable_get(:@used)) - when Hash # Rails 4.0 - new(value['flashes'], value['discard']) - else - new - end - - flash.tap(&:sweep) - end - - # Builds a hash containing the discarded values and the hashes - # representing the flashes. - # If there are no values in @flashes, returns nil. + case value + when FlashHash # Rails 3.1, 3.2 + flashes = value.instance_variable_get(:@flashes) + if discard = value.instance_variable_get(:@used) + flashes.except!(*discard) + end + new(flashes, flashes.keys) + when Hash # Rails 4.0 + flashes = value['flashes'] + if discard = value['discard'] + flashes.except!(*discard) + end + new(flashes, flashes.keys) + else + new + end + end + + # Builds a hash containing the flashes to keep for the next request. + # If there are none to keep, returns nil. def to_session_value #:nodoc: - return nil if empty? - {'discard' => @discard.to_a, 'flashes' => @flashes} + flashes_to_keep = @flashes.except(*@discard) + return nil if flashes_to_keep.empty? + {'flashes' => flashes_to_keep} end def initialize(flashes = {}, discard = []) #:nodoc: diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 8b04dfaa45..f2c9e7b1a0 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -1739,9 +1739,10 @@ module ActionDispatch member_name = parent_resource.member_name end - name = @scope.action_name(name_prefix, prefix, collection_name, member_name) + action_name = @scope.action_name(name_prefix, prefix, collection_name, member_name) + candidate = action_name.select(&:present?).join('_') - if candidate = name.compact.join("_").presence + unless candidate.empty? # If a name was not explicitly given, we check if it is valid # and return nil in case it isn't. Otherwise, we pass the invalid name # forward so the underlying router engine treats it and raises an exception. diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index b4c861d306..65d6a5eb1e 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -12,14 +12,15 @@ require 'action_dispatch/routing/endpoint' module ActionDispatch module Routing - class RouteSet #:nodoc: + # :stopdoc: + class RouteSet # Since the router holds references to many parts of the system # like engines, controllers and the application itself, inspecting # the route set can actually be really slow, therefore we default # alias inspect to to_s. alias inspect to_s - class Dispatcher < Routing::Endpoint #:nodoc: + class Dispatcher < Routing::Endpoint def initialize(defaults) @defaults = defaults @controller_class_names = ThreadSafe::Cache.new @@ -84,7 +85,7 @@ module ActionDispatch # A NamedRouteCollection instance is a collection of named routes, and also # maintains an anonymous module that can be used to install helpers for the # named routes. - class NamedRouteCollection #:nodoc: + class NamedRouteCollection include Enumerable attr_reader :routes, :url_helpers_module, :path_helpers_module @@ -161,7 +162,7 @@ module ActionDispatch routes.length end - class UrlHelper # :nodoc: + class UrlHelper def self.create(route, options, route_name, url_strategy) if optimize_helper?(route) OptimizedUrlHelper.new(route, options, route_name, url_strategy) @@ -176,7 +177,7 @@ module ActionDispatch attr_reader :url_strategy, :route_name - class OptimizedUrlHelper < UrlHelper # :nodoc: + class OptimizedUrlHelper < UrlHelper attr_reader :arg_size def initialize(route, options, route_name, url_strategy) @@ -264,9 +265,10 @@ module ActionDispatch path_params -= controller_options.keys path_params -= result.keys end - path_params.each { |param| - result[param] = inner_options.fetch(param) { args.shift } - } + path_params -= inner_options.keys + path_params.take(args.size).each do |param| + result[param] = args.shift + end end result.merge!(inner_options) @@ -299,11 +301,9 @@ module ActionDispatch end end - # :stopdoc: # strategy for building urls to send to the client PATH = ->(options) { ActionDispatch::Http::URL.path_for(options) } UNKNOWN = ->(options) { ActionDispatch::Http::URL.url_for(options) } - # :startdoc: attr_accessor :formatter, :set, :named_routes, :default_scope, :router attr_accessor :disable_clear_and_finalize, :resources_path_names @@ -378,7 +378,7 @@ module ActionDispatch Routing::RouteSet::Dispatcher.new(defaults) end - module MountedHelpers #:nodoc: + module MountedHelpers extend ActiveSupport::Concern include UrlFor end @@ -419,7 +419,14 @@ module ActionDispatch # Rails.application.routes.url_helpers.url_for(args) @_routes = routes class << self - delegate :url_for, :optimize_routes_generation?, to: '@_routes' + def url_for(options) + @_routes.url_for(options) + end + + def optimize_routes_generation? + @_routes.optimize_routes_generation? + end + attr_reader :_routes def url_options; {}; end end @@ -535,7 +542,7 @@ module ActionDispatch end private :build_conditions - class Generator #:nodoc: + class Generator PARAMETERIZE = lambda do |name, value| if name == :controller value @@ -687,7 +694,7 @@ module ActionDispatch options.delete(:script_name) || '' end - def path_for(options, route_name = nil) # :nodoc: + def path_for(options, route_name = nil) url_for(options, route_name, PATH) end @@ -773,5 +780,6 @@ module ActionDispatch raise ActionController::RoutingError, "No route matches #{path.inspect}" end end + # :startdoc: end end diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index 28dc88d317..a927738b42 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -144,7 +144,7 @@ module ActionDispatch old_controller, @controller = @controller, @controller.clone _routes = @routes - @controller.singleton_class.send(:include, _routes.url_helpers) + @controller.singleton_class.include(_routes.url_helpers) @controller.view_context_class = Class.new(@controller.view_context_class) do include _routes.url_helpers end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index f0e2c5becc..9b00616968 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -12,12 +12,14 @@ module ActionDispatch # # - +path+: The URI (as a String) on which you want to perform a GET # request. - # - +parameters+: The HTTP parameters that you want to pass. This may + # - +params+: The HTTP parameters that you want to pass. This may # be +nil+, # a Hash, or a String that is appropriately encoded # (<tt>application/x-www-form-urlencoded</tt> or # <tt>multipart/form-data</tt>). - # - +headers_or_env+: Additional headers to pass, as a Hash. The headers will be + # - +headers+: Additional headers to pass, as a Hash. The headers will be + # merged into the Rack env hash. + # - +env+: Additional env to pass, as a Hash. The headers will be # merged into the Rack env hash. # # This method returns a Response object, which one can use to @@ -28,38 +30,43 @@ module ActionDispatch # # You can also perform POST, PATCH, PUT, DELETE, and HEAD requests with # +#post+, +#patch+, +#put+, +#delete+, and +#head+. - def get(path, parameters = nil, headers_or_env = nil) - process :get, path, parameters, headers_or_env + # + # Example: + # + # get '/feed', params: { since: 201501011400 } + # post '/profile', headers: { "X-Test-Header" => "testvalue" } + def get(path, *args) + process_with_kwargs(:get, path, *args) end # Performs a POST request with the given parameters. See +#get+ for more # details. - def post(path, parameters = nil, headers_or_env = nil) - process :post, path, parameters, headers_or_env + def post(path, *args) + process_with_kwargs(:post, path, *args) end # Performs a PATCH request with the given parameters. See +#get+ for more # details. - def patch(path, parameters = nil, headers_or_env = nil) - process :patch, path, parameters, headers_or_env + def patch(path, *args) + process_with_kwargs(:patch, path, *args) end # Performs a PUT request with the given parameters. See +#get+ for more # details. - def put(path, parameters = nil, headers_or_env = nil) - process :put, path, parameters, headers_or_env + def put(path, *args) + process_with_kwargs(:put, path, *args) end # Performs a DELETE request with the given parameters. See +#get+ for # more details. - def delete(path, parameters = nil, headers_or_env = nil) - process :delete, path, parameters, headers_or_env + def delete(path, *args) + process_with_kwargs(:delete, path, *args) end # Performs a HEAD request with the given parameters. See +#get+ for more # details. - def head(path, parameters = nil, headers_or_env = nil) - process :head, path, parameters, headers_or_env + def head(path, *args) + process_with_kwargs(:head, path, *args) end # Performs an XMLHttpRequest request with the given parameters, mirroring @@ -68,11 +75,29 @@ module ActionDispatch # The request_method is +:get+, +:post+, +:patch+, +:put+, +:delete+ or # +:head+; the parameters are +nil+, a hash, or a url-encoded or multipart # string; the headers are a hash. - def xml_http_request(request_method, path, parameters = nil, headers_or_env = nil) - headers_or_env ||= {} - headers_or_env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' - headers_or_env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') - process(request_method, path, parameters, headers_or_env) + # + # Example: + # + # xhr :get, '/feed', params: { since: 201501011400 } + def xml_http_request(request_method, path, *args) + if kwarg_request?(*args) + params, headers, env = args.first.values_at(:params, :headers, :env) + else + params = args[0] + headers = args[1] + env = {} + + if params.present? || headers.present? + non_kwarg_request_warning + end + end + + ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc) + xhr and xml_http_request methods are deprecated in favor of + `get "/posts", xhr: true` and `post "/posts/1", xhr: true` + MSG + + process(request_method, path, params: params, headers: headers, xhr: true) end alias xhr :xml_http_request @@ -89,40 +114,52 @@ module ActionDispatch # redirect. Note that the redirects are followed until the response is # not a redirect--this means you may run into an infinite loop if your # redirect loops back to itself. - def request_via_redirect(http_method, path, parameters = nil, headers_or_env = nil) - process(http_method, path, parameters, headers_or_env) + # + # Example: + # + # request_via_redirect :post, '/welcome', + # params: { ref_id: 14 }, + # headers: { "X-Test-Header" => "testvalue" } + def request_via_redirect(http_method, path, *args) + process_with_kwargs(http_method, path, *args) + follow_redirect! while redirect? status end # Performs a GET request, following any subsequent redirect. # See +request_via_redirect+ for more information. - def get_via_redirect(path, parameters = nil, headers_or_env = nil) - request_via_redirect(:get, path, parameters, headers_or_env) + def get_via_redirect(path, *args) + ActiveSupport::Deprecation.warn('`get_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + request_via_redirect(:get, path, *args) end # Performs a POST request, following any subsequent redirect. # See +request_via_redirect+ for more information. - def post_via_redirect(path, parameters = nil, headers_or_env = nil) - request_via_redirect(:post, path, parameters, headers_or_env) + def post_via_redirect(path, *args) + ActiveSupport::Deprecation.warn('`post_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + request_via_redirect(:post, path, *args) end # Performs a PATCH request, following any subsequent redirect. # See +request_via_redirect+ for more information. - def patch_via_redirect(path, parameters = nil, headers_or_env = nil) - request_via_redirect(:patch, path, parameters, headers_or_env) + def patch_via_redirect(path, *args) + ActiveSupport::Deprecation.warn('`patch_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + request_via_redirect(:patch, path, *args) end # Performs a PUT request, following any subsequent redirect. # See +request_via_redirect+ for more information. - def put_via_redirect(path, parameters = nil, headers_or_env = nil) - request_via_redirect(:put, path, parameters, headers_or_env) + def put_via_redirect(path, *args) + ActiveSupport::Deprecation.warn('`put_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + request_via_redirect(:put, path, *args) end # Performs a DELETE request, following any subsequent redirect. # See +request_via_redirect+ for more information. - def delete_via_redirect(path, parameters = nil, headers_or_env = nil) - request_via_redirect(:delete, path, parameters, headers_or_env) + def delete_via_redirect(path, *args) + ActiveSupport::Deprecation.warn('`delete_via_redirect` is deprecated and will be removed in the next version of Rails. Please use follow_redirect! manually after the request call for the same behavior.') + request_via_redirect(:delete, path, *args) end end @@ -185,15 +222,6 @@ module ActionDispatch super() @app = app - # If the app is a Rails app, make url_helpers available on the session - # This makes app.url_for and app.foo_path available in the console - if app.respond_to?(:routes) - singleton_class.class_eval do - include app.routes.url_helpers - include app.routes.mounted_helpers - end - end - reset! end @@ -261,8 +289,38 @@ module ActionDispatch @_mock_session ||= Rack::MockSession.new(@app, host) end + def process_with_kwargs(http_method, path, *args) + if kwarg_request?(*args) + process(http_method, path, *args) + else + non_kwarg_request_warning if args.present? + process(http_method, path, { params: args[0], headers: args[1] }) + end + end + + REQUEST_KWARGS = %i(params headers env xhr) + def kwarg_request?(*args) + args[0].respond_to?(:keys) && args[0].keys.any? { |k| REQUEST_KWARGS.include?(k) } + end + + def non_kwarg_request_warning + ActiveSupport::Deprecation.warn(<<-MSG.strip_heredoc) + ActionDispatch::IntegrationTest HTTP request methods will accept only + the following keyword arguments in future Rails versions: + #{REQUEST_KWARGS.join(', ')} + + Examples: + + get '/profile', + params: { id: 1 }, + headers: { 'X-Extra-Header' => '123' }, + env: { 'action_dispatch.custom' => 'custom' }, + xhr: true + MSG + end + # Performs the actual request. - def process(method, path, parameters = nil, headers_or_env = nil) + def process(method, path, params: nil, headers: nil, env: nil, xhr: false) if path =~ %r{://} location = URI.parse(path) https! URI::HTTPS === location if location.scheme @@ -272,9 +330,9 @@ module ActionDispatch hostname, port = host.split(':') - env = { + request_env = { :method => method, - :params => parameters, + :params => params, "SERVER_NAME" => hostname, "SERVER_PORT" => port || (https? ? "443" : "80"), @@ -287,14 +345,26 @@ module ActionDispatch "CONTENT_TYPE" => "application/x-www-form-urlencoded", "HTTP_ACCEPT" => accept } - # this modifies the passed env directly - Http::Headers.new(env).merge!(headers_or_env || {}) + + if xhr + headers ||= {} + headers['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' + headers['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') + end + + # this modifies the passed request_env directly + if headers.present? + Http::Headers.new(request_env).merge!(headers) + end + if env.present? + Http::Headers.new(request_env).merge!(env) + end session = Rack::Test::Session.new(_mock_session) # NOTE: rack-test v0.5 doesn't build a default uri correctly # Make sure requested path is always a full uri - session.request(build_full_uri(path, env), env) + session.request(build_full_uri(path, request_env), request_env) @request_count += 1 @request = ActionDispatch::Request.new(session.last_request.env) @@ -305,7 +375,7 @@ module ActionDispatch @controller = session.last_request.env['action_controller.instance'] - return response.status + response.status end def build_full_uri(path, env) @@ -316,14 +386,36 @@ module ActionDispatch module Runner include ActionDispatch::Assertions - def app - @app ||= nil + APP_SESSIONS = {} + + attr_reader :app + + def before_setup + super + @app = nil + @integration_session = nil + end + + def integration_session + @integration_session ||= create_session(app) end # Reset the current session. This is useful for testing multiple sessions # in a single test case. def reset! - @integration_session = Integration::Session.new(app) + @integration_session = create_session(app) + end + + def create_session(app) + klass = APP_SESSIONS[app] ||= Class.new(Integration::Session) { + # If the app is a Rails app, make url_helpers available on the session + # This makes app.url_for and app.foo_path available in the console + if app.respond_to?(:routes) + include app.routes.url_helpers + include app.routes.mounted_helpers + end + } + klass.new(app) end def remove! # :nodoc: @@ -333,8 +425,6 @@ module ActionDispatch %w(get post patch put head delete cookies assigns xml_http_request xhr get_via_redirect post_via_redirect).each do |method| define_method(method) do |*args| - reset! unless integration_session - # reset the html_document variable, except for cookies/assigns calls unless method == 'cookies' || method == 'assigns' @html_document = nil @@ -366,19 +456,16 @@ module ActionDispatch # Copy the instance variables from the current session instance into the # test instance. def copy_session_variables! #:nodoc: - return unless integration_session - %w(controller response request).each do |var| - instance_variable_set("@#{var}", @integration_session.__send__(var)) - end + @controller = @integration_session.controller + @response = @integration_session.response + @request = @integration_session.request end def default_url_options - reset! unless integration_session integration_session.default_url_options end def default_url_options=(options) - reset! unless integration_session integration_session.default_url_options = options end @@ -388,7 +475,6 @@ module ActionDispatch # Delegate unhandled messages to the current session instance. def method_missing(sym, *args, &block) - reset! unless integration_session if integration_session.respond_to?(sym) integration_session.__send__(sym, *args, &block).tap do copy_session_variables! @@ -397,11 +483,6 @@ module ActionDispatch super end end - - private - def integration_session - @integration_session ||= nil - end end end @@ -578,7 +659,6 @@ module ActionDispatch end def url_options - reset! unless integration_session integration_session.url_options end diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb index 82039e72e7..369ea1467c 100644 --- a/actionpack/lib/action_dispatch/testing/test_response.rb +++ b/actionpack/lib/action_dispatch/testing/test_response.rb @@ -25,5 +25,12 @@ module ActionDispatch # Was there a server-side error? alias_method :error?, :server_error? + + def merge_default_headers(original, *args) + # Default headers are already applied, no need to merge them a second time. + # This makes sure that default headers, removed in controller actions, will + # not be reapplied to the test response. + original + end end end |