aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_controller')
-rw-r--r--actionpack/lib/action_controller/api.rb147
-rw-r--r--actionpack/lib/action_controller/base.rb10
-rw-r--r--actionpack/lib/action_controller/caching.rb2
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb2
-rw-r--r--actionpack/lib/action_controller/metal.rb59
-rw-r--r--actionpack/lib/action_controller/metal/basic_implicit_render.rb11
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb6
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb12
-rw-r--r--actionpack/lib/action_controller/metal/head.rb14
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb4
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb8
-rw-r--r--actionpack/lib/action_controller/metal/implicit_render.rb26
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb8
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb8
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb2
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb14
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb14
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb2
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb235
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb6
-rw-r--r--actionpack/lib/action_controller/middleware.rb39
-rw-r--r--actionpack/lib/action_controller/template_assertions.rb185
-rw-r--r--actionpack/lib/action_controller/test_case.rb270
23 files changed, 599 insertions, 485 deletions
diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb
new file mode 100644
index 0000000000..b4594bf302
--- /dev/null
+++ b/actionpack/lib/action_controller/api.rb
@@ -0,0 +1,147 @@
+require 'action_view'
+require 'action_controller'
+require 'action_controller/log_subscriber'
+
+module ActionController
+ # API Controller is a lightweight version of <tt>ActionController::Base</tt>,
+ # created for applications that don't require all functionalities that a complete
+ # \Rails controller provides, allowing you to create controllers with just the
+ # features that you need for API only applications.
+ #
+ # An API Controller is different from a normal controller in the sense that
+ # by default it doesn't include a number of features that are usually required
+ # by browser access only: layouts and templates rendering, cookies, sessions,
+ # flash, assets, and so on. This makes the entire controller stack thinner,
+ # suitable for API applications. It doesn't mean you won't have such
+ # features if you need them: they're all available for you to include in
+ # your application, they're just not part of the default API Controller stack.
+ #
+ # By default, only the ApplicationController in a \Rails application inherits
+ # from <tt>ActionController::API</tt>. All other controllers in turn inherit
+ # from ApplicationController.
+ #
+ # A sample controller could look like this:
+ #
+ # class PostsController < ApplicationController
+ # def index
+ # @posts = Post.all
+ # render json: @posts
+ # end
+ # end
+ #
+ # Request, response and parameters objects all work the exact same way as
+ # <tt>ActionController::Base</tt>.
+ #
+ # == Renders
+ #
+ # The default API Controller stack includes all renderers, which means you
+ # can use <tt>render :json</tt> and brothers freely in your controllers. Keep
+ # in mind that templates are not going to be rendered, so you need to ensure
+ # your controller is calling either <tt>render</tt> or <tt>redirect</tt> in
+ # all actions, otherwise it will return 204 No Content response.
+ #
+ # def show
+ # @post = Post.find(params[:id])
+ # render json: @post
+ # end
+ #
+ # == Redirects
+ #
+ # Redirects are used to move from one action to another. You can use the
+ # <tt>redirect</tt> method in your controllers in the same way as
+ # <tt>ActionController::Base</tt>. For example:
+ #
+ # def create
+ # redirect_to root_url and return if not_authorized?
+ # # do stuff here
+ # end
+ #
+ # == Adding new behavior
+ #
+ # In some scenarios you may want to add back some functionality provided by
+ # <tt>ActionController::Base</tt> that is not present by default in
+ # <tt>ActionController::API</tt>, for instance <tt>MimeResponds</tt>. This
+ # module gives you the <tt>respond_to</tt> method. Adding it is quite simple,
+ # you just need to include the module in a specific controller or in
+ # +ApplicationController+ in case you want it available in your entire
+ # application:
+ #
+ # class ApplicationController < ActionController::API
+ # include ActionController::MimeResponds
+ # end
+ #
+ # class PostsController < ApplicationController
+ # def index
+ # @posts = Post.all
+ #
+ # respond_to do |format|
+ # format.json { render json: @posts }
+ # format.xml { render xml: @posts }
+ # end
+ # end
+ # end
+ #
+ # Quite straightforward. Make sure to check <tt>ActionController::Base</tt>
+ # available modules if you want to include any other functionality that is
+ # not provided by <tt>ActionController::API</tt> out of the box.
+ class API < Metal
+ abstract!
+
+ # Shortcut helper that returns all the ActionController::API modules except
+ # the ones passed as arguments:
+ #
+ # class MyAPIBaseController < ActionController::Metal
+ # ActionController::API.without_modules(:ForceSSL, :UrlFor).each do |left|
+ # include left
+ # end
+ # end
+ #
+ # This gives better control over what you want to exclude and makes it easier
+ # to create an API controller class, instead of listing the modules required
+ # manually.
+ def self.without_modules(*modules)
+ modules = modules.map do |m|
+ m.is_a?(Symbol) ? ActionController.const_get(m) : m
+ end
+
+ MODULES - modules
+ end
+
+ MODULES = [
+ AbstractController::Rendering,
+
+ UrlFor,
+ Redirecting,
+ Rendering,
+ Renderers::All,
+ ConditionalGet,
+ RackDelegation,
+ BasicImplicitRender,
+ StrongParameters,
+
+ ForceSSL,
+ DataStreaming,
+
+ # Before callbacks should also be executed as early as possible, so
+ # also include them at the bottom.
+ AbstractController::Callbacks,
+
+ # Append rescue at the bottom to wrap as much as possible.
+ Rescue,
+
+ # Add instrumentations hooks at the bottom, to ensure they instrument
+ # all the methods properly.
+ Instrumentation,
+
+ # Params wrapper should come before instrumentation so they are
+ # properly showed in logs
+ ParamsWrapper
+ ]
+
+ MODULES.each do |mod|
+ include mod
+ end
+
+ ActiveSupport.run_load_hooks(:action_controller, self)
+ end
+end
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index bfae372f53..55734b9774 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -50,9 +50,9 @@ module ActionController
#
# == Parameters
#
- # All request parameters, whether they come from a GET or POST request, or from the URL, are available through the params method
- # which returns a hash. For example, an action that was performed through <tt>/posts?category=All&limit=5</tt> will include
- # <tt>{ "category" => "All", "limit" => "5" }</tt> in params.
+ # All request parameters, whether they come from a query string in the URL or form data submitted through a POST request are
+ # available through the params method which returns a hash. For example, an action that was performed through
+ # <tt>/posts?category=All&limit=5</tt> will include <tt>{ "category" => "All", "limit" => "5" }</tt> in params.
#
# It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as:
#
@@ -183,7 +183,7 @@ module ActionController
# Shortcut helper that returns all the modules included in
# ActionController::Base except the ones passed as arguments:
#
- # class MetalController
+ # class MyBaseController < ActionController::Metal
# ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
# include left
# end
@@ -252,7 +252,7 @@ module ActionController
# Define some internal variables that should not be propagated to the view.
PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [
- :@_status, :@_headers, :@_params, :@_env, :@_response, :@_request,
+ :@_status, :@_headers, :@_params, :@_response, :@_request,
:@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout ]
def _protected_ivars # :nodoc:
diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb
index de85e0c1a7..a4e4992cfe 100644
--- a/actionpack/lib/action_controller/caching.rb
+++ b/actionpack/lib/action_controller/caching.rb
@@ -8,7 +8,7 @@ module ActionController
#
# You can read more about each approach by clicking the modules below.
#
- # Note: To turn off all caching, set
+ # Note: To turn off all caching provided by Action Controller, set
# config.action_controller.perform_caching = false
#
# == \Caching stores
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 87609d8aa7..4c9f14e409 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -25,7 +25,7 @@ module ActionController
status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
end
message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
- message << " (#{additions.join(" | ")})" unless additions.blank?
+ message << " (#{additions.join(" | ".freeze)})" unless additions.blank?
message
end
end
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index ae111e4951..914b0d4b30 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -1,5 +1,6 @@
require 'active_support/core_ext/array/extract_options'
require 'action_dispatch/middleware/stack'
+require 'active_support/deprecation'
module ActionController
# Extend ActionDispatch middleware stack to make it aware of options
@@ -11,22 +12,14 @@ module ActionController
#
class MiddlewareStack < ActionDispatch::MiddlewareStack #:nodoc:
class Middleware < ActionDispatch::MiddlewareStack::Middleware #:nodoc:
- def initialize(klass, *args, &block)
- options = args.extract_options!
- @only = Array(options.delete(:only)).map(&:to_s)
- @except = Array(options.delete(:except)).map(&:to_s)
- args << options unless options.empty?
- super
+ def initialize(klass, args, actions, strategy, block)
+ @actions = actions
+ @strategy = strategy
+ super(klass, args, block)
end
def valid?(action)
- if @only.present?
- @only.include?(action)
- elsif @except.present?
- !@except.include?(action)
- else
- true
- end
+ @strategy.call @actions, action
end
end
@@ -37,6 +30,32 @@ module ActionController
middleware.valid?(action) ? middleware.build(a) : a
end
end
+
+ private
+
+ INCLUDE = ->(list, action) { list.include? action }
+ EXCLUDE = ->(list, action) { !list.include? action }
+ NULL = ->(list, action) { true }
+
+ def build_middleware(klass, args, block)
+ options = args.extract_options!
+ only = Array(options.delete(:only)).map(&:to_s)
+ except = Array(options.delete(:except)).map(&:to_s)
+ args << options unless options.empty?
+
+ strategy = NULL
+ list = nil
+
+ if only.any?
+ strategy = INCLUDE
+ list = only
+ elsif except.any?
+ strategy = EXCLUDE
+ list = except
+ end
+
+ Middleware.new(get_class(klass), args, list, strategy, block)
+ end
end
# <tt>ActionController::Metal</tt> is the simplest possible controller, providing a
@@ -98,11 +117,10 @@ module ActionController
class Metal < AbstractController::Base
abstract!
- attr_internal_writer :env
-
def env
- @_env ||= {}
+ @_request.env
end
+ deprecate :env
# Returns the last part of the controller's name, underscored, without the ending
# <tt>Controller</tt>. For instance, PostsController returns <tt>posts</tt>.
@@ -197,8 +215,7 @@ module ActionController
def set_request!(request) #:nodoc:
@_request = request
- @_env = request.env
- @_env['action_controller.instance'] = self
+ @_request.controller_instance = self
end
def to_a #:nodoc:
@@ -232,13 +249,13 @@ module ActionController
end
# Returns a Rack endpoint for the given action name.
- def self.action(name, klass = ActionDispatch::Request)
+ def self.action(name)
if middleware_stack.any?
middleware_stack.build(name) do |env|
- new.dispatch(name, klass.new(env))
+ new.dispatch(name, ActionDispatch::Request.new(env))
end
else
- lambda { |env| new.dispatch(name, klass.new(env)) }
+ lambda { |env| new.dispatch(name, ActionDispatch::Request.new(env)) }
end
end
end
diff --git a/actionpack/lib/action_controller/metal/basic_implicit_render.rb b/actionpack/lib/action_controller/metal/basic_implicit_render.rb
new file mode 100644
index 0000000000..6c6f8381ff
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/basic_implicit_render.rb
@@ -0,0 +1,11 @@
+module ActionController
+ module BasicImplicitRender
+ def send_action(method, *args)
+ super.tap { default_render unless performed? }
+ end
+
+ def default_render(*args)
+ head :no_content
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 1abd8d3a33..e6d7f958bb 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -85,6 +85,10 @@ module ActionController #:nodoc:
@to_path = path
end
+ def body
+ File.binread(to_path)
+ end
+
# Stream the file's contents if Rack::Sendfile isn't present.
def each
File.open(to_path, 'rb') do |file|
@@ -126,7 +130,7 @@ module ActionController #:nodoc:
# See +send_file+ for more information on HTTP Content-* headers and caching.
def send_data(data, options = {}) #:doc:
send_file_headers! options
- render options.slice(:status, :content_type).merge(:text => data)
+ render options.slice(:status, :content_type).merge(body: data)
end
private
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index d920668184..e31d65aac2 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -55,10 +55,10 @@ module ActionController
# You can pass any of the following options to affect the before_action callback
# * <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
- # * <tt>if</tt> - A symbol naming an instance method or a proc; the callback
- # will be called only when it returns a true value.
- # * <tt>unless</tt> - A symbol naming an instance method or a proc; the callback
- # will be called only when it returns a false value.
+ # * <tt>if</tt> - A symbol naming an instance method or a proc; the
+ # callback will be called only when it returns a true value.
+ # * <tt>unless</tt> - A symbol naming an instance method or a proc; the
+ # callback will be called only when it returns a false value.
def force_ssl(options = {})
action_options = options.slice(*ACTION_OPTIONS)
redirect_options = options.except(*ACTION_OPTIONS)
@@ -71,8 +71,8 @@ module ActionController
# Redirect the existing request to use the HTTPS protocol.
#
# ==== Parameters
- # * <tt>host_or_options</tt> - Either a host name or any of the url & redirect options
- # available to the <tt>force_ssl</tt> method.
+ # * <tt>host_or_options</tt> - Either a host name or any of the url &
+ # redirect options available to the <tt>force_ssl</tt> method.
def force_ssl_redirect(host_or_options = nil)
unless request.ssl?
options = {
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index 70f42bf565..f445094bdc 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -17,8 +17,18 @@ module ActionController
#
# See Rack::Utils::SYMBOL_TO_STATUS_CODE for a full list of valid +status+ symbols.
def head(status, options = {})
- options, status = status, nil if status.is_a?(Hash)
- status ||= options.delete(:status) || :ok
+ if status.is_a?(Hash)
+ msg = status[:status] ? 'The :status option' : 'The implicit :ok status'
+ options, status = status, status.delete(:status)
+
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ #{msg} on `head` has been deprecated and will be removed in Rails 5.1.
+ Please pass the status as a separate parameter before the options, instead.
+ MSG
+ end
+
+ status ||= :ok
+
location = options.delete(:location)
content_type = options.delete(:content_type)
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index b4da381d26..fcaf3e6425 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -73,7 +73,7 @@ module ActionController
# Provides a proxy to access helpers methods from outside the view.
def helpers
- @helper_proxy ||= begin
+ @helper_proxy ||= begin
proxy = ActionView::Base.new
proxy.config = config.inheritable_copy
proxy.extend(_helpers)
@@ -100,7 +100,7 @@ module ActionController
def all_helpers_from_path(path)
helpers = Array(path).flat_map do |_path|
extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
- names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') }
+ names = Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1'.freeze) }
names.sort!
end
helpers.uniq!
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 32c3c9652f..bbb38cf8fc 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -94,7 +94,7 @@ module ActionController
end
def has_basic_credentials?(request)
- request.authorization.present? && (auth_scheme(request) == 'Basic')
+ request.authorization.present? && (auth_scheme(request).downcase == 'basic')
end
def user_name_and_password(request)
@@ -397,7 +397,7 @@ module ActionController
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
module Token
TOKEN_KEY = 'token='
- TOKEN_REGEX = /^Token /
+ TOKEN_REGEX = /^(Token|Bearer) /
AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/
extend self
@@ -493,7 +493,7 @@ module ActionController
"Token #{values * ", "}"
end
- # Sets a WWW-Authenticate to let the client know a token is desired.
+ # Sets a WWW-Authenticate header to let the client know a token is desired.
#
# controller - ActionController::Base instance for the outgoing response.
# realm - String realm to use in the header.
@@ -502,7 +502,7 @@ module ActionController
def authentication_request(controller, realm, message = nil)
message ||= "HTTP Token: Access denied.\n"
controller.headers["WWW-Authenticate"] = %(Token realm="#{realm.tr('"'.freeze, "".freeze)}")
- controller.__send__ :render, :text => message, :status => :unauthorized
+ controller.__send__ :render, plain: message, status: :unauthorized
end
end
end
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index 1573ea7099..17fcc2fa02 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -1,17 +1,29 @@
module ActionController
module ImplicitRender
- def send_action(method, *args)
- ret = super
- default_render unless performed?
- ret
- end
+ include BasicImplicitRender
+
+ # Renders the template corresponding to the controller action, if it exists.
+ # The action name, format, and variant are all taken into account.
+ # For example, the "new" action with an HTML format and variant "phone"
+ # would try to render the <tt>new.html+phone.erb</tt> template.
+ #
+ # If no template is found <tt>ActionController::BasicImplicitRender</tt>'s implementation is called, unless
+ # a block is passed. In that case, it will override the super implementation.
+ #
+ # default_render do
+ # head 404 # No template was found
+ # end
def default_render(*args)
if template_exists?(action_name.to_s, _prefixes, variants: request.variant)
render(*args)
else
- logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
- head :no_content
+ if block_given?
+ yield(*args)
+ else
+ logger.info "No template found for #{self.class.name}\##{action_name}, rendering head :no_content" if logger
+ super
+ end
end
end
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index fab1be3459..1db68db20f 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -156,16 +156,16 @@ module ActionController #:nodoc:
# It works for both inline:
#
# respond_to do |format|
- # format.html.any { render text: "any" }
- # format.html.phone { render text: "phone" }
+ # format.html.any { render html: "any" }
+ # format.html.phone { render html: "phone" }
# end
#
# and block syntax:
#
# respond_to do |format|
# format.html do |variant|
- # variant.any(:tablet, :phablet){ render text: "any" }
- # variant.phone { render text: "phone" }
+ # variant.any(:tablet, :phablet){ render html: "any" }
+ # variant.phone { render html: "phone" }
# end
# end
#
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index 8a4ea70649..e680432127 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -4,8 +4,8 @@ require 'active_support/core_ext/module/anonymous'
require 'action_dispatch/http/mime_type'
module ActionController
- # Wraps the parameters hash into a nested hash. This will allow clients to submit
- # POST requests without having to specify any root elements.
+ # Wraps the parameters hash into a nested hash. This will allow clients to
+ # submit requests without having to specify any root elements.
#
# This functionality is enabled in +config/initializers/wrap_parameters.rb+
# and can be customized.
@@ -14,7 +14,7 @@ module ActionController
# a non-empty array:
#
# class UsersController < ApplicationController
- # wrap_parameters format: [:json, :xml]
+ # wrap_parameters format: [:json, :xml, :url_encoded_form, :multipart_form]
# end
#
# If you enable +ParamsWrapper+ for +:json+ format, instead of having to
@@ -40,7 +40,7 @@ module ActionController
# wrap_parameters :person, include: [:username, :password]
# end
#
- # On ActiveRecord models with no +:include+ or +:exclude+ option set,
+ # On Active Record models with no +:include+ or +:exclude+ option set,
# it will only wrap the parameters returned by the class method
# <tt>attribute_names</tt>.
#
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 45d3962494..cb74c4f0d4 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -94,7 +94,7 @@ module ActionController
# This method is the opposite of add method.
#
- # Usage:
+ # To remove a csv renderer:
#
# ActionController::Renderers.remove(:csv)
def self.remove(key)
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index b9ae8dd5ea..a3b0645dc0 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -1,3 +1,6 @@
+require 'active_support/deprecation'
+require 'active_support/core_ext/string/filters'
+
module ActionController
module Rendering
extend ActiveSupport::Concern
@@ -74,6 +77,17 @@ module ActionController
def _normalize_options(options) #:nodoc:
_normalize_text(options)
+ if options[:text]
+ ActiveSupport::Deprecation.warn <<-WARNING.squish
+ `render :text` is deprecated because it does not actually render a
+ `text/plain` response. Switch to `render plain: 'plain text'` to
+ render as `text/plain`, `render html: '<strong>HTML</strong>'` to
+ render as `text/html`, or `render body: 'raw'` to match the deprecated
+ behavior and render with the default Content-Type, which is
+ `text/plain`.
+ WARNING
+ end
+
if options[:html]
options[:html] = ERB::Util.html_escape(options[:html])
end
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index a7bd3622ee..d21a778d8d 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -20,7 +20,7 @@ module ActionController #:nodoc:
# Since HTML and JavaScript requests are typically made from the browser, we
# need to ensure to verify request authenticity for the web browser. We can
# use session-oriented authentication for these types of requests, by using
- # the `protect_form_forgery` method in our controllers.
+ # the `protect_from_forgery` method in our controllers.
#
# GET requests are not protected since they don't have side effects like writing
# to the database and don't leak sensitive information. JavaScript requests are
@@ -139,7 +139,7 @@ module ActionController #:nodoc:
request.session = NullSessionHash.new(request.env)
request.env['action_dispatch.request.flash_hash'] = nil
request.env['rack.session.options'] = { skip: true }
- request.env['action_dispatch.cookies'] = NullCookieJar.build(request)
+ request.cookie_jar = NullCookieJar.build(request, {})
end
protected
@@ -160,14 +160,6 @@ module ActionController #:nodoc:
end
class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
- def self.build(request)
- key_generator = request.env[ActionDispatch::Cookies::GENERATOR_KEY]
- host = request.host
- secure = request.ssl?
-
- new(key_generator, host, secure, options_for_env({}))
- end
-
def write(*)
# nothing
end
@@ -258,7 +250,7 @@ module ActionController #:nodoc:
# Returns true or false if a request is verified. Checks:
#
- # * is it a GET or HEAD request? Gets should be safe and idempotent
+ # * Is it a GET or HEAD request? Gets should be safe and idempotent
# * Does the form_authenticity_token match the given token value from the params?
# * Does the X-CSRF-Token header match the form_authenticity_token
def verified_request?
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index af31de1f3a..a6115674aa 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -199,7 +199,7 @@ module ActionController #:nodoc:
def _process_options(options) #:nodoc:
super
if options[:stream]
- if env["HTTP_VERSION"] == "HTTP/1.0"
+ if request.version == "HTTP/1.0"
options.delete(:stream)
else
headers["Cache-Control"] ||= "no-cache"
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index c98e937423..fc8e345d43 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -11,9 +11,9 @@ module ActionController
#
# params = ActionController::Parameters.new(a: {})
# params.fetch(:b)
- # # => ActionController::ParameterMissing: param not found: b
+ # # => ActionController::ParameterMissing: param is missing or the value is empty: b
# params.require(:a)
- # # => ActionController::ParameterMissing: param not found: a
+ # # => ActionController::ParameterMissing: param is missing or the value is empty: a
class ParameterMissing < KeyError
attr_reader :param # :nodoc:
@@ -23,11 +23,13 @@ module ActionController
end
end
- # Raised when a supplied parameter is not expected.
+ # Raised when a supplied parameter is not expected and
+ # ActionController::Parameters.action_on_unpermitted_parameters
+ # is set to <tt>:raise</tt>.
#
# params = ActionController::Parameters.new(a: "123", b: "456")
# params.permit(:c)
- # # => ActionController::UnpermittedParameters: found unexpected keys: a, b
+ # # => ActionController::UnpermittedParameters: found unpermitted parameters: a, b
class UnpermittedParameters < IndexError
attr_reader :params # :nodoc:
@@ -102,10 +104,12 @@ module ActionController
# params = ActionController::Parameters.new(key: 'value')
# params[:key] # => "value"
# params["key"] # => "value"
- class Parameters < ActiveSupport::HashWithIndifferentAccess
+ class Parameters
cattr_accessor :permit_all_parameters, instance_accessor: false
cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
+ delegate :keys, :key?, :has_key?, :empty?, :inspect, to: :@parameters
+
# By default, never raise an UnpermittedParameters exception if these
# params are present. The default includes both 'controller' and 'action'
# because they are added by Rails and should be of no concern. One way
@@ -142,11 +146,22 @@ module ActionController
# params = ActionController::Parameters.new(name: 'Francesco')
# params.permitted? # => true
# Person.new(params) # => #<Person id: nil, name: "Francesco">
- def initialize(attributes = nil)
- super(attributes)
+ def initialize(parameters = {})
+ @parameters = parameters.with_indifferent_access
@permitted = self.class.permit_all_parameters
end
+ # Returns true if another +Parameters+ object contains the same content and
+ # permitted flag, or other Hash-like object contains the same content. This
+ # override is in place so you can perform a comparison with `Hash`.
+ def ==(other_hash)
+ if other_hash.respond_to?(:permitted?)
+ super
+ else
+ @parameters == other_hash
+ end
+ end
+
# Returns a safe +Hash+ representation of this parameter with all
# unpermitted keys removed.
#
@@ -160,7 +175,7 @@ module ActionController
# safe_params.to_h # => {"name"=>"Senjougahara Hitagi"}
def to_h
if permitted?
- to_hash
+ @parameters.to_h
else
slice(*self.class.always_permitted_parameters).permit!.to_h
end
@@ -168,20 +183,17 @@ module ActionController
# Returns an unsafe, unfiltered +Hash+ representation of this parameter.
def to_unsafe_h
- to_hash
+ @parameters.to_h
end
alias_method :to_unsafe_hash, :to_unsafe_h
# Convert all hashes in values into parameters, then yield each pair like
# the same way as <tt>Hash#each_pair</tt>
def each_pair(&block)
- super do |key, value|
- convert_hashes_to_parameters(key, value)
+ @parameters.each_pair do |key, value|
+ yield key, convert_hashes_to_parameters(key, value)
end
-
- super
end
-
alias_method :each, :each_pair
# Attribute that keeps track of converted arrays, if any, to avoid double
@@ -236,10 +248,10 @@ module ActionController
# # => {"name"=>"Francesco"}
#
# ActionController::Parameters.new(person: nil).require(:person)
- # # => ActionController::ParameterMissing: param not found: person
+ # # => ActionController::ParameterMissing: param is missing or the value is empty: person
#
# ActionController::Parameters.new(person: {}).require(:person)
- # # => ActionController::ParameterMissing: param not found: person
+ # # => ActionController::ParameterMissing: param is missing or the value is empty: person
def require(key)
value = self[key]
if value.present? || value == false
@@ -345,7 +357,13 @@ module ActionController
# params[:person] # => {"name"=>"Francesco"}
# params[:none] # => nil
def [](key)
- convert_hashes_to_parameters(key, super)
+ convert_hashes_to_parameters(key, @parameters[key])
+ end
+
+ # Assigns a value to a given +key+. The given key may still get filtered out
+ # when +permit+ is called.
+ def []=(key, value)
+ @parameters[key] = value
end
# Returns a parameter for the given +key+. If the +key+
@@ -356,13 +374,19 @@ module ActionController
#
# params = ActionController::Parameters.new(person: { name: 'Francesco' })
# params.fetch(:person) # => {"name"=>"Francesco"}
- # params.fetch(:none) # => ActionController::ParameterMissing: param not found: none
+ # params.fetch(:none) # => ActionController::ParameterMissing: param is missing or the value is empty: none
# params.fetch(:none, 'Francesco') # => "Francesco"
# params.fetch(:none) { 'Francesco' } # => "Francesco"
- def fetch(key, *args)
- convert_hashes_to_parameters(key, super, false)
- rescue KeyError
- raise ActionController::ParameterMissing.new(key)
+ def fetch(key, *args, &block)
+ convert_value_to_parameters(
+ @parameters.fetch(key) {
+ if block_given?
+ yield
+ else
+ args.fetch(0) { raise ActionController::ParameterMissing.new(key) }
+ end
+ }
+ )
end
# Returns a new <tt>ActionController::Parameters</tt> instance that
@@ -373,7 +397,24 @@ module ActionController
# params.slice(:a, :b) # => {"a"=>1, "b"=>2}
# params.slice(:d) # => {}
def slice(*keys)
- new_instance_with_inherited_permitted_status(super)
+ new_instance_with_inherited_permitted_status(@parameters.slice(*keys))
+ end
+
+ # Returns current <tt>ActionController::Parameters</tt> instance which
+ # contains only the given +keys+.
+ def slice!(*keys)
+ @parameters.slice!(*keys)
+ self
+ end
+
+ # Returns a new <tt>ActionController::Parameters</tt> instance that
+ # filters out the given +keys+.
+ #
+ # params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
+ # params.except(:a, :b) # => {"c"=>3}
+ # params.except(:d) # => {"a"=>1,"b"=>2,"c"=>3}
+ def except(*keys)
+ new_instance_with_inherited_permitted_status(@parameters.except(*keys))
end
# Removes and returns the key/value pairs matching the given keys.
@@ -382,7 +423,7 @@ module ActionController
# params.extract!(:a, :b) # => {"a"=>1, "b"=>2}
# params # => {"c"=>3}
def extract!(*keys)
- new_instance_with_inherited_permitted_status(super)
+ new_instance_with_inherited_permitted_status(@parameters.extract!(*keys))
end
# Returns a new <tt>ActionController::Parameters</tt> with the results of
@@ -391,36 +432,80 @@ module ActionController
# params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
# params.transform_values { |x| x * 2 }
# # => {"a"=>2, "b"=>4, "c"=>6}
- def transform_values
- if block_given?
- new_instance_with_inherited_permitted_status(super)
+ def transform_values(&block)
+ if block
+ new_instance_with_inherited_permitted_status(
+ @parameters.transform_values(&block)
+ )
else
- super
+ @parameters.transform_values
end
end
- # This method is here only to make sure that the returned object has the
- # correct +permitted+ status. It should not matter since the parent of
- # this object is +HashWithIndifferentAccess+
- def transform_keys # :nodoc:
- if block_given?
- new_instance_with_inherited_permitted_status(super)
+ # Performs values transformation and returns the altered
+ # <tt>ActionController::Parameters</tt> instance.
+ def transform_values!(&block)
+ @parameters.transform_values!(&block)
+ self
+ end
+
+ # Returns a new <tt>ActionController::Parameters</tt> instance with the
+ # results of running +block+ once for every key. The values are unchanged.
+ def transform_keys(&block)
+ if block
+ new_instance_with_inherited_permitted_status(
+ @parameters.transform_keys(&block)
+ )
else
- super
+ @parameters.transform_keys
end
end
+ # Performs keys transformation and returns the altered
+ # <tt>ActionController::Parameters</tt> instance.
+ def transform_keys!(&block)
+ @parameters.transform_keys!(&block)
+ self
+ end
+
# Deletes and returns a key-value pair from +Parameters+ whose key is equal
# to key. If the key is not found, returns the default value. If the
# optional code block is given and the key is not found, pass in the key
# and return the result of block.
def delete(key, &block)
- convert_hashes_to_parameters(key, super, false)
+ convert_value_to_parameters(@parameters.delete(key))
+ end
+
+ # Returns a new instance of <tt>ActionController::Parameters</tt> with only
+ # items that the block evaluates to true.
+ def select(&block)
+ new_instance_with_inherited_permitted_status(@parameters.select(&block))
end
# Equivalent to Hash#keep_if, but returns nil if no changes were made.
def select!(&block)
- convert_value_to_parameters(super)
+ @parameters.select!(&block)
+ self
+ end
+ alias_method :keep_if, :select!
+
+ # Returns a new instance of <tt>ActionController::Parameters</tt> with items
+ # that the block evaluates to true removed.
+ def reject(&block)
+ new_instance_with_inherited_permitted_status(@parameters.reject(&block))
+ end
+
+ # Removes items that the block evaluates to true and returns self.
+ def reject!(&block)
+ @parameters.reject!(&block)
+ self
+ end
+ alias_method :delete_if, :reject!
+
+ # Return values that were assigned to the given +keys+. Note that all the
+ # +Hash+ objects will be converted to <tt>ActionController::Parameters</tt>.
+ def values_at(*keys)
+ convert_value_to_parameters(@parameters.values_at(*keys))
end
# Returns an exact copy of the <tt>ActionController::Parameters</tt>
@@ -437,11 +522,30 @@ module ActionController
end
end
+ # Returns a new <tt>ActionController::Parameters</tt> with all keys from
+ # +other_hash+ merges into current hash.
+ def merge(other_hash)
+ new_instance_with_inherited_permitted_status(
+ @parameters.merge(other_hash)
+ )
+ end
+
+ # This is required by ActiveModel attribute assignment, so that user can
+ # pass +Parameters+ to a mass assignment methods in a model. It should not
+ # matter as we are using +HashWithIndifferentAccess+ internally.
+ def stringify_keys # :nodoc:
+ dup
+ end
+
protected
def permitted=(new_permitted)
@permitted = new_permitted
end
+ def fields_for_style?
+ @parameters.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
+ end
+
private
def new_instance_with_inherited_permitted_status(hash)
self.class.new(hash).tap do |new_instance|
@@ -449,40 +553,41 @@ module ActionController
end
end
- def convert_hashes_to_parameters(key, value, assign_if_converted=true)
+ def convert_hashes_to_parameters(key, value)
converted = convert_value_to_parameters(value)
- self[key] = converted if assign_if_converted && !converted.equal?(value)
+ @parameters[key] = converted unless converted.equal?(value)
converted
end
def convert_value_to_parameters(value)
- if value.is_a?(Array) && !converted_arrays.member?(value)
+ case value
+ when Array
+ return value if converted_arrays.member?(value)
converted = value.map { |_| convert_value_to_parameters(_) }
converted_arrays << converted
converted
- elsif value.is_a?(Parameters) || !value.is_a?(Hash)
- value
- else
+ when Hash
self.class.new(value)
+ else
+ value
end
end
def each_element(object)
- if object.is_a?(Array)
- object.map { |el| yield el }.compact
- elsif fields_for_style?(object)
- hash = object.class.new
- object.each { |k,v| hash[k] = yield v }
- hash
- else
- yield object
+ case object
+ when Array
+ object.grep(Parameters).map { |el| yield el }.compact
+ when Parameters
+ if object.fields_for_style?
+ hash = object.class.new
+ object.each { |k,v| hash[k] = yield v }
+ hash
+ else
+ yield object
+ end
end
end
- def fields_for_style?(object)
- object.is_a?(Hash) && object.all? { |k, v| k =~ /\A-?\d+\z/ && v.is_a?(Hash) }
- end
-
def unpermitted_parameters!(params)
unpermitted_keys = unpermitted_keys(params)
if unpermitted_keys.any?
@@ -544,14 +649,8 @@ module ActionController
end
def array_of_permitted_scalars?(value)
- if value.is_a?(Array)
- value.all? {|element| permitted_scalar?(element)}
- end
- end
-
- def array_of_permitted_scalars_filter(params, key)
- if has_key?(key) && array_of_permitted_scalars?(self[key])
- params[key] = self[key]
+ if value.is_a?(Array) && value.all? {|element| permitted_scalar?(element)}
+ yield value
end
end
@@ -562,17 +661,17 @@ module ActionController
# Slicing filters out non-declared keys.
slice(*filter.keys).each do |key, value|
next unless value
+ next unless has_key? key
if filter[key] == EMPTY_ARRAY
# Declaration { comment_ids: [] }.
- array_of_permitted_scalars_filter(params, key)
+ array_of_permitted_scalars?(self[key]) do |val|
+ params[key] = val
+ end
else
# Declaration { user: :name } or { user: [:name, :age, { address: ... }] }.
params[key] = each_element(value) do |element|
- if element.is_a?(Hash)
- element = self.class.new(element) unless element.respond_to?(:permit)
- element.permit(*Array.wrap(filter[key]))
- end
+ element.permit(*Array.wrap(filter[key]))
end
end
end
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 5a0e5c62e4..dbf7241a14 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -41,7 +41,11 @@ module ActionController
if original_script_name
options[:original_script_name] = original_script_name
else
- options[:script_name] = same_origin ? request.script_name.dup : script_name
+ if same_origin
+ options[:script_name] = request.script_name.empty? ? "".freeze : request.script_name.dup
+ else
+ options[:script_name] = script_name
+ end
end
options.freeze
else
diff --git a/actionpack/lib/action_controller/middleware.rb b/actionpack/lib/action_controller/middleware.rb
deleted file mode 100644
index 437fec3dc6..0000000000
--- a/actionpack/lib/action_controller/middleware.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-module ActionController
- class Middleware < Metal
- class ActionMiddleware
- def initialize(controller, app)
- @controller, @app = controller, app
- end
-
- def call(env)
- request = ActionDispatch::Request.new(env)
- @controller.build(@app).dispatch(:index, request)
- end
- end
-
- class << self
- alias build new
-
- def new(app)
- ActionMiddleware.new(self, app)
- end
- end
-
- attr_internal :app
-
- def process(action)
- response = super
- self.status, self.headers, self.response_body = response if response.is_a?(Array)
- response
- end
-
- def initialize(app)
- super()
- @_app = app
- end
-
- def index
- call(env)
- end
- end
-end \ No newline at end of file
diff --git a/actionpack/lib/action_controller/template_assertions.rb b/actionpack/lib/action_controller/template_assertions.rb
index 304012d24d..0179f4afcd 100644
--- a/actionpack/lib/action_controller/template_assertions.rb
+++ b/actionpack/lib/action_controller/template_assertions.rb
@@ -1,188 +1,9 @@
module ActionController
module TemplateAssertions
- extend ActiveSupport::Concern
-
- included do
- setup :setup_subscriptions
- teardown :teardown_subscriptions
- end
-
- RENDER_TEMPLATE_INSTANCE_VARIABLES = %w{partials templates layouts files}.freeze
-
- def setup_subscriptions
- RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable|
- instance_variable_set("@_#{instance_variable}", Hash.new(0))
- end
-
- @_subscribers = []
-
- @_subscribers << ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload|
- path = payload[:layout]
- if path
- @_layouts[path] += 1
- if path =~ /^layouts\/(.*)/
- @_layouts[$1] += 1
- end
- end
- end
-
- @_subscribers << ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
- if virtual_path = payload[:virtual_path]
- partial = virtual_path =~ /^.*\/_[^\/]*$/
-
- if partial
- @_partials[virtual_path] += 1
- @_partials[virtual_path.split("/").last] += 1
- end
-
- @_templates[virtual_path] += 1
- else
- path = payload[:identifier]
- if path
- @_files[path] += 1
- @_files[path.split("/").last] += 1
- end
- end
- end
- end
-
- def teardown_subscriptions
- @_subscribers.each do |subscriber|
- ActiveSupport::Notifications.unsubscribe(subscriber)
- end
- end
-
- def process(*args)
- reset_template_assertion
- super
- end
-
- def reset_template_assertion
- RENDER_TEMPLATE_INSTANCE_VARIABLES.each do |instance_variable|
- ivar_name = "@_#{instance_variable}"
- if instance_variable_defined?(ivar_name)
- instance_variable_get(ivar_name).clear
- end
- end
- end
-
- # Asserts that the request was rendered with the appropriate template file or partials.
- #
- # # assert that the "new" view template was rendered
- # assert_template "new"
- #
- # # assert that the exact template "admin/posts/new" was rendered
- # assert_template %r{\Aadmin/posts/new\Z}
- #
- # # assert that the layout 'admin' was rendered
- # assert_template layout: 'admin'
- # assert_template layout: 'layouts/admin'
- # assert_template layout: :admin
- #
- # # assert that no layout was rendered
- # assert_template layout: nil
- # assert_template layout: false
- #
- # # assert that the "_customer" partial was rendered twice
- # assert_template partial: '_customer', count: 2
- #
- # # assert that no partials were rendered
- # assert_template partial: false
- #
- # # assert that a file was rendered
- # assert_template file: "README.rdoc"
- #
- # # assert that no file was rendered
- # assert_template file: nil
- # assert_template file: false
- #
- # In a view test case, you can also assert that specific locals are passed
- # to partials:
- #
- # # assert that the "_customer" partial was rendered with a specific object
- # assert_template partial: '_customer', locals: { customer: @customer }
def assert_template(options = {}, message = nil)
- # Force body to be read in case the template is being streamed.
- response.body
-
- case options
- when NilClass, Regexp, String, Symbol
- options = options.to_s if Symbol === options
- rendered = @_templates
- msg = message || sprintf("expecting <%s> but rendering with <%s>",
- options.inspect, rendered.keys)
- matches_template =
- case options
- when String
- !options.empty? && rendered.any? do |t, num|
- options_splited = options.split(File::SEPARATOR)
- t_splited = t.split(File::SEPARATOR)
- t_splited.last(options_splited.size) == options_splited
- end
- when Regexp
- rendered.any? { |t,num| t.match(options) }
- when NilClass
- rendered.blank?
- end
- assert matches_template, msg
- when Hash
- options.assert_valid_keys(:layout, :partial, :locals, :count, :file)
-
- if options.key?(:layout)
- expected_layout = options[:layout]
- msg = message || sprintf("expecting layout <%s> but action rendered <%s>",
- expected_layout, @_layouts.keys)
-
- case expected_layout
- when String, Symbol
- assert_includes @_layouts.keys, expected_layout.to_s, msg
- when Regexp
- assert(@_layouts.keys.any? {|l| l =~ expected_layout }, msg)
- when nil, false
- assert(@_layouts.empty?, msg)
- else
- raise ArgumentError, "assert_template only accepts a String, Symbol, Regexp, nil or false for :layout"
- end
- end
-
- if options[:file]
- assert_includes @_files.keys, options[:file]
- elsif options.key?(:file)
- assert @_files.blank?, "expected no files but #{@_files.keys} was rendered"
- end
-
- if expected_partial = options[:partial]
- if expected_locals = options[:locals]
- if defined?(@_rendered_views)
- view = expected_partial.to_s.sub(/^_/, '').sub(/\/_(?=[^\/]+\z)/, '/')
-
- partial_was_not_rendered_msg = "expected %s to be rendered but it was not." % view
- assert_includes @_rendered_views.rendered_views, view, partial_was_not_rendered_msg
-
- msg = 'expecting %s to be rendered with %s but was with %s' % [expected_partial,
- expected_locals,
- @_rendered_views.locals_for(view)]
- assert(@_rendered_views.view_rendered?(view, options[:locals]), msg)
- else
- warn "the :locals option to #assert_template is only supported in a ActionView::TestCase"
- end
- elsif expected_count = options[:count]
- actual_count = @_partials[expected_partial]
- msg = message || sprintf("expecting %s to be rendered %s time(s) but rendered %s time(s)",
- expected_partial, expected_count, actual_count)
- assert(actual_count == expected_count.to_i, msg)
- else
- msg = message || sprintf("expecting partial <%s> but action rendered <%s>",
- options[:partial], @_partials.keys)
- assert_includes @_partials, expected_partial, msg
- end
- elsif options.key?(:partial)
- assert @_partials.empty?,
- "Expected no partials to be rendered"
- end
- else
- raise ArgumentError, "assert_template only accepts a String, Symbol, Hash, Regexp, or nil"
- end
+ raise NoMethodError,
+ "assert_template has been extracted to a gem. To continue using it,
+ add `gem 'rails-controller-testing'` to your Gemfile."
end
end
end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index b29c5b23fc..e012fa617e 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -1,4 +1,5 @@
require 'rack/session/abstract/id'
+require 'active_support/core_ext/hash/conversions'
require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/module/anonymous'
require 'active_support/core_ext/hash/keys'
@@ -10,28 +11,44 @@ module ActionController
DEFAULT_ENV = ActionDispatch::TestRequest::DEFAULT_ENV.dup
DEFAULT_ENV.delete 'PATH_INFO'
- def initialize(env = {})
- super
+ def self.new_session
+ TestSession.new
+ end
+
+ # Create a new test request with default `env` values
+ def self.create
+ env = {}
+ env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application
+ env["rack.request.cookie_hash"] = {}.with_indifferent_access
+ new(default_env.merge(env), new_session)
+ end
- self.session = TestSession.new
+ def self.default_env
+ DEFAULT_ENV
+ end
+ private_class_method :default_env
+
+ def initialize(env, session)
+ super(env)
+
+ self.session = session
self.session_options = TestSession::DEFAULT_OPTIONS
end
- def assign_parameters(routes, controller_path, action, parameters = {})
- parameters = parameters.symbolize_keys
- extra_keys = routes.extra_keys(parameters.merge(:controller => controller_path, :action => action))
- non_path_parameters = get? ? query_parameters : request_parameters
+ def query_string=(string)
+ @env[Rack::QUERY_STRING] = string
+ end
- parameters.each do |key, value|
- if value.is_a?(Array) && (value.frozen? || value.any?(&:frozen?))
- value = value.map{ |v| v.duplicable? ? v.dup : v }
- elsif value.is_a?(Hash) && (value.frozen? || value.any?{ |k,v| v.frozen? })
- value = Hash[value.map{ |k,v| [k, v.duplicable? ? v.dup : v] }]
- elsif value.frozen? && value.duplicable?
- value = value.dup
- end
+ def request_parameters=(params)
+ @env["action_dispatch.request.request_parameters"] = params
+ end
- if extra_keys.include?(key) || key == :action || key == :controller
+ def assign_parameters(routes, controller_path, action, parameters, generated_path, query_string_keys)
+ non_path_parameters = {}
+ path_parameters = {}
+
+ parameters.each do |key, value|
+ if query_string_keys.include?(key)
non_path_parameters[key] = value
else
if value.is_a?(Array)
@@ -44,69 +61,83 @@ module ActionController
end
end
- path_parameters[:controller] = controller_path
- path_parameters[:action] = action
-
- # Clear the combined params hash in case it was already referenced.
- @env.delete("action_dispatch.request.parameters")
+ if get?
+ if self.query_string.blank?
+ self.query_string = non_path_parameters.to_query
+ end
+ else
+ if ENCODER.should_multipart?(non_path_parameters)
+ @env['CONTENT_TYPE'] = ENCODER.content_type
+ data = ENCODER.build_multipart non_path_parameters
+ else
+ @env['CONTENT_TYPE'] ||= 'application/x-www-form-urlencoded'
+
+ # FIXME: setting `request_parametes` is normally handled by the
+ # params parser middleware, and we should remove this roundtripping
+ # when we switch to caling `call` on the controller
+
+ case content_mime_type.ref
+ when :json
+ data = ActiveSupport::JSON.encode(non_path_parameters)
+ params = ActiveSupport::JSON.decode(data).with_indifferent_access
+ self.request_parameters = params
+ when :xml
+ data = non_path_parameters.to_xml
+ params = Hash.from_xml(data)['hash']
+ self.request_parameters = params
+ when :url_encoded_form
+ data = non_path_parameters.to_query
+ else
+ raise "Unknown Content-Type: #{content_type}"
+ end
+ end
- # Clear the filter cache variables so they're not stale
- @filtered_parameters = @filtered_env = @filtered_path = nil
+ @env['CONTENT_LENGTH'] = data.length.to_s
+ @env['rack.input'] = StringIO.new(data)
+ end
- data = request_parameters.to_query
- @env['CONTENT_LENGTH'] = data.length.to_s
- @env['rack.input'] = StringIO.new(data)
- end
+ @env["PATH_INFO"] ||= generated_path
+ path_parameters[:controller] = controller_path
+ path_parameters[:action] = action
- def recycle!
- @formats = nil
- @env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
- @env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
- @method = @request_method = nil
- @fullpath = @ip = @remote_ip = @protocol = nil
- @env['action_dispatch.request.query_parameters'] = {}
- @set_cookies ||= {}
- @set_cookies.update(Hash[cookie_jar.instance_variable_get("@set_cookies").map{ |k,o| [k,o[:value]] }])
- deleted_cookies = cookie_jar.instance_variable_get("@delete_cookies")
- @set_cookies.reject!{ |k,v| deleted_cookies.include?(k) }
- cookie_jar.update(rack_cookies)
- cookie_jar.update(cookies)
- cookie_jar.update(@set_cookies)
- cookie_jar.recycle!
+ self.path_parameters = path_parameters
end
- private
+ ENCODER = Class.new do
+ include Rack::Test::Utils
+
+ def should_multipart?(params)
+ # FIXME: lifted from Rack-Test. We should push this separation upstream
+ multipart = false
+ query = lambda { |value|
+ case value
+ when Array
+ value.each(&query)
+ when Hash
+ value.values.each(&query)
+ when Rack::Test::UploadedFile
+ multipart = true
+ end
+ }
+ params.values.each(&query)
+ multipart
+ end
- def default_env
- DEFAULT_ENV
- end
- end
+ public :build_multipart
- class TestResponse < ActionDispatch::TestResponse
- def recycle!
- initialize
- end
+ def content_type
+ "multipart/form-data; boundary=#{Rack::Test::MULTIPART_BOUNDARY}"
+ end
+ end.new
end
class LiveTestResponse < Live::Response
- def recycle!
- @body = nil
- initialize
- end
-
- def body
- @body ||= super
- end
-
# Was the response successful?
alias_method :success?, :successful?
# Was the URL not found?
alias_method :missing?, :not_found?
- # Were we redirected?
- alias_method :redirect?, :redirection?
-
# Was there a server-side error?
alias_method :error?, :server_error?
end
@@ -139,6 +170,10 @@ module ActionController
clear
end
+ def fetch(*args, &block)
+ @data.fetch(*args, &block)
+ end
+
private
def load!
@@ -165,7 +200,7 @@ module ActionController
# class BooksControllerTest < ActionController::TestCase
# def test_create
# # Simulate a POST response with the given HTTP parameters.
- # post(:create, book: { title: "Love Hina" })
+ # post(:create, params: { book: { title: "Love Hina" }})
#
# # Assert that the controller tried to redirect us to
# # the created book's URI.
@@ -195,7 +230,7 @@ module ActionController
# request. You can modify this object before sending the HTTP request. For example,
# you might want to set some session properties before sending a GET request.
# <b>@response</b>::
- # An ActionController::TestResponse object, representing the response
+ # An ActionDispatch::TestResponse object, representing the response
# of the last HTTP response. In the above example, <tt>@response</tt> becomes valid
# after calling +post+. If the various assert methods are not sufficient, then you
# may use this object to inspect the HTTP response in detail.
@@ -218,21 +253,15 @@ module ActionController
# In addition to these specific assertions, you also have easy access to various collections that the regular test/unit assertions
# can be used against. These collections are:
#
- # * assigns: Instance variables assigned in the action that are available for the view.
# * session: Objects being saved in the session.
# * flash: The flash objects currently in the session.
# * cookies: \Cookies being sent to the user on this request.
#
# These collections can be used just like any other hash:
#
- # assert_not_nil assigns(:person) # makes sure that a @person instance variable was set
# assert_equal "Dave", cookies[:name] # makes sure that a cookie called :name was set as "Dave"
# assert flash.empty? # makes sure that there's nothing in the flash
#
- # For historic reasons, the assigns hash uses string-based keys. So <tt>assigns[:person]</tt> won't work, but <tt>assigns["person"]</tt> will. To
- # appease our yearning for symbols, though, an alternative accessor has been devised using a method call instead of index referencing.
- # So <tt>assigns(:person)</tt> will work just like <tt>assigns["person"]</tt>, but again, <tt>assigns[:person]</tt> will not work.
- #
# On top of the collections, you have the complete url that a given action redirected to available in <tt>redirect_to_url</tt>.
#
# For redirects within the same controller, you can even call follow_redirect and the redirect will be followed, triggering another
@@ -323,7 +352,9 @@ module ActionController
# Note that the request method is not verified. The different methods are
# available to make the tests more expressive.
def get(action, *args)
- process_with_kwargs("GET", action, *args)
+ res = process_with_kwargs("GET", action, *args)
+ cookies.update res.cookies
+ res
end
# Simulate a POST request with the given parameters and set/volley the response.
@@ -371,19 +402,6 @@ module ActionController
end
alias xhr :xml_http_request
- def paramify_values(hash_or_array_or_value)
- case hash_or_array_or_value
- when Hash
- Hash[hash_or_array_or_value.map{|key, value| [key, paramify_values(value)] }]
- when Array
- hash_or_array_or_value.map {|i| paramify_values(i)}
- when Rack::Test::UploadedFile, ActionDispatch::Http::UploadedFile
- hash_or_array_or_value
- else
- hash_or_array_or_value.to_param
- end
- end
-
# Simulate a HTTP request to +action+ by specifying request method,
# parameters and set/volley the response.
#
@@ -443,10 +461,6 @@ module ActionController
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
@@ -457,17 +471,24 @@ module ActionController
@controller.extend(Testing::Functional)
end
- @request.recycle!
- @response.recycle!
+ self.cookies.update @request.cookies
+ @request.env['HTTP_COOKIE'] = cookies.to_header
+ @request.env['action_dispatch.cookies'] = nil
+
+ @request = TestRequest.new scrub_env!(@request.env), @request.session
+ @response = build_response @response_klass
+ @response.request = @request
@controller.recycle!
@request.env['REQUEST_METHOD'] = http_method
- controller_class_name = @controller.class.anonymous? ?
- "anonymous" :
- @controller.class.controller_path
+ parameters = parameters.symbolize_keys
- @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters)
+ generated_extras = @routes.generate_extras(parameters.merge(controller: controller_class_name, action: action.to_s))
+ generated_path = generated_path(generated_extras)
+ query_string_keys = query_parameter_names(generated_extras)
+
+ @request.assign_parameters(@routes, controller_class_name, action.to_s, parameters, generated_path, query_string_keys)
@request.session.update(session) if session
@request.flash.update(flash || {})
@@ -480,20 +501,21 @@ module ActionController
@controller.request = @request
@controller.response = @response
- build_request_uri(controller_class_name, action, parameters)
+ @request.env["SCRIPT_NAME"] ||= @controller.config.relative_url_root
@controller.recycle!
@controller.process(action)
+ @request.env.delete 'HTTP_COOKIE'
+
if cookies = @request.env['action_dispatch.cookies']
unless @response.committed?
cookies.write(@response)
+ self.cookies.update(cookies.instance_variable_get(:@cookies))
end
end
@response.prepare!
- @assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
-
if flash_value = @request.flash.to_session_value
@request.session['flash'] = flash_value
else
@@ -504,18 +526,31 @@ module ActionController
@request.env.delete 'HTTP_X_REQUESTED_WITH'
@request.env.delete 'HTTP_ACCEPT'
end
+ @request.query_string = ''
@response
end
+ def controller_class_name
+ @controller.class.anonymous? ? "anonymous" : @controller.class.controller_path
+ end
+
+ def generated_path(generated_extras)
+ generated_extras[0]
+ end
+
+ def query_parameter_names(generated_extras)
+ generated_extras[1] + [:controller, :action]
+ end
+
def setup_controller_request_and_response
@controller = nil unless defined? @controller
- response_klass = TestResponse
+ @response_klass = ActionDispatch::TestResponse
if klass = self.class.controller_class
if klass < ActionController::Live
- response_klass = LiveTestResponse
+ @response_klass = LiveTestResponse
end
unless @controller
begin
@@ -526,8 +561,8 @@ module ActionController
end
end
- @request = build_request
- @response = build_response response_klass
+ @request = TestRequest.create
+ @response = build_response @response_klass
@response.request = @request
if @controller
@@ -536,10 +571,6 @@ module ActionController
end
end
- def build_request
- TestRequest.new
- end
-
def build_response(klass)
klass.new
end
@@ -553,12 +584,20 @@ module ActionController
private
+ def scrub_env!(env)
+ env.delete_if { |k, v| k =~ /^(action_dispatch|rack)\.request/ }
+ env.delete_if { |k, v| k =~ /^action_dispatch\.rescue/ }
+ env.delete 'action_dispatch.request.query_parameters'
+ env.delete 'action_dispatch.request.request_parameters'
+ env
+ end
+
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?
+ non_kwarg_request_warning if args.any?
args = args.unshift(http_method)
process(action, *args)
@@ -599,23 +638,6 @@ module ActionController
end
end
- def build_request_uri(controller_class_name, action, parameters)
- unless @request.env["PATH_INFO"]
- options = @controller.respond_to?(:url_options) ? @controller.__send__(:url_options).merge(parameters) : parameters
- options.update(
- :controller => controller_class_name,
- :action => action,
- :relative_url_root => nil,
- :_recall => @request.path_parameters)
-
- url, query_string = @routes.path_for(options).split("?", 2)
-
- @request.env["SCRIPT_NAME"] = @controller.config.relative_url_root
- @request.env["PATH_INFO"] = url
- @request.env["QUERY_STRING"] = query_string || ""
- end
- end
-
def html_format?(parameters)
return true unless parameters.key?(:format)
Mime.fetch(parameters[:format]) { Mime['html'] }.html?