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/base.rb23
-rw-r--r--actionpack/lib/action_controller/caching.rb34
-rw-r--r--actionpack/lib/action_controller/caching/sweeping.rb116
-rw-r--r--actionpack/lib/action_controller/deprecated.rb7
-rw-r--r--actionpack/lib/action_controller/deprecated/integration_test.rb5
-rw-r--r--actionpack/lib/action_controller/deprecated/performance_test.rb3
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb7
-rw-r--r--actionpack/lib/action_controller/metal.rb8
-rw-r--r--actionpack/lib/action_controller/metal/conditional_get.rb6
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb11
-rw-r--r--actionpack/lib/action_controller/metal/exceptions.rb9
-rw-r--r--actionpack/lib/action_controller/metal/flash.rb19
-rw-r--r--actionpack/lib/action_controller/metal/force_ssl.rb71
-rw-r--r--actionpack/lib/action_controller/metal/head.rb7
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb16
-rw-r--r--actionpack/lib/action_controller/metal/hide_actions.rb10
-rw-r--r--actionpack/lib/action_controller/metal/http_authentication.rb77
-rw-r--r--actionpack/lib/action_controller/metal/instrumentation.rb2
-rw-r--r--actionpack/lib/action_controller/metal/live.rb108
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb42
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb166
-rw-r--r--actionpack/lib/action_controller/metal/redirecting.rb67
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb12
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb30
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb72
-rw-r--r--actionpack/lib/action_controller/metal/responder.rb36
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb54
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb327
-rw-r--r--actionpack/lib/action_controller/metal/url_for.rb5
-rw-r--r--actionpack/lib/action_controller/railtie.rb21
-rw-r--r--actionpack/lib/action_controller/record_identifier.rb20
-rw-r--r--actionpack/lib/action_controller/test_case.rb139
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner.rb5
33 files changed, 941 insertions, 594 deletions
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index 9b3bf99fc3..3b0d094f4f 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -1,8 +1,23 @@
require "action_controller/log_subscriber"
+require "action_controller/metal/params_wrapper"
module ActionController
+ # The <tt>metal</tt> anonymous class was introduced to solve issue with including modules in <tt>ActionController::Base</tt>.
+ # Modules needs to be included in particluar order. First we need to have <tt>AbstractController::Rendering</tt> included,
+ # next we should include actuall implementation which would be for example <tt>ActionView::Rendering</tt> and after that
+ # <tt>ActionController::Rendering</tt>. This order must be preserved and as we want to have middle module included dynamicaly
+ # <tt>metal</tt> class was introduced. It has <tt>AbstractController::Rendering</tt> included and is parent class of
+ # <tt>ActionController::Base</tt> which includes <tt>ActionController::Rendering</tt>. If we include <tt>ActionView::Rendering</tt>
+ # beetween them to perserve the required order, we can simply do this by:
+ #
+ # ActionController::Base.superclass.send(:include, ActionView::Rendering)
+ #
+ metal = Class.new(Metal) do
+ include AbstractController::Rendering
+ end
+
# Action Controllers are the core of a web request in \Rails. They are made up of one or more actions that are executed
- # on request and then either render a template or redirect to another action. An action is defined as a public method
+ # on request and then either it renders a template or redirects to another action. An action is defined as a public method
# on the controller, which will automatically be made accessible to the web-server through \Rails Routes.
#
# By default, only the ApplicationController in a \Rails application inherits from <tt>ActionController::Base</tt>. All other
@@ -58,7 +73,7 @@ module ActionController
# <input type="text" name="post[address]" value="hyacintvej">
#
# A request stemming from a form holding these inputs will include <tt>{ "post" => { "name" => "david", "address" => "hyacintvej" } }</tt>.
- # If the address input had been named "post[address][street]", the params would have included
+ # If the address input had been named <tt>post[address][street]</tt>, the params would have included
# <tt>{ "post" => { "address" => { "street" => "hyacintvej" } } }</tt>. There's no limit to the depth of the nesting.
#
# == Sessions
@@ -159,7 +174,7 @@ module ActionController
# render action: "overthere" # won't be called if monkeys is nil
# end
#
- class Base < Metal
+ class Base < metal
abstract!
# We document the request and response methods here because albeit they are
@@ -199,7 +214,6 @@ module ActionController
end
MODULES = [
- AbstractController::Layouts,
AbstractController::Translation,
AbstractController::AssetPaths,
@@ -222,7 +236,6 @@ module ActionController
ForceSSL,
Streaming,
DataStreaming,
- RecordIdentifier,
HttpAuthentication::Basic::ControllerMethods,
HttpAuthentication::Digest::ControllerMethods,
HttpAuthentication::Token::ControllerMethods,
diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb
index 462f147371..12d798d0c1 100644
--- a/actionpack/lib/action_controller/caching.rb
+++ b/actionpack/lib/action_controller/caching.rb
@@ -6,11 +6,10 @@ module ActionController
# \Caching is a cheap way of speeding up slow applications by keeping the result of
# calculations, renderings, and database calls around for subsequent requests.
#
- # You can read more about each approach and the sweeping assistance by clicking the
- # modules below.
+ # You can read more about each approach by clicking the modules below.
#
- # Note: To turn off all caching and sweeping, set
- # config.action_controller.perform_caching = false.
+ # Note: To turn off all caching, set
+ # config.action_controller.perform_caching = false
#
# == \Caching stores
#
@@ -30,8 +29,6 @@ module ActionController
eager_autoload do
autoload :Fragments
- autoload :Sweeper, 'action_controller/caching/sweeping'
- autoload :Sweeping, 'action_controller/caching/sweeping'
end
module ConfigMethods
@@ -54,7 +51,6 @@ module ActionController
include ConfigMethods
include Fragments
- include Sweeping if defined?(ActiveRecord)
included do
extend ConfigMethods
@@ -62,22 +58,22 @@ module ActionController
config_accessor :default_static_extension
self.default_static_extension ||= '.html'
- def self.page_cache_extension=(extension)
- ActiveSupport::Deprecation.deprecation_warning(:page_cache_extension, :default_static_extension)
- self.default_static_extension = extension
- end
-
- def self.page_cache_extension
- ActiveSupport::Deprecation.deprecation_warning(:page_cache_extension, :default_static_extension)
- default_static_extension
- end
-
config_accessor :perform_caching
self.perform_caching = true if perform_caching.nil?
+
+ class_attribute :_view_cache_dependencies
+ self._view_cache_dependencies = []
+ helper_method :view_cache_dependencies if respond_to?(:helper_method)
+ end
+
+ module ClassMethods
+ def view_cache_dependency(&dependency)
+ self._view_cache_dependencies += [dependency]
+ end
end
- def caching_allowed?
- request.get? && response.status == 200
+ def view_cache_dependencies
+ self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact
end
protected
diff --git a/actionpack/lib/action_controller/caching/sweeping.rb b/actionpack/lib/action_controller/caching/sweeping.rb
deleted file mode 100644
index 317ac74b40..0000000000
--- a/actionpack/lib/action_controller/caching/sweeping.rb
+++ /dev/null
@@ -1,116 +0,0 @@
-module ActionController
- module Caching
- # Sweepers are the terminators of the caching world and responsible for expiring
- # caches when Active Record objects change. They do this by being half-observers,
- # half-filters and implementing callbacks for both roles.
- #
- # class ListSweeper < ActionController::Caching::Sweeper
- # observe List, Item
- #
- # def after_save(record)
- # list = record.is_a?(List) ? record : record.list
- # expire_page(controller: 'lists', action: %w( show public feed ), id: list.id)
- # expire_action(controller: 'lists', action: 'all')
- # list.shares.each { |share| expire_page(controller: 'lists', action: 'show', id: share.url_key) }
- # end
- # end
- #
- # The sweeper is assigned in the controllers that wish to have its job performed using
- # the +cache_sweeper+ class method:
- #
- # class ListsController < ApplicationController
- # caches_action :index, :show, :public, :feed
- # cache_sweeper :list_sweeper, only: [ :edit, :destroy, :share ]
- # end
- #
- # In the example above, four actions are cached and three actions are responsible for expiring those caches.
- #
- # You can also name an explicit class in the declaration of a sweeper, which is needed
- # if the sweeper is in a module:
- #
- # class ListsController < ApplicationController
- # caches_action :index, :show, :public, :feed
- # cache_sweeper OpenBar::Sweeper, only: [ :edit, :destroy, :share ]
- # end
- module Sweeping
- extend ActiveSupport::Concern
-
- module ClassMethods # :nodoc:
- def cache_sweeper(*sweepers)
- configuration = sweepers.extract_options!
-
- sweepers.each do |sweeper|
- ActiveRecord::Base.observers << sweeper if defined?(ActiveRecord) and defined?(ActiveRecord::Base)
- sweeper_instance = (sweeper.is_a?(Symbol) ? Object.const_get(sweeper.to_s.classify) : sweeper).instance
-
- if sweeper_instance.is_a?(Sweeper)
- around_filter(sweeper_instance, :only => configuration[:only])
- else
- after_filter(sweeper_instance, :only => configuration[:only])
- end
- end
- end
- end
- end
-
- if defined?(ActiveRecord) and defined?(ActiveRecord::Observer)
- class Sweeper < ActiveRecord::Observer # :nodoc:
- attr_accessor :controller
-
- def initialize(*args)
- super
- @controller = nil
- end
-
- def before(controller)
- self.controller = controller
- callback(:before) if controller.perform_caching
- true # before method from sweeper should always return true
- end
-
- def after(controller)
- self.controller = controller
- callback(:after) if controller.perform_caching
- end
-
- def around(controller)
- before(controller)
- yield
- after(controller)
- ensure
- clean_up
- end
-
- protected
- # gets the action cache path for the given options.
- def action_path_for(options)
- Actions::ActionCachePath.new(controller, options).path
- end
-
- # Retrieve instance variables set in the controller.
- def assigns(key)
- controller.instance_variable_get("@#{key}")
- end
-
- private
- def clean_up
- # Clean up, so that the controller can be collected after this request
- self.controller = nil
- end
-
- def callback(timing)
- controller_callback_method_name = "#{timing}_#{controller.controller_name.underscore}"
- action_callback_method_name = "#{controller_callback_method_name}_#{controller.action_name}"
-
- __send__(controller_callback_method_name) if respond_to?(controller_callback_method_name, true)
- __send__(action_callback_method_name) if respond_to?(action_callback_method_name, true)
- end
-
- def method_missing(method, *arguments, &block)
- return super unless @controller
- @controller.__send__(method, *arguments, &block)
- end
- end
- end
- end
-end
diff --git a/actionpack/lib/action_controller/deprecated.rb b/actionpack/lib/action_controller/deprecated.rb
deleted file mode 100644
index 2405bebb97..0000000000
--- a/actionpack/lib/action_controller/deprecated.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-ActionController::AbstractRequest = ActionController::Request = ActionDispatch::Request
-ActionController::AbstractResponse = ActionController::Response = ActionDispatch::Response
-ActionController::Routing = ActionDispatch::Routing
-
-ActiveSupport::Deprecation.warn 'ActionController::AbstractRequest and ActionController::Request are deprecated and will be removed, use ActionDispatch::Request instead.'
-ActiveSupport::Deprecation.warn 'ActionController::AbstractResponse and ActionController::Response are deprecated and will be removed, use ActionDispatch::Response instead.'
-ActiveSupport::Deprecation.warn 'ActionController::Routing is deprecated and will be removed, use ActionDispatch::Routing instead.' \ No newline at end of file
diff --git a/actionpack/lib/action_controller/deprecated/integration_test.rb b/actionpack/lib/action_controller/deprecated/integration_test.rb
deleted file mode 100644
index 54eae48f47..0000000000
--- a/actionpack/lib/action_controller/deprecated/integration_test.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-ActionController::Integration = ActionDispatch::Integration
-ActionController::IntegrationTest = ActionDispatch::IntegrationTest
-
-ActiveSupport::Deprecation.warn 'ActionController::Integration is deprecated and will be removed, use ActionDispatch::Integration instead.'
-ActiveSupport::Deprecation.warn 'ActionController::IntegrationTest is deprecated and will be removed, use ActionDispatch::IntegrationTest instead.'
diff --git a/actionpack/lib/action_controller/deprecated/performance_test.rb b/actionpack/lib/action_controller/deprecated/performance_test.rb
deleted file mode 100644
index c7ba5a2fe7..0000000000
--- a/actionpack/lib/action_controller/deprecated/performance_test.rb
+++ /dev/null
@@ -1,3 +0,0 @@
-ActionController::PerformanceTest = ActionDispatch::PerformanceTest
-
-ActiveSupport::Deprecation.warn 'ActionController::PerformanceTest is deprecated and will be removed, use ActionDispatch::PerformanceTest instead.'
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index 3d274e7dd7..9279d8bcea 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -33,7 +33,7 @@ module ActionController
end
def halted_callback(event)
- info("Filter chain halted as #{event.payload[:filter]} rendered or redirected")
+ info("Filter chain halted as #{event.payload[:filter].inspect} rendered or redirected")
end
def send_file(event)
@@ -48,6 +48,11 @@ module ActionController
info("Sent data #{event.payload[:filename]} (#{event.duration.round(1)}ms)")
end
+ def unpermitted_parameters(event)
+ unpermitted_keys = event.payload[:keys]
+ debug("Unpermitted parameters: #{unpermitted_keys.join(", ")}")
+ end
+
%w(write_fragment read_fragment exist_fragment?
expire_fragment expire_page write_page).each do |method|
class_eval <<-METHOD, __FILE__, __LINE__ + 1
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index f5ab1e2350..b84c9e78c3 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/array/extract_options'
require 'action_dispatch/middleware/stack'
module ActionController
@@ -5,7 +6,7 @@ module ActionController
# allowing the following syntax in controllers:
#
# class PostsController < ApplicationController
- # use AuthenticationMiddleware, :except => [:index, :show]
+ # use AuthenticationMiddleware, except: [:index, :show]
# end
#
class MiddlewareStack < ActionDispatch::MiddlewareStack #:nodoc:
@@ -35,8 +36,7 @@ module ActionController
raise "MiddlewareStack#build requires an app" unless app
middlewares.reverse.inject(app) do |a, middleware|
- middleware.valid?(action) ?
- middleware.build(a) : a
+ middleware.valid?(action) ? middleware.build(a) : a
end
end
end
@@ -56,7 +56,7 @@ module ActionController
# And then to route requests to your metal controller, you would add
# something like this to <tt>config/routes.rb</tt>:
#
- # match 'hello', :to => HelloController.action(:index)
+ # get 'hello', to: HelloController.action(:index)
#
# The +action+ method returns a valid Rack application for the \Rails
# router to dispatch to.
diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb
index 3f37a6a618..6e0cd51d8b 100644
--- a/actionpack/lib/action_controller/metal/conditional_get.rb
+++ b/actionpack/lib/action_controller/metal/conditional_get.rb
@@ -1,4 +1,4 @@
-require 'active_support/core_ext/class/attribute'
+require 'active_support/core_ext/hash/keys'
module ActionController
module ConditionalGet
@@ -42,7 +42,7 @@ module ActionController
# * <tt>:public</tt> By default the Cache-Control header is private, set this to
# +true+ if you want your application to be cachable by other devices (proxy caches).
#
- # === Example:
+ # === Example:
#
# def show
# @article = Article.find(params[:id])
@@ -64,7 +64,7 @@ module ActionController
#
# def show
# @article = Article.find(params[:id])
- # fresh_when(@article, :public => true)
+ # fresh_when(@article, public: true)
# end
def fresh_when(record_or_options, additional_options = {})
if record_or_options.is_a? Hash
diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb
index 5422cb93c4..75c4d3ef99 100644
--- a/actionpack/lib/action_controller/metal/data_streaming.rb
+++ b/actionpack/lib/action_controller/metal/data_streaming.rb
@@ -47,11 +47,11 @@ module ActionController #:nodoc:
#
# Show a JPEG in the browser:
#
- # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
+ # send_file '/path/to.jpeg', type: 'image/jpeg', disposition: 'inline'
#
# Show a 404 page in the browser:
#
- # send_file '/path/to/404.html', :type => 'text/html; charset=utf-8', :status => 404
+ # send_file '/path/to/404.html', type: 'text/html; charset=utf-8', status: 404
#
# Read about the other Content-* HTTP headers if you'd like to
# provide the user with more information (such as Content-Description) in
@@ -96,7 +96,7 @@ module ActionController #:nodoc:
end
# Sends the given binary data to the browser. This method is similar to
- # <tt>render :text => data</tt>, but also allows you to specify whether
+ # <tt>render text: data</tt>, but also allows you to specify whether
# the browser should display the response as a file attachment (i.e. in a
# download dialog) or as inline data. You may also set the content type,
# the apparent file name, and other things.
@@ -117,11 +117,11 @@ module ActionController #:nodoc:
#
# Download a dynamically-generated tarball:
#
- # send_data generate_tgz('dir'), :filename => 'dir.tgz'
+ # send_data generate_tgz('dir'), filename: 'dir.tgz'
#
# Display an image Active Record in the browser:
#
- # send_data image.data, :type => image.content_type, :disposition => 'inline'
+ # send_data image.data, type: image.content_type, disposition: 'inline'
#
# See +send_file+ for more information on HTTP Content-* headers and caching.
def send_data(data, options = {}) #:doc:
@@ -150,6 +150,7 @@ module ActionController #:nodoc:
disposition = options.fetch(:disposition, DEFAULT_SEND_FILE_DISPOSITION)
unless disposition.nil?
+ disposition = disposition.to_s
disposition += %(; filename="#{options[:filename]}") if options[:filename]
headers['Content-Disposition'] = disposition
end
diff --git a/actionpack/lib/action_controller/metal/exceptions.rb b/actionpack/lib/action_controller/metal/exceptions.rb
index 3c9d0c86a7..3844dbf2a6 100644
--- a/actionpack/lib/action_controller/metal/exceptions.rb
+++ b/actionpack/lib/action_controller/metal/exceptions.rb
@@ -3,6 +3,15 @@ module ActionController
end
class BadRequest < ActionControllerError #:nodoc:
+ attr_reader :original_exception
+
+ def initialize(type = nil, e = nil)
+ return super() unless type && e
+
+ super("Invalid #{type} parameters: #{e.message}")
+ @original_exception = e
+ set_backtrace e.backtrace
+ end
end
class RenderError < ActionControllerError #:nodoc:
diff --git a/actionpack/lib/action_controller/metal/flash.rb b/actionpack/lib/action_controller/metal/flash.rb
index b078beb675..65351284b9 100644
--- a/actionpack/lib/action_controller/metal/flash.rb
+++ b/actionpack/lib/action_controller/metal/flash.rb
@@ -11,6 +11,23 @@ module ActionController #:nodoc:
end
module ClassMethods
+ # Creates new flash types. You can pass as many types as you want to create
+ # flash types other than the default <tt>alert</tt> and <tt>notice</tt> in
+ # your controllers and views. For instance:
+ #
+ # # in application_controller.rb
+ # class ApplicationController < ActionController::Base
+ # add_flash_types :warning
+ # end
+ #
+ # # in your controller
+ # redirect_to user_path(@user), warning: "Incomplete profile"
+ #
+ # # in your view
+ # <%= warning %>
+ #
+ # This method will automatically define a new method for each of the given
+ # names, and it will be available in your views.
def add_flash_types(*types)
types.each do |type|
next if _flash_types.include?(type)
@@ -20,7 +37,7 @@ module ActionController #:nodoc:
end
helper_method type
- _flash_types << type
+ self._flash_types += [type]
end
end
end
diff --git a/actionpack/lib/action_controller/metal/force_ssl.rb b/actionpack/lib/action_controller/metal/force_ssl.rb
index e905a3cf1d..a2cb6d1e66 100644
--- a/actionpack/lib/action_controller/metal/force_ssl.rb
+++ b/actionpack/lib/action_controller/metal/force_ssl.rb
@@ -1,3 +1,6 @@
+require 'active_support/core_ext/hash/except'
+require 'active_support/core_ext/hash/slice'
+
module ActionController
# This module provides a method which will redirect browser to use HTTPS
# protocol. This will ensure that user's sensitive information will be
@@ -14,6 +17,10 @@ module ActionController
extend ActiveSupport::Concern
include AbstractController::Callbacks
+ ACTION_OPTIONS = [:only, :except, :if, :unless]
+ URL_OPTIONS = [:protocol, :host, :domain, :subdomain, :port, :path]
+ REDIRECT_OPTIONS = [:status, :flash, :alert, :notice]
+
module ClassMethods
# Force the request to this particular controller or specified actions to be
# under HTTPS protocol.
@@ -22,25 +29,41 @@ module ActionController
# an +:if+ or +:unless+ condition.
#
# class AccountsController < ApplicationController
- # force_ssl :if => :ssl_configured?
+ # force_ssl if: :ssl_configured?
#
# def ssl_configured?
# !Rails.env.development?
# end
# end
#
- # ==== Options
- # * <tt>host</tt> - Redirect to a different host name
- # * <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.
+ # ==== URL Options
+ # You can pass any of the following options to affect the redirect url
+ # * <tt>host</tt> - Redirect to a different host name
+ # * <tt>subdomain</tt> - Redirect to a different subdomain
+ # * <tt>domain</tt> - Redirect to a different domain
+ # * <tt>port</tt> - Redirect to a non-standard port
+ # * <tt>path</tt> - Redirect to a different path
+ #
+ # ==== Redirect Options
+ # You can pass any of the following options to affect the redirect status and response
+ # * <tt>status</tt> - Redirect with a custom status (default is 301 Moved Permanently)
+ # * <tt>flash</tt> - Set a flash message when redirecting
+ # * <tt>alert</tt> - Set an alert message when redirecting
+ # * <tt>notice</tt> - Set a notice message when redirecting
+ #
+ # ==== Action Options
+ # 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.
def force_ssl(options = {})
- host = options.delete(:host)
- before_filter(options) do
- force_ssl_redirect(host)
+ action_options = options.slice(*ACTION_OPTIONS)
+ redirect_options = options.except(*ACTION_OPTIONS)
+ before_action(action_options) do
+ force_ssl_redirect(redirect_options)
end
end
end
@@ -48,14 +71,26 @@ module ActionController
# Redirect the existing request to use the HTTPS protocol.
#
# ==== Parameters
- # * <tt>host</tt> - Redirect to a different host name
- def force_ssl_redirect(host = nil)
+ # * <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?
- redirect_options = {:protocol => 'https://', :status => :moved_permanently}
- redirect_options.merge!(:host => host) if host
- redirect_options.merge!(:params => request.query_parameters)
+ options = {
+ :protocol => 'https://',
+ :host => request.host,
+ :path => request.fullpath,
+ :status => :moved_permanently
+ }
+
+ if host_or_options.is_a?(Hash)
+ options.merge!(host_or_options)
+ elsif host_or_options
+ options.merge!(:host => host_or_options)
+ end
+
+ secure_url = ActionDispatch::Http::URL.url_for(options.slice(*URL_OPTIONS))
flash.keep if respond_to?(:flash)
- redirect_to redirect_options
+ redirect_to secure_url, options.slice(*REDIRECT_OPTIONS)
end
end
end
diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb
index 747e1273be..424473801d 100644
--- a/actionpack/lib/action_controller/metal/head.rb
+++ b/actionpack/lib/action_controller/metal/head.rb
@@ -1,15 +1,13 @@
module ActionController
module Head
- extend ActiveSupport::Concern
-
# Return a response that has no content (merely headers). The options
# argument is interpreted to be a hash of header names and values.
# This allows you to easily return a response that consists only of
# significant headers:
#
- # head :created, :location => person_path(@person)
+ # head :created, location: person_path(@person)
#
- # head :created, :location => @person
+ # head :created, location: @person
#
# It can also be used to return exceptional conditions:
#
@@ -31,6 +29,7 @@ module ActionController
if include_content?(self.status)
self.content_type = content_type || (Mime[formats.first] if formats)
+ self.response.charset = false if self.response
self.response_body = " "
else
headers.delete('Content-Type')
diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb
index d2cbbd3330..a9c3e438fb 100644
--- a/actionpack/lib/action_controller/metal/helpers.rb
+++ b/actionpack/lib/action_controller/metal/helpers.rb
@@ -1,4 +1,3 @@
-
module ActionController
# The \Rails framework provides a large number of helpers for working with assets, dates, forms,
# numbers and model objects, to name a few. These helpers are available to all templates
@@ -6,7 +5,7 @@ module ActionController
#
# In addition to using the standard template helpers provided, creating custom helpers to
# extract complicated logic or reusable functionality is strongly encouraged. By default, each controller
- # will include all helpers.
+ # will include all helpers. These helpers are only accessible on the controller through <tt>.helpers</tt>
#
# In previous versions of \Rails the controller will include a helper whose
# name matches that of the controller, e.g., <tt>MyController</tt> will automatically
@@ -74,7 +73,11 @@ module ActionController
# Provides a proxy to access helpers methods from outside the view.
def helpers
- @helper_proxy ||= ActionView::Base.new.extend(_helpers)
+ @helper_proxy ||= begin
+ proxy = ActionView::Base.new
+ proxy.config = config.inheritable_copy
+ proxy.extend(_helpers)
+ end
end
# Overwrite modules_for_helpers to accept :all as argument, which loads
@@ -91,11 +94,10 @@ module ActionController
end
def all_helpers_from_path(path)
- helpers = []
- Array(path).each do |_path|
- extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/
+ 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') }
- helpers += names.sort
+ names.sort!
end
helpers.uniq!
helpers
diff --git a/actionpack/lib/action_controller/metal/hide_actions.rb b/actionpack/lib/action_controller/metal/hide_actions.rb
index 420b22cf56..af36ffa240 100644
--- a/actionpack/lib/action_controller/metal/hide_actions.rb
+++ b/actionpack/lib/action_controller/metal/hide_actions.rb
@@ -26,20 +26,14 @@ module ActionController
self.hidden_actions = hidden_actions.dup.merge(args.map(&:to_s)).freeze
end
- def inherited(klass)
- klass.class_eval { @visible_actions = {} }
- super
- end
-
def visible_action?(action_name)
- return @visible_actions[action_name] if @visible_actions.key?(action_name)
- @visible_actions[action_name] = !hidden_actions.include?(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) })
+ @action_methods ||= Set.new(super.reject { |name| hidden_actions.include?(name) }).freeze
end
end
end
diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb
index 03b8d8db1a..158d552ec7 100644
--- a/actionpack/lib/action_controller/metal/http_authentication.rb
+++ b/actionpack/lib/action_controller/metal/http_authentication.rb
@@ -8,14 +8,14 @@ module ActionController
# === Simple \Basic example
#
# class PostsController < ApplicationController
- # http_basic_authenticate_with :name => "dhh", :password => "secret", :except => :index
+ # http_basic_authenticate_with name: "dhh", password: "secret", except: :index
#
# def index
- # render :text => "Everyone can see me!"
+ # render text: "Everyone can see me!"
# end
#
# def edit
- # render :text => "I'm only accessible if you know the password"
+ # render text: "I'm only accessible if you know the password"
# end
# end
#
@@ -25,11 +25,11 @@ module ActionController
# the regular HTML interface is protected by a session approach:
#
# class ApplicationController < ActionController::Base
- # before_filter :set_account, :authenticate
+ # before_action :set_account, :authenticate
#
# protected
# def set_account
- # @account = Account.find_by_url_name(request.subdomains.first)
+ # @account = Account.find_by(url_name: request.subdomains.first)
# end
#
# def authenticate
@@ -68,7 +68,7 @@ module ActionController
module ClassMethods
def http_basic_authenticate_with(options = {})
- before_filter(options.except(:name, :password, :realm)) do
+ before_action(options.except(:name, :password, :realm)) do
authenticate_or_request_with_http_basic(options[:realm] || "Application") do |name, password|
name == options[:name] && password == options[:password]
end
@@ -124,14 +124,14 @@ module ActionController
# USERS = {"dhh" => "secret", #plain text password
# "dap" => Digest::MD5.hexdigest(["dap",REALM,"secret"].join(":"))} #ha1 digest password
#
- # before_filter :authenticate, :except => [:index]
+ # before_action :authenticate, except: [:index]
#
# def index
- # render :text => "Everyone can see me!"
+ # render text: "Everyone can see me!"
# end
#
# def edit
- # render :text => "I'm only accessible if you know the password"
+ # render text: "I'm only accessible if you know the password"
# end
#
# private
@@ -228,7 +228,7 @@ module ActionController
end
def decode_credentials(header)
- HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/,'').split(',').map do |pair|
+ ActiveSupport::HashWithIndifferentAccess[header.to_s.gsub(/^Digest\s+/, '').split(',').map do |pair|
key, value = pair.split('=', 2)
[key.strip, value.to_s.gsub(/^"|"$/,'').delete('\'')]
end]
@@ -249,9 +249,9 @@ module ActionController
end
def secret_token(request)
- secret = request.env["action_dispatch.secret_token"]
- raise "You must set config.secret_token in your app's config" if secret.blank?
- secret
+ key_generator = request.env["action_dispatch.key_generator"]
+ http_auth_salt = request.env["action_dispatch.http_auth_salt"]
+ key_generator.generate_key(http_auth_salt)
end
# Uses an MD5 digest based on time to generate a value to be used only once.
@@ -299,6 +299,7 @@ module ActionController
# allow a user to use new nonce without prompting user again for their
# username and password.
def validate_nonce(secret_key, request, value, seconds_to_timeout=5*60)
+ return false if value.nil?
t = ::Base64.decode64(value).split(":").first.to_i
nonce(secret_key, t) == value && (t - Time.now.to_i).abs <= seconds_to_timeout
end
@@ -317,14 +318,14 @@ module ActionController
# class PostsController < ApplicationController
# TOKEN = "secret"
#
- # before_filter :authenticate, :except => [ :index ]
+ # before_action :authenticate, except: [ :index ]
#
# def index
- # render :text => "Everyone can see me!"
+ # render text: "Everyone can see me!"
# end
#
# def edit
- # render :text => "I'm only accessible if you know the password"
+ # render text: "I'm only accessible if you know the password"
# end
#
# private
@@ -340,11 +341,11 @@ module ActionController
# the regular HTML interface is protected by a session approach:
#
# class ApplicationController < ActionController::Base
- # before_filter :set_account, :authenticate
+ # before_action :set_account, :authenticate
#
# protected
# def set_account
- # @account = Account.find_by_url_name(request.subdomains.first)
+ # @account = Account.find_by(url_name: request.subdomains.first)
# end
#
# def authenticate
@@ -384,6 +385,8 @@ module ActionController
#
# RewriteRule ^(.*)$ dispatch.fcgi [E=X-HTTP_AUTHORIZATION:%{HTTP:Authorization},QSA,L]
module Token
+ TOKEN_REGEX = /^Token /
+ AUTHN_PAIR_DELIMITERS = /(?:,|;|\t+)/
extend self
module ControllerMethods
@@ -424,27 +427,41 @@ module ActionController
# Parses the token and options out of the token authorization header. If
# the header looks like this:
# Authorization: Token token="abc", nonce="def"
- # Then the returned token is "abc", and the options is {:nonce => "def"}
+ # Then the returned token is "abc", and the options is {nonce: "def"}
#
# request - ActionDispatch::Request instance with the current headers.
#
# Returns an Array of [String, Hash] if a token is present.
# Returns nil if no token is found.
def token_and_options(request)
- if request.authorization.to_s[/^Token (.*)/]
- values = Hash[$1.split(',').map do |value|
- value.strip! # remove any spaces between commas and values
- key, value = value.split(/\=\"?/) # split key=value pairs
- if value
- value.chomp!('"') # chomp trailing " in value
- value.gsub!(/\\\"/, '"') # unescape remaining quotes
- [key, value]
- end
- end.compact]
- [values.delete("token"), values.with_indifferent_access]
+ authorization_request = request.authorization.to_s
+ if authorization_request[TOKEN_REGEX]
+ params = token_params_from authorization_request
+ [params.shift.last, Hash[params].with_indifferent_access]
end
end
+ def token_params_from(auth)
+ rewrite_param_values params_array_from raw_params auth
+ end
+
+ # Takes raw_params and turns it into an array of parameters
+ def params_array_from(raw_params)
+ raw_params.map { |param| param.split %r/=(.+)?/ }
+ end
+
+ # This removes the `"` characters wrapping the value.
+ def rewrite_param_values(array_params)
+ array_params.each { |param| param.last.gsub! %r/^"|"$/, '' }
+ end
+
+ # This method takes an authorization body and splits up the key-value
+ # pairs by the standardized `:`, `;`, or `\t` delimiters defined in
+ # `AUTHN_PAIR_DELIMITERS`.
+ def raw_params(auth)
+ auth.sub(TOKEN_REGEX, '').split(/"\s*#{AUTHN_PAIR_DELIMITERS}\s*/)
+ end
+
# Encodes the given token and options into an Authorization header value.
#
# token - String token.
diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb
index ca4ae532ca..d3aa8f90c5 100644
--- a/actionpack/lib/action_controller/metal/instrumentation.rb
+++ b/actionpack/lib/action_controller/metal/instrumentation.rb
@@ -60,7 +60,7 @@ module ActionController
ActiveSupport::Notifications.instrument("redirect_to.action_controller") do |payload|
result = super
payload[:status] = response.status
- payload[:location] = response.location
+ payload[:location] = response.filtered_location
result
end
end
diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb
index 32e5afa335..0dd788645b 100644
--- a/actionpack/lib/action_controller/metal/live.rb
+++ b/actionpack/lib/action_controller/metal/live.rb
@@ -1,5 +1,6 @@
require 'action_dispatch/http/response'
require 'delegate'
+require 'active_support/json'
module ActionController
# Mix this module in to your controller, and all actions in that controller
@@ -14,6 +15,7 @@ module ActionController
# response.stream.write "hello world\n"
# sleep 1
# }
+ # ensure
# response.stream.close
# end
# end
@@ -31,8 +33,82 @@ module ActionController
# the main thread. Make sure your actions are thread safe, and this shouldn't
# be a problem (don't share state across threads, etc).
module Live
+ # This class provides the ability to write an SSE (Server Sent Event)
+ # to an IO stream. The class is initialized with a stream and can be used
+ # to either write a JSON string or an object which can be converted to JSON.
+ #
+ # Writing an object will convert it into standard SSE format with whatever
+ # options you have configured. You may choose to set the following options:
+ #
+ # 1) Event. If specified, an event with this name will be dispatched on
+ # the browser.
+ # 2) Retry. The reconnection time in milliseconds used when attempting
+ # to send the event.
+ # 3) Id. If the connection dies while sending an SSE to the browser, then
+ # the server will receive a +Last-Event-ID+ header with value equal to +id+.
+ #
+ # After setting an option in the constructor of the SSE object, all future
+ # SSEs sent accross the stream will use those options unless overridden.
+ #
+ # Example Usage:
+ #
+ # class MyController < ActionController::Base
+ # include ActionController::Live
+ #
+ # def index
+ # response.headers['Content-Type'] = 'text/event-stream'
+ # sse = SSE.new(response.stream, retry: 300, event: "event-name")
+ # sse.write({ name: 'John'})
+ # sse.write({ name: 'John'}, id: 10)
+ # sse.write({ name: 'John'}, id: 10, event: "other-event")
+ # sse.write({ name: 'John'}, id: 10, event: "other-event", retry: 500)
+ # ensure
+ # sse.close
+ # end
+ # end
+ #
+ # Note: SSEs are not currently supported by IE. However, they are supported
+ # by Chrome, Firefox, Opera, and Safari.
+ class SSE
+
+ WHITELISTED_OPTIONS = %w( retry event id )
+
+ def initialize(stream, options = {})
+ @stream = stream
+ @options = options
+ end
+
+ def close
+ @stream.close
+ end
+
+ def write(object, options = {})
+ case object
+ when String
+ perform_write(object, options)
+ else
+ perform_write(ActiveSupport::JSON.encode(object), options)
+ end
+ end
+
+ private
+
+ def perform_write(json, options)
+ current_options = @options.merge(options).stringify_keys
+
+ WHITELISTED_OPTIONS.each do |option_name|
+ if (option_value = current_options[option_name])
+ @stream.write "#{option_name}: #{option_value}\n"
+ end
+ end
+
+ @stream.write "data: #{json}\n\n"
+ end
+ end
+
class Buffer < ActionDispatch::Response::Buffer #:nodoc:
def initialize(response)
+ @error_callback = nil
super(response, SizedQueue.new(10))
end
@@ -55,6 +131,14 @@ module ActionController
super
@buf.push nil
end
+
+ def on_error(&block)
+ @error_callback = block
+ end
+
+ def call_on_error
+ @error_callback.call
+ end
end
class Response < ActionDispatch::Response #:nodoc: all
@@ -97,6 +181,10 @@ module ActionController
def merge_default_headers(original, default)
Header.new self, super
end
+
+ def handle_conditional_get!
+ super unless committed?
+ end
end
def process(name)
@@ -116,6 +204,16 @@ module ActionController
begin
super(name)
+ rescue => e
+ begin
+ @_response.stream.write(ActionView::Base.streaming_completion_on_exception) if request.format == :html
+ @_response.stream.call_on_error
+ rescue => exception
+ log_error(exception)
+ ensure
+ log_error(e)
+ @_response.stream.close
+ end
ensure
@_response.commit!
end
@@ -124,6 +222,16 @@ module ActionController
@_response.await_commit
end
+ def log_error(exception)
+ logger = ActionController::Base.logger
+ return unless logger
+
+ message = "\n#{exception.class} (#{exception.message}):\n"
+ message << exception.annoted_source_code.to_s if exception.respond_to?(:annoted_source_code)
+ message << " " << exception.backtrace.join("\n ")
+ logger.fatal("#{message}\n\n")
+ end
+
def response_body=(body)
super
response.stream.close if response
diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb
index 18ae2c3bfc..a072fce1a1 100644
--- a/actionpack/lib/action_controller/metal/mime_responds.rb
+++ b/actionpack/lib/action_controller/metal/mime_responds.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/array/extract_options'
require 'abstract_controller/collector'
module ActionController #:nodoc:
@@ -23,13 +24,13 @@ module ActionController #:nodoc:
# <tt>:except</tt> with an array of actions or a single action:
#
# respond_to :html
- # respond_to :xml, :json, :except => [ :edit ]
+ # respond_to :xml, :json, except: [ :edit ]
#
# This specifies that all actions respond to <tt>:html</tt>
# and all actions except <tt>:edit</tt> respond to <tt>:xml</tt> and
# <tt>:json</tt>.
#
- # respond_to :json, :only => :create
+ # respond_to :json, only: :create
#
# This specifies that the <tt>:create</tt> action and no other responds
# to <tt>:json</tt>.
@@ -70,7 +71,7 @@ module ActionController #:nodoc:
#
# respond_to do |format|
# format.html
- # format.xml { render :xml => @people }
+ # format.xml { render xml: @people }
# end
# end
#
@@ -82,7 +83,7 @@ module ActionController #:nodoc:
# (by name) if it does not already exist, without web-services, it might look like this:
#
# def create
- # @company = Company.find_or_create_by_name(params[:company][:name])
+ # @company = Company.find_or_create_by(name: params[:company][:name])
# @person = @company.people.create(params[:person])
#
# redirect_to(person_list_url)
@@ -92,13 +93,13 @@ module ActionController #:nodoc:
#
# def create
# company = params[:person].delete(:company)
- # @company = Company.find_or_create_by_name(company[:name])
+ # @company = Company.find_or_create_by(name: company[:name])
# @person = @company.people.create(params[:person])
#
# respond_to do |format|
# format.html { redirect_to(person_list_url) }
# format.js
- # format.xml { render :xml => @person.to_xml(:include => @company) }
+ # format.xml { render xml: @person.to_xml(include: @company) }
# end
# end
#
@@ -120,7 +121,7 @@ module ActionController #:nodoc:
# Note, however, the extra bit at the top of that action:
#
# company = params[:person].delete(:company)
- # @company = Company.find_or_create_by_name(company[:name])
+ # @company = Company.find_or_create_by(name: company[:name])
#
# This is because the incoming XML document (if a web-service request is in process) can only contain a
# single root-node. So, we have to rearrange things so that the request looks like this (url-encoded):
@@ -162,11 +163,11 @@ module ActionController #:nodoc:
#
# In the example above, if the format is xml, it will render:
#
- # render :xml => @people
+ # render xml: @people
#
# Or if the format is json:
#
- # render :json => @people
+ # render json: @people
#
# Since this is a common pattern, you can use the class method respond_to
# with the respond_with method to have the same results:
@@ -227,7 +228,7 @@ module ActionController #:nodoc:
# i.e. its +show+ action.
# 2. If there are validation errors, the response
# renders a default action, which is <tt>:new</tt> for a
- # +post+ request or <tt>:edit</tt> for +put+.
+ # +post+ request or <tt>:edit</tt> for +patch+ or +put+.
# Thus an example like this -
#
# respond_to :html, :xml
@@ -246,10 +247,10 @@ module ActionController #:nodoc:
# if @user.save
# flash[:notice] = 'User was successfully created.'
# format.html { redirect_to(@user) }
- # format.xml { render :xml => @user }
+ # format.xml { render xml: @user }
# else
- # format.html { render :action => "new" }
- # format.xml { render :xml => @user }
+ # format.html { render action: "new" }
+ # format.xml { render xml: @user }
# end
# end
# end
@@ -260,7 +261,7 @@ module ActionController #:nodoc:
# the resource passed to +respond_with+ responds to <code>to_<format></code>,
# the method attempts to render the resource in the requested format
# directly, e.g. for an xml request, the response is equivalent to calling
- # <code>render :xml => resource</code>.
+ # <code>render xml: resource</code>.
#
# === Nested resources
#
@@ -309,7 +310,7 @@ module ActionController #:nodoc:
# Also, a hash passed to +respond_with+ immediately after the specified
# resource(s) is interpreted as a set of options relevant to all
# formats. Any option accepted by +render+ can be used, e.g.
- # respond_with @people, :status => 200
+ # respond_with @people, status: 200
# However, note that these options are ignored after an unsuccessful attempt
# to save a resource, e.g. when automatically rendering <tt>:new</tt>
# after a post request.
@@ -320,11 +321,12 @@ module ActionController #:nodoc:
# 2. <tt>:action</tt> - overwrites the default render action used after an
# unsuccessful html +post+ request.
def respond_with(*resources, &block)
- raise "In order to use respond_with, first you need to declare the formats your " <<
+ raise "In order to use respond_with, first you need to declare the formats your " \
"controller responds to in the class level" if self.class.mimes_for_respond_to.empty?
if collector = retrieve_collector_from_mimes(&block)
options = resources.size == 1 ? {} : resources.extract_options!
+ options = options.clone
options[:default_response] = collector.response
(options.delete(:responder) || self.class.responder).call(self, resources, options)
end
@@ -363,9 +365,7 @@ module ActionController #:nodoc:
format = collector.negotiate_format(request)
if format
- self.content_type ||= format.to_s
- lookup_context.formats = [format.to_sym]
- lookup_context.rendered_format = lookup_context.formats.first
+ _process_format(format)
collector
else
raise ActionController::UnknownFormat
@@ -381,7 +381,7 @@ module ActionController #:nodoc:
#
# respond_to do |format|
# format.html
- # format.xml { render :xml => @people }
+ # format.xml { render xml: @people }
# end
#
# In this usage, the argument passed to the block (+format+ above) is an
@@ -419,7 +419,7 @@ module ActionController #:nodoc:
end
def response
- @responses[format] || @responses[Mime::ALL]
+ @responses.fetch(format, @responses[Mime::ALL])
end
def negotiate_format(request)
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index 88b9e78da7..c9f1d8dcb4 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -1,7 +1,8 @@
require 'active_support/core_ext/hash/slice'
require 'active_support/core_ext/hash/except'
require 'active_support/core_ext/module/anonymous'
-require 'action_dispatch/http/mime_types'
+require 'active_support/core_ext/struct'
+require 'action_dispatch/http/mime_type'
module ActionController
# Wraps the parameters hash into a nested hash. This will allow clients to submit
@@ -72,17 +73,104 @@ module ActionController
EXCLUDE_PARAMETERS = %w(authenticity_token _method utf8)
+ require 'mutex_m'
+
+ class Options < Struct.new(:name, :format, :include, :exclude, :klass, :model) # :nodoc:
+ include Mutex_m
+
+ def self.from_hash(hash)
+ name = hash[:name]
+ format = Array(hash[:format])
+ include = hash[:include] && Array(hash[:include]).collect(&:to_s)
+ exclude = hash[:exclude] && Array(hash[:exclude]).collect(&:to_s)
+ new name, format, include, exclude, nil, nil
+ end
+
+ def initialize(name, format, include, exclude, klass, model) # nodoc
+ super
+ @include_set = include
+ @name_set = name
+ end
+
+ def model
+ super || synchronize { super || self.model = _default_wrap_model }
+ end
+
+ def include
+ return super if @include_set
+
+ m = model
+ synchronize do
+ return super if @include_set
+
+ @include_set = true
+
+ unless super || exclude
+ if m.respond_to?(:attribute_names) && m.attribute_names.any?
+ self.include = m.attribute_names
+ end
+ end
+ end
+ end
+
+ def name
+ return super if @name_set
+
+ m = model
+ synchronize do
+ return super if @name_set
+
+ @name_set = true
+
+ unless super || klass.anonymous?
+ self.name = m ? m.to_s.demodulize.underscore :
+ klass.controller_name.singularize
+ end
+ end
+ end
+
+ private
+ # Determine the wrapper model from the controller's name. By convention,
+ # this could be done by trying to find the defined model that has the
+ # same singularize name as the controller. For example, +UsersController+
+ # will try to find if the +User+ model exists.
+ #
+ # This method also does namespace lookup. Foo::Bar::UsersController will
+ # try to find Foo::Bar::User, Foo::User and finally User.
+ def _default_wrap_model #:nodoc:
+ return nil if klass.anonymous?
+ model_name = klass.name.sub(/Controller$/, '').classify
+
+ begin
+ if model_klass = model_name.safe_constantize
+ model_klass
+ else
+ namespaces = model_name.split("::")
+ namespaces.delete_at(-2)
+ break if namespaces.last == model_name
+ model_name = namespaces.join("::")
+ end
+ end until model_klass
+
+ model_klass
+ end
+ end
+
included do
class_attribute :_wrapper_options
- self._wrapper_options = { :format => [] }
+ self._wrapper_options = Options.from_hash(format: [])
end
module ClassMethods
+ def _set_wrapper_options(options)
+ self._wrapper_options = Options.from_hash(options)
+ end
+
# Sets the name of the wrapper key, or the model which +ParamsWrapper+
# would use to determine the attribute names from.
#
# ==== Examples
- # wrap_parameters :format => :xml
+ # wrap_parameters format: :xml
# # enables the parameter wrapper for XML format
#
# wrap_parameters :person
@@ -92,7 +180,7 @@ module ActionController
# # wraps parameters by determining the wrapper key from Person class
# (+person+, in this case) and the list of attribute names
#
- # wrap_parameters :include => [:username, :title]
+ # wrap_parameters include: [:username, :title]
# # wraps only +:username+ and +:title+ attributes from parameters.
#
# wrap_parameters false
@@ -119,68 +207,24 @@ module ActionController
model = name_or_model_or_options
end
- _set_wrapper_defaults(_wrapper_options.slice(:format).merge(options), model)
+ opts = Options.from_hash _wrapper_options.to_h.slice(:format).merge(options)
+ opts.model = model
+ opts.klass = self
+
+ self._wrapper_options = opts
end
# Sets the default wrapper key or model which will be used to determine
# wrapper key and attribute names. Will be called automatically when the
# module is inherited.
def inherited(klass)
- if klass._wrapper_options[:format].present?
- klass._set_wrapper_defaults(klass._wrapper_options.slice(:format))
+ if klass._wrapper_options.format.any?
+ params = klass._wrapper_options.dup
+ params.klass = klass
+ klass._wrapper_options = params
end
super
end
-
- protected
-
- # Determine the wrapper model from the controller's name. By convention,
- # this could be done by trying to find the defined model that has the
- # same singularize name as the controller. For example, +UsersController+
- # will try to find if the +User+ model exists.
- #
- # This method also does namespace lookup. Foo::Bar::UsersController will
- # try to find Foo::Bar::User, Foo::User and finally User.
- def _default_wrap_model #:nodoc:
- return nil if self.anonymous?
- model_name = self.name.sub(/Controller$/, '').classify
-
- begin
- if model_klass = model_name.safe_constantize
- model_klass
- else
- namespaces = model_name.split("::")
- namespaces.delete_at(-2)
- break if namespaces.last == model_name
- model_name = namespaces.join("::")
- end
- end until model_klass
-
- model_klass
- end
-
- def _set_wrapper_defaults(options, model=nil)
- options = options.dup
-
- unless options[:include] || options[:exclude]
- model ||= _default_wrap_model
- if model.respond_to?(:attribute_names) && model.attribute_names.present?
- options[:include] = model.attribute_names
- end
- end
-
- unless options[:name] || self.anonymous?
- model ||= _default_wrap_model
- options[:name] = model ? model.to_s.demodulize.underscore :
- controller_name.singularize
- end
-
- options[:include] = Array(options[:include]).collect(&:to_s) if options[:include]
- options[:exclude] = Array(options[:exclude]).collect(&:to_s) if options[:exclude]
- options[:format] = Array(options[:format])
-
- self._wrapper_options = options
- end
end
# Performs parameters wrapping upon the request. Will be called automatically
@@ -205,20 +249,20 @@ module ActionController
# Returns the wrapper key which will use to stored wrapped parameters.
def _wrapper_key
- _wrapper_options[:name]
+ _wrapper_options.name
end
# Returns the list of enabled formats.
def _wrapper_formats
- _wrapper_options[:format]
+ _wrapper_options.format
end
# Returns the list of parameters which will be selected for wrapped.
def _wrap_parameters(parameters)
- value = if include_only = _wrapper_options[:include]
+ value = if include_only = _wrapper_options.include
parameters.slice(*include_only)
else
- exclude = _wrapper_options[:exclude] || []
+ exclude = _wrapper_options.exclude || []
parameters.except(*(exclude + EXCLUDE_PARAMETERS))
end
diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb
index ee0e69d87c..ab14a61b97 100644
--- a/actionpack/lib/action_controller/metal/redirecting.rb
+++ b/actionpack/lib/action_controller/metal/redirecting.rb
@@ -24,7 +24,7 @@ module ActionController
# * <tt>:back</tt> - Back to the page that issued the request. Useful for forms that are triggered from multiple places.
# Short-hand for <tt>redirect_to(request.env["HTTP_REFERER"])</tt>
#
- # redirect_to :action => "show", :id => 5
+ # redirect_to action: "show", id: 5
# redirect_to post
# redirect_to "http://www.rubyonrails.org"
# redirect_to "/images/screenshot.jpg"
@@ -32,12 +32,12 @@ module ActionController
# redirect_to :back
# redirect_to proc { edit_post_url(@post) }
#
- # The redirection happens as a "302 Moved" header unless otherwise specified.
+ # The redirection happens as a "302 Found" header unless otherwise specified.
#
- # redirect_to post_url(@post), :status => :found
- # redirect_to :action=>'atom', :status => :moved_permanently
- # redirect_to post_url(@post), :status => 301
- # redirect_to :action=>'atom', :status => 302
+ # redirect_to post_url(@post), status: :found
+ # redirect_to action: 'atom', status: :moved_permanently
+ # redirect_to post_url(@post), status: 301
+ # redirect_to action: 'atom', status: 302
#
# The status code can either be a standard {HTTP Status code}[http://www.iana.org/assignments/http-status-codes] as an
# integer, or a symbol representing the downcased, underscored and symbolized description.
@@ -49,32 +49,51 @@ module ActionController
# around this you can return a <tt>303 See Other</tt> status code which will be
# followed using a GET request.
#
- # redirect_to posts_url, :status => :see_other
- # redirect_to :action => 'index', :status => 303
+ # redirect_to posts_url, status: :see_other
+ # redirect_to action: 'index', status: 303
#
# It is also possible to assign a flash message as part of the redirection. There are two special accessors for the commonly used flash names
# +alert+ and +notice+ as well as a general purpose +flash+ bucket.
#
- # redirect_to post_url(@post), :alert => "Watch it, mister!"
- # redirect_to post_url(@post), :status=> :found, :notice => "Pay attention to the road"
- # redirect_to post_url(@post), :status => 301, :flash => { :updated_post_id => @post.id }
- # redirect_to { :action=>'atom' }, :alert => "Something serious happened"
+ # redirect_to post_url(@post), alert: "Watch it, mister!"
+ # redirect_to post_url(@post), status: :found, notice: "Pay attention to the road"
+ # redirect_to post_url(@post), status: 301, flash: { updated_post_id: @post.id }
+ # redirect_to { action: 'atom' }, alert: "Something serious happened"
#
# When using <tt>redirect_to :back</tt>, if there is no referrer, ActionController::RedirectBackError will be raised. You may specify some fallback
# behavior for this case by rescuing ActionController::RedirectBackError.
def redirect_to(options = {}, response_status = {}) #:doc:
raise ActionControllerError.new("Cannot redirect to nil!") unless options
raise AbstractController::DoubleRenderError if response_body
- logger.debug { "Redirected by #{caller(1).first rescue "unknown"}" } if logger
self.status = _extract_redirect_to_status(options, response_status)
self.location = _compute_redirect_to_location(options)
self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(location)}\">redirected</a>.</body></html>"
end
+ def _compute_redirect_to_location(options) #:nodoc:
+ case options
+ # The scheme name consist of a letter followed by any combination of
+ # letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
+ # characters; and is terminated by a colon (":").
+ # See http://tools.ietf.org/html/rfc3986#section-3.1
+ # The protocol relative scheme starts with a double slash "//".
+ when /\A([a-z][a-z\d\-+\.]*:|\/\/).*/i
+ options
+ when String
+ request.protocol + request.host_with_port + options
+ when :back
+ request.headers["Referer"] or raise RedirectBackError
+ when Proc
+ _compute_redirect_to_location options.call
+ else
+ url_for(options)
+ end.delete("\0\r\n")
+ end
+
private
def _extract_redirect_to_status(options, response_status)
- status = if options.is_a?(Hash) && options.key?(:status)
+ if options.is_a?(Hash) && options.key?(:status)
Rack::Utils.status_code(options.delete(:status))
elsif response_status.key?(:status)
Rack::Utils.status_code(response_status[:status])
@@ -82,25 +101,5 @@ module ActionController
302
end
end
-
- def _compute_redirect_to_location(options)
- case options
- # The scheme name consist of a letter followed by any combination of
- # letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
- # characters; and is terminated by a colon (":").
- # The protocol relative scheme starts with a double slash "//"
- when %r{^(\w[\w+.-]*:|//).*}
- options
- when String
- request.protocol + request.host_with_port + options
- when :back
- raise RedirectBackError unless refer = request.headers["Referer"]
- refer
- when Proc
- _compute_redirect_to_location options.call
- else
- url_for(options)
- end.delete("\0\r\n")
- end
end
end
diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb
index 78aeeef2bf..62a3844b04 100644
--- a/actionpack/lib/action_controller/metal/renderers.rb
+++ b/actionpack/lib/action_controller/metal/renderers.rb
@@ -6,6 +6,12 @@ module ActionController
Renderers.add(key, &block)
end
+ class MissingRenderer < LoadError
+ def initialize(format)
+ super "No renderer defined for format: #{format}"
+ end
+ end
+
module Renderers
extend ActiveSupport::Concern
@@ -52,8 +58,8 @@ module ActionController
# ActionController::Renderers.add :csv do |obj, options|
# filename = options[:filename] || 'data'
# str = obj.respond_to?(:to_csv) ? obj.to_csv : obj.to_s
- # send_data str, :type => Mime::CSV,
- # :disposition => "attachment; filename=#{filename}.csv"
+ # send_data str, type: Mime::CSV,
+ # disposition: "attachment; filename=#{filename}.csv"
# end
#
# Note that we used Mime::CSV for the csv mime type as it comes with Rails.
@@ -66,7 +72,7 @@ module ActionController
# @csvable = Csvable.find(params[:id])
# respond_to do |format|
# format.html
- # format.csv { render :csv => @csvable, :filename => @csvable.name }
+ # format.csv { render csv: @csvable, filename: @csvable.name }
# }
# end
# To use renderers and their mime types in more concise ways, see
diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb
index c5e7d4e357..90f0ef0b1c 100644
--- a/actionpack/lib/action_controller/metal/rendering.rb
+++ b/actionpack/lib/action_controller/metal/rendering.rb
@@ -2,39 +2,45 @@ module ActionController
module Rendering
extend ActiveSupport::Concern
- include AbstractController::Rendering
-
# Before processing, set the request formats in current controller formats.
def process_action(*) #:nodoc:
- self.formats = request.formats.map { |x| x.ref }
+ self.formats = request.formats.map(&:ref).compact
super
end
# Check for double render errors and set the content_type after rendering.
def render(*args) #:nodoc:
- raise ::AbstractController::DoubleRenderError if response_body
+ raise ::AbstractController::DoubleRenderError if self.response_body
super
- self.content_type ||= Mime[lookup_context.rendered_format].to_s
- response_body
end
# Overwrite render_to_string because body can now be set to a rack body.
def render_to_string(*)
- if self.response_body = super
+ result = super
+ if result.respond_to?(:each)
string = ""
- response_body.each { |r| string << r }
+ result.each { |r| string << r }
string
+ else
+ result
end
- ensure
- self.response_body = nil
end
- def render_to_body(*)
- super || " "
+ def render_to_body(options = {})
+ super || if options[:text].present?
+ options[:text]
+ else
+ " "
+ end
end
private
+ def _process_format(format)
+ super
+ self.content_type ||= format.to_s
+ end
+
# Normalize arguments by catching blocks and setting them on :update.
def _normalize_args(action=nil, options={}, &blk) #:nodoc:
options = super
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index 17d4a793ac..bd64b1f812 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -19,7 +19,7 @@ module ActionController #:nodoc:
#
# class ApplicationController < ActionController::Base
# protect_from_forgery
- # skip_before_filter :verify_authenticity_token, :if => :json_request?
+ # skip_before_action :verify_authenticity_token, if: :json_request?
#
# protected
#
@@ -50,6 +50,10 @@ module ActionController #:nodoc:
config_accessor :request_forgery_protection_token
self.request_forgery_protection_token ||= :authenticity_token
+ # Holds the class which implements the request forgery protection.
+ config_accessor :forgery_protection_strategy
+ self.forgery_protection_strategy = nil
+
# Controls whether request forgery protection is turned on or not. Turned off by default only in test mode.
config_accessor :allow_forgery_protection
self.allow_forgery_protection = true if allow_forgery_protection.nil?
@@ -62,19 +66,19 @@ module ActionController #:nodoc:
# Turn on request forgery protection. Bear in mind that only non-GET, HTML/JavaScript requests are checked.
#
# class FooController < ApplicationController
- # protect_from_forgery :except => :index
+ # protect_from_forgery except: :index
#
# You can disable csrf protection on controller-by-controller basis:
#
- # skip_before_filter :verify_authenticity_token
+ # skip_before_action :verify_authenticity_token
#
# It can also be disabled for specific controller actions:
#
- # skip_before_filter :verify_authenticity_token, :except => [:create]
+ # skip_before_action :verify_authenticity_token, except: [:create]
#
# Valid Options:
#
- # * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified.
+ # * <tt>:only/:except</tt> - Passed to the <tt>before_action</tt> call. Set which actions are verified.
# * <tt>:with</tt> - Set the method to handle unverified request.
#
# Valid unverified request handling methods are:
@@ -82,14 +86,14 @@ 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 = {})
- include protection_method_module(options[:with] || :null_session)
+ self.forgery_protection_strategy = protection_method_class(options[:with] || :null_session)
self.request_forgery_protection_token ||= :authenticity_token
- prepend_before_filter :verify_authenticity_token, options
+ prepend_before_action :verify_authenticity_token, options
end
private
- def protection_method_module(name)
+ def protection_method_class(name)
ActionController::RequestForgeryProtection::ProtectionMethods.const_get(name.to_s.classify)
rescue NameError
raise ArgumentError, 'Invalid request forgery protection method, use :null_session, :exception, or :reset_session'
@@ -97,23 +101,32 @@ module ActionController #:nodoc:
end
module ProtectionMethods
- module NullSession
- protected
+ class NullSession
+ def initialize(controller)
+ @controller = controller
+ end
# This is the method that defines the application behavior when a request is found to be unverified.
def handle_unverified_request
- request.session = NullSessionHash.new
+ request = @controller.request
+ 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)
end
+ protected
+
class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
- def initialize
- super(nil, nil)
+ def initialize(env)
+ super(nil, env)
+ @data = {}
@loaded = true
end
+ # no-op
+ def destroy; end
+
def exists?
true
end
@@ -121,11 +134,11 @@ module ActionController #:nodoc:
class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
def self.build(request)
- secret = request.env[ActionDispatch::Cookies::TOKEN_KEY]
- host = request.host
- secure = request.ssl?
+ key_generator = request.env[ActionDispatch::Cookies::GENERATOR_KEY]
+ host = request.host
+ secure = request.ssl?
- new(secret, host, secure)
+ new(key_generator, host, secure, options_for_env({}))
end
def write(*)
@@ -134,16 +147,20 @@ module ActionController #:nodoc:
end
end
- module ResetSession
- protected
+ class ResetSession
+ def initialize(controller)
+ @controller = controller
+ end
def handle_unverified_request
- reset_session
+ @controller.reset_session
end
end
- module Exception
- protected
+ class Exception
+ def initialize(controller)
+ @controller = controller
+ end
def handle_unverified_request
raise ActionController::InvalidAuthenticityToken
@@ -152,7 +169,11 @@ module ActionController #:nodoc:
end
protected
- # The actual before_filter that is used. Modify this to change how you handle unverified requests.
+ def handle_unverified_request
+ forgery_protection_strategy.new(self).handle_unverified_request
+ end
+
+ # The actual before_action that is used. Modify this to change how you handle unverified requests.
def verify_authenticity_token
unless verified_request?
logger.warn "Can't verify CSRF token authenticity" if logger
@@ -162,11 +183,11 @@ module ActionController #:nodoc:
# Returns true or false if a request is verified. Checks:
#
- # * is it a GET 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?
- !protect_against_forgery? || request.get? ||
+ !protect_against_forgery? || request.get? || request.head? ||
form_authenticity_token == params[request_forgery_protection_token] ||
form_authenticity_token == request.headers['X-CSRF-Token']
end
@@ -181,6 +202,7 @@ module ActionController #:nodoc:
params[request_forgery_protection_token]
end
+ # Checks if the controller allows forgery protection.
def protect_against_forgery?
allow_forgery_protection
end
diff --git a/actionpack/lib/action_controller/metal/responder.rb b/actionpack/lib/action_controller/metal/responder.rb
index 42a0959a58..b4ba169e8f 100644
--- a/actionpack/lib/action_controller/metal/responder.rb
+++ b/actionpack/lib/action_controller/metal/responder.rb
@@ -45,10 +45,10 @@ module ActionController #:nodoc:
# if @user.save
# flash[:notice] = 'User was successfully created.'
# format.html { redirect_to(@user) }
- # format.xml { render :xml => @user, :status => :created, :location => @user }
+ # format.xml { render xml: @user, status: :created, location: @user }
# else
- # format.html { render :action => "new" }
- # format.xml { render :xml => @user.errors, :status => :unprocessable_entity }
+ # format.html { render action: "new" }
+ # format.xml { render xml: @user.errors, status: :unprocessable_entity }
# end
# end
# end
@@ -92,18 +92,22 @@ module ActionController #:nodoc:
# @project = Project.find(params[:project_id])
# @task = @project.tasks.build(params[:task])
# flash[:notice] = 'Task was successfully created.' if @task.save
- # respond_with(@project, @task, :status => 201)
+ # respond_with(@project, @task, status: 201)
# end
#
# This will return status 201 if the task was saved successfully. If not,
# it will simply ignore the given options and return status 422 and the
- # resource errors. To customize the failure scenario, you can pass a
- # a block to <code>respond_with</code>:
+ # resource errors. You can also override the location to redirect to:
+ #
+ # respond_with(@project, location: root_path)
+ #
+ # To customize the failure scenario, you can pass a block to
+ # <code>respond_with</code>:
#
# def create
# @project = Project.find(params[:project_id])
# @task = @project.tasks.build(params[:task])
- # respond_with(@project, @task, :status => 201) do |format|
+ # respond_with(@project, @task, status: 201) do |format|
# if @task.save
# flash[:notice] = 'Task was successfully created.'
# else
@@ -140,7 +144,7 @@ module ActionController #:nodoc:
undef_method(:to_json) if method_defined?(:to_json)
undef_method(:to_yaml) if method_defined?(:to_yaml)
- # Initializes a new responder an invoke the proper format. If the format is
+ # Initializes a new responder and invokes the proper format. If the format is
# not defined, call to_format.
#
def self.call(*args)
@@ -198,6 +202,7 @@ module ActionController #:nodoc:
# This is the common behavior for formats associated with APIs, such as :xml and :json.
def api_behavior(error)
raise error unless resourceful?
+ raise MissingRenderer.new(format) unless has_renderer?
if get?
display resource
@@ -236,20 +241,20 @@ module ActionController #:nodoc:
# Display is just a shortcut to render a resource with the current format.
#
- # display @user, :status => :ok
+ # display @user, status: :ok
#
# For XML requests it's equivalent to:
#
- # render :xml => @user, :status => :ok
+ # render xml: @user, status: :ok
#
# Options sent by the user are also used:
#
- # respond_with(@user, :status => :created)
- # display(@user, :status => :ok)
+ # respond_with(@user, status: :created)
+ # display(@user, status: :ok)
#
# Results in:
#
- # render :xml => @user, :status => :created
+ # render xml: @user, status: :created
#
def display(resource, given_options={})
controller.render given_options.merge!(options).merge!(format => resource)
@@ -265,6 +270,11 @@ module ActionController #:nodoc:
resource.respond_to?(:errors) && !resource.errors.empty?
end
+ # Check whether the neceessary Renderer is available
+ def has_renderer?
+ Renderers::RENDERERS.include?(format)
+ end
+
# By default, render the <code>:edit</code> action for HTML requests with errors, unless
# the verb was POST.
#
diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb
index 9f3c997024..62d5931b45 100644
--- a/actionpack/lib/action_controller/metal/streaming.rb
+++ b/actionpack/lib/action_controller/metal/streaming.rb
@@ -21,15 +21,13 @@ module ActionController #:nodoc:
# supports fibers (fibers are supported since version 1.9.2 of the main
# Ruby implementation).
#
- # == Examples
- #
# Streaming can be added to a given template easily, all you need to do is
# to pass the :stream option.
#
# class PostsController
# def index
- # @posts = Post.scoped
- # render :stream => true
+ # @posts = Post.all
+ # render stream: true
# end
# end
#
@@ -53,10 +51,10 @@ module ActionController #:nodoc:
#
# def dashboard
# # Allow lazy execution of the queries
- # @posts = Post.scoped
- # @pages = Page.scoped
- # @articles = Article.scoped
- # render :stream => true
+ # @posts = Post.all
+ # @pages = Page.all
+ # @articles = Article.all
+ # render stream: true
# end
#
# Notice that :stream only works with templates. Rendering :json
@@ -176,7 +174,7 @@ module ActionController #:nodoc:
# need to create a config file as follow:
#
# # unicorn.config.rb
- # listen 3000, :tcp_nopush => false
+ # listen 3000, tcp_nopush: false
#
# And use it on initialization:
#
@@ -195,31 +193,29 @@ module ActionController #:nodoc:
module Streaming
extend ActiveSupport::Concern
- include AbstractController::Rendering
-
protected
- # Set proper cache control and transfer encoding when streaming
- def _process_options(options) #:nodoc:
- super
- if options[:stream]
- if env["HTTP_VERSION"] == "HTTP/1.0"
- options.delete(:stream)
- else
- headers["Cache-Control"] ||= "no-cache"
- headers["Transfer-Encoding"] = "chunked"
- headers.delete("Content-Length")
+ # Set proper cache control and transfer encoding when streaming
+ def _process_options(options) #:nodoc:
+ super
+ if options[:stream]
+ if env["HTTP_VERSION"] == "HTTP/1.0"
+ options.delete(:stream)
+ else
+ headers["Cache-Control"] ||= "no-cache"
+ headers["Transfer-Encoding"] = "chunked"
+ headers.delete("Content-Length")
+ end
end
end
- end
- # Call render_body if we are streaming instead of usual +render+.
- def _render_template(options) #:nodoc:
- if options.delete(:stream)
- Rack::Chunked::Body.new view_renderer.render_body(view_context, options)
- else
- super
+ # Call render_body if we are streaming instead of usual +render+.
+ def _render_template(options) #:nodoc:
+ if options.delete(:stream)
+ Rack::Chunked::Body.new view_renderer.render_body(view_context, options)
+ else
+ super
+ end
end
- end
end
end
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index 6f46954266..8ae7e474a3 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -1,6 +1,8 @@
-require 'active_support/concern'
require 'active_support/core_ext/hash/indifferent_access'
+require 'active_support/core_ext/array/wrap'
require 'active_support/rescuable'
+require 'action_dispatch/http/upload'
+require 'stringio'
module ActionController
# Raised when a required parameter is missing.
@@ -8,8 +10,6 @@ module ActionController
# params = ActionController::Parameters.new(a: {})
# params.fetch(:b)
# # => ActionController::ParameterMissing: param not found: b
- # params.require(:a)
- # # => ActionController::ParameterMissing: param not found: a
class ParameterMissing < KeyError
attr_reader :param # :nodoc:
@@ -19,13 +19,41 @@ module ActionController
end
end
- # == Action Controller Parameters
+ # Raised when a required parameter value is empty.
+ #
+ # params = ActionController::Parameters.new(a: {})
+ # params.require(:a)
+ # # => ActionController::EmptyParameter: value is empty for required key: a
+ class EmptyParameter < IndexError
+ attr_reader :param
+
+ def initialize(param)
+ @param = param
+ super("value is empty for required key: #{param}")
+ end
+ end
+
+ # Raised when a supplied parameter is not expected.
+ #
+ # params = ActionController::Parameters.new(a: "123", b: "456")
+ # params.permit(:c)
+ # # => ActionController::UnpermittedParameters: found unexpected keys: a, b
+ class UnpermittedParameters < IndexError
+ attr_reader :params # :nodoc:
+
+ def initialize(params) # :nodoc:
+ @params = params
+ super("found unpermitted parameters: #{params.join(", ")}")
+ end
+ end
+
+ # == Action Controller \Parameters
#
# Allows to choose which attributes should be whitelisted for mass updating
# and thus prevent accidentally exposing that which shouldn’t be exposed.
# Provides two methods for this purpose: #require and #permit. The former is
# used to mark parameters as required. The latter is used to set the parameter
- # as permitted and limit which attributes should be allowed for mass updating.
+ # as permitted and limit which attributes should be allowed for mass updating.
#
# params = ActionController::Parameters.new({
# person: {
@@ -40,13 +68,20 @@ module ActionController
# permitted.class # => ActionController::Parameters
# permitted.permitted? # => true
#
- # Person.first.update_attributes!(permitted)
+ # Person.first.update!(permitted)
# # => #<Person id: 1, name: "Francesco", age: 22, role: "user">
#
- # It provides a +permit_all_parameters+ option that controls the top-level
- # behaviour of new instances. If it's +true+, all the parameters will be
- # permitted by default. The default value for +permit_all_parameters+
- # option is +false+.
+ # It provides two options that controls the top-level behavior of new instances:
+ #
+ # * +permit_all_parameters+ - If it's +true+, all the parameters will be
+ # permitted by default. The default is +false+.
+ # * +action_on_unpermitted_parameters+ - Allow to control the behavior when parameters
+ # that are not explicitly permitted are found. The values can be <tt>:log</tt> to
+ # write a message on the logger or <tt>:raise</tt> to raise
+ # ActionController::UnpermittedParameters exception. The default value is <tt>:log</tt>
+ # in test and development environments, +false+ otherwise.
+ #
+ # Examples:
#
# params = ActionController::Parameters.new
# params.permitted? # => false
@@ -56,6 +91,16 @@ module ActionController
# params = ActionController::Parameters.new
# params.permitted? # => true
#
+ # params = ActionController::Parameters.new(a: "123", b: "456")
+ # params.permit(:c)
+ # # => {}
+ #
+ # ActionController::Parameters.action_on_unpermitted_parameters = :raise
+ #
+ # params = ActionController::Parameters.new(a: "123", b: "456")
+ # params.permit(:c)
+ # # => ActionController::UnpermittedParameters: found unpermitted keys: a, b
+ #
# <tt>ActionController::Parameters</tt> is inherited from
# <tt>ActiveSupport::HashWithIndifferentAccess</tt>, this means
# that you can fetch values using either <tt>:key</tt> or <tt>"key"</tt>.
@@ -65,24 +110,27 @@ module ActionController
# params["key"] # => "value"
class Parameters < ActiveSupport::HashWithIndifferentAccess
cattr_accessor :permit_all_parameters, instance_accessor: false
- attr_accessor :permitted # :nodoc:
+ cattr_accessor :action_on_unpermitted_parameters, instance_accessor: false
+
+ # Never raise an UnpermittedParameters exception because of these params
+ # are present. They are added by Rails and it's of no concern.
+ NEVER_UNPERMITTED_PARAMS = %w( controller action )
# Returns a new instance of <tt>ActionController::Parameters</tt>.
# Also, sets the +permitted+ attribute to the default value of
# <tt>ActionController::Parameters.permit_all_parameters</tt>.
#
- # class Person
- # include ActiveRecord::Base
+ # class Person < ActiveRecord::Base
# end
#
# params = ActionController::Parameters.new(name: 'Francesco')
# params.permitted? # => false
- # Person.new(params) # => ActiveModel::ForbiddenAttributesError
+ # Person.new(params) # => ActiveModel::ForbiddenAttributesError
#
# ActionController::Parameters.permit_all_parameters = true
#
# params = ActionController::Parameters.new(name: 'Francesco')
- # params.permitted? # => true
+ # params.permitted? # => true
# Person.new(params) # => #<Person id: nil, name: "Francesco">
def initialize(attributes = nil)
super(attributes)
@@ -106,7 +154,7 @@ module ActionController
# end
#
# params = ActionController::Parameters.new(name: 'Francesco')
- # params.permitted? # => false
+ # params.permitted? # => false
# Person.new(params) # => ActiveModel::ForbiddenAttributesError
# params.permit!
# params.permitted? # => true
@@ -121,41 +169,59 @@ module ActionController
self
end
- # Ensures that a parameter is present. If it's present, returns
- # the parameter at the given +key+, otherwise raises an
- # <tt>ActionController::ParameterMissing</tt> error.
+ # Ensures that a parameter is present. If it's present and not empty,
+ # returns the parameter at the given +key+, if it's empty raises
+ # an <tt>ActionController::EmptyParameter</tt> error, otherwise
+ # raises an <tt>ActionController::ParameterMissing</tt> error.
#
- # ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person)
- # # => {"name"=>"Francesco"}
- #
- # ActionController::Parameters.new(person: nil).require(:person)
- # # => ActionController::ParameterMissing: param not found: person
+ # ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person)
+ # # => {"name"=>"Francesco"}
#
# ActionController::Parameters.new(person: {}).require(:person)
+ # # => ActionController::EmptyParameter: value is empty for required key: person
+ #
+ # ActionController::Parameters.new(name: {}).require(:person)
# # => ActionController::ParameterMissing: param not found: person
def require(key)
- self[key].presence || raise(ParameterMissing.new(key))
+ raise(ActionController::ParameterMissing.new(key)) unless self.key?(key)
+ self[key].presence || raise(ActionController::EmptyParameter.new(key))
end
# Alias of #require.
alias :required :require
# Returns a new <tt>ActionController::Parameters</tt> instance that
- # includes only the given +filters+ and sets the +permitted+ for the
- # object to +true+. This is useful for limiting which attributes
+ # includes only the given +filters+ and sets the +permitted+ attribute
+ # for the object to +true+. This is useful for limiting which attributes
# should be allowed for mass updating.
#
# params = ActionController::Parameters.new(user: { name: 'Francesco', age: 22, role: 'admin' })
# permitted = params.require(:user).permit(:name, :age)
- # permitted.permitted? # => true
+ # permitted.permitted? # => true
# permitted.has_key?(:name) # => true
# permitted.has_key?(:age) # => true
# permitted.has_key?(:role) # => false
#
+ # Only permitted scalars pass the filter. For example, given
+ #
+ # params.permit(:name)
+ #
+ # +:name+ passes it is a key of +params+ whose associated value is of type
+ # +String+, +Symbol+, +NilClass+, +Numeric+, +TrueClass+, +FalseClass+,
+ # +Date+, +Time+, +DateTime+, +StringIO+, +IO+,
+ # +ActionDispatch::Http::UploadedFile+ or +Rack::Test::UploadedFile+.
+ # Otherwise, the key +:name+ is filtered out.
+ #
+ # You may declare that the parameter should be an array of permitted scalars
+ # by mapping it to an empty array:
+ #
+ # params = ActionController::Parameters.new(tags: ['rails', 'parameters'])
+ # params.permit(tags: [])
+ #
# You can also use +permit+ on nested parameters, like:
#
# params = ActionController::Parameters.new({
- # person: {
+ # person: {
# name: 'Francesco',
# age: 22,
# pets: [{
@@ -165,10 +231,10 @@ module ActionController
# }
# })
#
- # permitted = params.permit(person: [ :name, { pets: :name } ])
+ # permitted = params.permit(person: [ :name, { pets: :name } ])
# permitted.permitted? # => true
# permitted[:person][:name] # => "Francesco"
- # permitted[:person][:age] # => nil
+ # permitted[:person][:age] # => nil
# permitted[:person][:pets][0][:name] # => "Purplish"
# permitted[:person][:pets][0][:category] # => nil
#
@@ -179,7 +245,7 @@ module ActionController
# params = ActionController::Parameters.new({
# person: {
# contact: {
- # email: 'none@test.com'
+ # email: 'none@test.com',
# phone: '555-1234'
# }
# }
@@ -189,47 +255,32 @@ module ActionController
# # => {}
#
# params.require(:person).permit(contact: :phone)
- # # => {"contact"=>{"phone"=>"555-1234"}}
+ # # => {"contact"=>{"phone"=>"555-1234"}}
#
# params.require(:person).permit(contact: [ :email, :phone ])
# # => {"contact"=>{"email"=>"none@test.com", "phone"=>"555-1234"}}
def permit(*filters)
params = self.class.new
- filters.each do |filter|
+ filters.flatten.each do |filter|
case filter
- when Symbol, String then
- if has_key?(filter)
- _value = self[filter]
- params[filter] = _value unless Hash === _value
- end
- keys.grep(/\A#{Regexp.escape(filter)}\(\di\)\z/) { |key| params[key] = self[key] }
+ when Symbol, String
+ permitted_scalar_filter(params, filter)
when Hash then
- self.slice(*filter.keys).each do |key, values|
- return unless values
-
- key = key.to_sym
-
- params[key] = each_element(values) do |value|
- # filters are a Hash, so we expect value to be a Hash too
- next if filter.is_a?(Hash) && !value.is_a?(Hash)
-
- value = self.class.new(value) if !value.respond_to?(:permit)
-
- value.permit(*Array.wrap(filter[key]))
- end
- end
+ hash_filter(params, filter)
end
end
+ unpermitted_parameters!(params) if self.class.action_on_unpermitted_parameters
+
params.permit!
end
# Returns a parameter for the given +key+. If not found,
# returns +nil+.
#
- # params = ActionController::Parameters.new(person: { name: 'Francesco' })
- # params[:person] # => {"name"=>"Francesco"}
+ # params = ActionController::Parameters.new(person: { name: 'Francesco' })
+ # params[:person] # => {"name"=>"Francesco"}
# params[:none] # => nil
def [](key)
convert_hashes_to_parameters(key, super)
@@ -242,12 +293,19 @@ module ActionController
# is given, then that will be run and its result returned.
#
# params = ActionController::Parameters.new(person: { name: 'Francesco' })
- # params.fetch(:person) # => {"name"=>"Francesco"}
- # params.fetch(:none) # => ActionController::ParameterMissing: param not found: none
+ # params.fetch(:person) # => {"name"=>"Francesco"}
+ # params.fetch(:none) # => ActionController::ParameterMissing: param not found: none
# params.fetch(:none, 'Francesco') # => "Francesco"
- # params.fetch(:none) { 'Francesco' } # => "Francesco"
+ # params.fetch(:none) { 'Francesco' } # => "Francesco"
def fetch(key, *args)
- convert_hashes_to_parameters(key, super)
+ value = super
+ # Don't rely on +convert_hashes_to_parameters+
+ # so as to not mutate via a +fetch+
+ if value.is_a?(Hash)
+ value = self.class.new(value)
+ value.permit! if permitted?
+ end
+ value
rescue KeyError
raise ActionController::ParameterMissing.new(key)
end
@@ -260,7 +318,9 @@ module ActionController
# params.slice(:a, :b) # => {"a"=>1, "b"=>2}
# params.slice(:d) # => {}
def slice(*keys)
- self.class.new(super)
+ self.class.new(super).tap do |new_instance|
+ new_instance.permitted = @permitted
+ end
end
# Returns an exact copy of the <tt>ActionController::Parameters</tt>
@@ -273,10 +333,15 @@ module ActionController
# copy_params.permitted? # => true
def dup
super.tap do |duplicate|
- duplicate.instance_variable_set :@permitted, @permitted
+ duplicate.permitted = @permitted
end
end
+ protected
+ def permitted=(new_permitted)
+ @permitted = new_permitted
+ end
+
private
def convert_hashes_to_parameters(key, value)
if value.is_a?(Parameters) || !value.is_a?(Hash)
@@ -290,7 +355,7 @@ module ActionController
def each_element(object)
if object.is_a?(Array)
object.map { |el| yield el }.compact
- elsif object.is_a?(Hash) && object.keys.all? { |k| k =~ /\A-?\d+\z/ }
+ elsif fields_for_style?(object)
hash = object.class.new
object.each { |k,v| hash[k] = yield v }
hash
@@ -298,12 +363,111 @@ module ActionController
yield object
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?
+ case self.class.action_on_unpermitted_parameters
+ when :log
+ name = "unpermitted_parameters.action_controller"
+ ActiveSupport::Notifications.instrument(name, keys: unpermitted_keys)
+ when :raise
+ raise ActionController::UnpermittedParameters.new(unpermitted_keys)
+ end
+ end
+ end
+
+ def unpermitted_keys(params)
+ self.keys - params.keys - NEVER_UNPERMITTED_PARAMS
+ end
+
+ #
+ # --- Filtering ----------------------------------------------------------
+ #
+
+ # This is a white list of permitted scalar types that includes the ones
+ # supported in XML and JSON requests.
+ #
+ # This list is in particular used to filter ordinary requests, String goes
+ # as first element to quickly short-circuit the common case.
+ #
+ # If you modify this collection please update the API of +permit+ above.
+ PERMITTED_SCALAR_TYPES = [
+ String,
+ Symbol,
+ NilClass,
+ Numeric,
+ TrueClass,
+ FalseClass,
+ Date,
+ Time,
+ # DateTimes are Dates, we document the type but avoid the redundant check.
+ StringIO,
+ IO,
+ ActionDispatch::Http::UploadedFile,
+ Rack::Test::UploadedFile,
+ ]
+
+ def permitted_scalar?(value)
+ PERMITTED_SCALAR_TYPES.any? {|type| value.is_a?(type)}
+ end
+
+ def permitted_scalar_filter(params, key)
+ if has_key?(key) && permitted_scalar?(self[key])
+ params[key] = self[key]
+ end
+
+ keys.grep(/\A#{Regexp.escape(key)}\(\d+[if]?\)\z/) do |k|
+ if permitted_scalar?(self[k])
+ params[k] = self[k]
+ end
+ end
+ 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]
+ end
+ end
+
+ EMPTY_ARRAY = []
+ def hash_filter(params, filter)
+ filter = filter.with_indifferent_access
+
+ # Slicing filters out non-declared keys.
+ slice(*filter.keys).each do |key, value|
+ next unless value
+
+ if filter[key] == EMPTY_ARRAY
+ # Declaration { comment_ids: [] }.
+ array_of_permitted_scalars_filter(params, key)
+ 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
+ end
+ end
+ end
+ end
end
# == Strong \Parameters
#
# It provides an interface for protecting attributes from end-user
- # assignment. This makes Action Controller parameters forbidden
+ # assignment. This makes Action Controller parameters forbidden
# to be used in Active Model mass assignment until they have been
# whitelisted.
#
@@ -326,13 +490,13 @@ module ActionController
# # into a 400 Bad Request reply.
# def update
# redirect_to current_account.people.find(params[:id]).tap { |person|
- # person.update_attributes!(person_params)
+ # person.update!(person_params)
# }
# end
#
# private
# # Using a private method to encapsulate the permissible parameters is
- # # just a good pattern since you'll be able to reuse the same permit
+ # # just a good pattern since you'll be able to reuse the same permit
# # list between create and update. Also, you can specialize this method
# # with per-user checking of permissible attributes.
# def person_params
@@ -340,18 +504,37 @@ module ActionController
# end
# end
#
+ # In order to use <tt>accepts_nested_attribute_for</tt> with Strong \Parameters, you
+ # will need to specify which nested attributes should be whitelisted.
+ #
+ # class Person
+ # has_many :pets
+ # accepts_nested_attributes_for :pets
+ # end
+ #
+ # class PeopleController < ActionController::Base
+ # def create
+ # Person.create(person_params)
+ # end
+ #
+ # ...
+ #
+ # private
+ #
+ # def person_params
+ # # It's mandatory to specify the nested attributes that should be whitelisted.
+ # # If you use `permit` with just the key that points to the nested attributes hash,
+ # # it will return an empty hash.
+ # params.require(:person).permit(:name, :age, pets_attributes: [ :name, :category ])
+ # end
+ # end
+ #
# See ActionController::Parameters.require and ActionController::Parameters.permit
# for more information.
module StrongParameters
extend ActiveSupport::Concern
include ActiveSupport::Rescuable
- included do
- rescue_from(ActionController::ParameterMissing) do |parameter_missing_exception|
- render text: "Required parameter missing: #{parameter_missing_exception.param}", status: :bad_request
- end
- end
-
# Returns a new ActionController::Parameters object that
# has been instantiated with the <tt>request.parameters</tt>.
def params
diff --git a/actionpack/lib/action_controller/metal/url_for.rb b/actionpack/lib/action_controller/metal/url_for.rb
index 0cdd17bc2e..754249cbc8 100644
--- a/actionpack/lib/action_controller/metal/url_for.rb
+++ b/actionpack/lib/action_controller/metal/url_for.rb
@@ -10,7 +10,7 @@ module ActionController
# include ActionController::UrlFor
# include Rails.application.routes.url_helpers
#
- # delegate :env, :request, :to => :controller
+ # delegate :env, :request, to: :controller
#
# def initialize(controller)
# @controller = controller
@@ -32,7 +32,8 @@ module ActionController
if (same_origin = _routes.equal?(env["action_dispatch.routes"])) ||
(script_name = env["ROUTES_#{_routes.object_id}_SCRIPT_NAME"]) ||
- (original_script_name = env['SCRIPT_NAME'])
+ (original_script_name = env['ORIGINAL_SCRIPT_NAME'])
+
@_url_options.dup.tap do |options|
if original_script_name
options[:original_script_name] = original_script_name
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index ee0f053bad..0833e65d23 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -1,7 +1,6 @@
require "rails"
require "action_controller"
require "action_dispatch/railtie"
-require "action_view/railtie"
require "abstract_controller/railties/routes_helpers"
require "action_controller/railties/helpers"
@@ -20,23 +19,27 @@ module ActionController
end
initializer "action_controller.parameters_config" do |app|
- ActionController::Parameters.permit_all_parameters = app.config.action_controller.delete(:permit_all_parameters) { false }
+ options = app.config.action_controller
+
+ ActionController::Parameters.permit_all_parameters = options.delete(:permit_all_parameters) { false }
+ ActionController::Parameters.action_on_unpermitted_parameters = options.delete(:action_on_unpermitted_parameters) do
+ (Rails.env.test? || Rails.env.development?) ? :log : false
+ end
end
initializer "action_controller.set_configs" do |app|
paths = app.config.paths
options = app.config.action_controller
- options.logger ||= Rails.logger
- options.cache_store ||= Rails.cache
+ options.logger ||= Rails.logger
+ options.cache_store ||= Rails.cache
- options.javascripts_dir ||= paths["public/javascripts"].first
- options.stylesheets_dir ||= paths["public/stylesheets"].first
+ options.javascripts_dir ||= paths["public/javascripts"].first
+ options.stylesheets_dir ||= paths["public/stylesheets"].first
# Ensure readers methods get compiled
- options.asset_path ||= app.config.asset_path
- options.asset_host ||= app.config.asset_host
- options.relative_url_root ||= app.config.relative_url_root
+ options.asset_host ||= app.config.asset_host
+ options.relative_url_root ||= app.config.relative_url_root
ActiveSupport.on_load(:action_controller) do
include app.routes.mounted_helpers
diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb
deleted file mode 100644
index bffd2a02d0..0000000000
--- a/actionpack/lib/action_controller/record_identifier.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'active_support/deprecation'
-require 'action_view/record_identifier'
-
-module ActionController
- module RecordIdentifier
- MESSAGE = 'method will no longer be included by default in controllers since Rails 4.1. ' +
- 'If you would like to use it in controllers, please include ' +
- 'ActionView::RecodIdentifier module.'
-
- def dom_id(record, prefix = nil)
- ActiveSupport::Deprecation.warn 'dom_id ' + MESSAGE
- ActionView::RecordIdentifier.dom_id(record, prefix)
- end
-
- def dom_class(record, prefix = nil)
- ActiveSupport::Deprecation.warn 'dom_class ' + MESSAGE
- ActionView::RecordIdentifier.dom_class(record, prefix)
- end
- end
-end
diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb
index d911d47a1d..5ed3d2ebc1 100644
--- a/actionpack/lib/action_controller/test_case.rb
+++ b/actionpack/lib/action_controller/test_case.rb
@@ -1,6 +1,7 @@
require 'rack/session/abstract/id'
require 'active_support/core_ext/object/to_query'
require 'active_support/core_ext/module/anonymous'
+require 'active_support/core_ext/hash/keys'
module ActionController
module TemplateAssertions
@@ -15,8 +16,9 @@ module ActionController
@_partials = Hash.new(0)
@_templates = Hash.new(0)
@_layouts = Hash.new(0)
+ @_files = Hash.new(0)
- ActiveSupport::Notifications.subscribe("render_template.action_view") do |name, start, finish, id, payload|
+ ActiveSupport::Notifications.subscribe("render_template.action_view") do |_name, _start, _finish, _id, payload|
path = payload[:layout]
if path
@_layouts[path] += 1
@@ -26,7 +28,7 @@ module ActionController
end
end
- ActiveSupport::Notifications.subscribe("!render_template.action_view") do |name, start, finish, id, payload|
+ ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
path = payload[:virtual_path]
next unless path
partial = path =~ /^.*\/_[^\/]*$/
@@ -38,6 +40,16 @@ module ActionController
@_templates[path] += 1
end
+
+ ActiveSupport::Notifications.subscribe("!render_template.action_view") do |_name, _start, _finish, _id, payload|
+ next if payload[:virtual_path] # files don't have virtual path
+
+ path = payload[:identifier]
+ if path
+ @_files[path] += 1
+ @_files[path.split("/").last] += 1
+ end
+ end
end
def teardown_subscriptions
@@ -61,28 +73,27 @@ module ActionController
# 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_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_template layout: nil
+ # assert_template layout: false
#
# # assert that the "_customer" partial was rendered twice
- # assert_template :partial => '_customer', :count => 2
+ # assert_template partial: '_customer', count: 2
#
# # assert that no partials were rendered
- # assert_template :partial => false
+ # assert_template partial: 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 }
+ # 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
+ # Force body to be read in case the template is being streamed.
response.body
case options
@@ -94,7 +105,7 @@ module ActionController
matches_template =
case options
when String
- rendered.any? do |t, num|
+ !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
@@ -106,6 +117,8 @@ module ActionController
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>",
@@ -121,10 +134,18 @@ module ActionController
end
end
+ if options[:file]
+ assert_includes @_files.keys, options[:file]
+ end
+
if expected_partial = options[:partial]
if expected_locals = options[:locals]
if defined?(@_rendered_views)
- view = expected_partial.to_s.sub(/^_/,'')
+ 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)]
@@ -234,18 +255,39 @@ module ActionController
end
end
+ # Methods #destroy and #load! are overridden to avoid calling methods on the
+ # @store object, which does not exist for the TestSession class.
class TestSession < Rack::Session::Abstract::SessionHash #:nodoc:
DEFAULT_OPTIONS = Rack::Session::Abstract::ID::DEFAULT_OPTIONS
def initialize(session = {})
super(nil, nil)
- replace(session.stringify_keys)
+ @id = SecureRandom.hex(16)
+ @data = stringify_keys(session)
@loaded = true
end
def exists?
true
end
+
+ def keys
+ @data.keys
+ end
+
+ def values
+ @data.values
+ end
+
+ def destroy
+ clear
+ end
+
+ private
+
+ def load!
+ @id
+ end
end
# Superclass for ActionController functional tests. Functional tests allow you to
@@ -267,21 +309,21 @@ 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, book: { title: "Love Hina" })
#
# # Assert that the controller tried to redirect us to
# # the created book's URI.
# assert_response :found
#
# # Assert that the controller really put the book in the database.
- # assert_not_nil Book.find_by_title("Love Hina")
+ # assert_not_nil Book.find_by(title: "Love Hina")
# end
# end
#
# You can also send a real document in the simulated HTTP request.
#
# def test_create
- # json = {:book => { :title => "Love Hina" }}.to_json
+ # json = {book: { title: "Love Hina" }}.to_json
# post :create, json
# end
#
@@ -356,15 +398,8 @@ module ActionController
#
# If you're using named routes, they can be easily tested using the original named routes' methods straight in the test case.
#
- # assert_redirected_to page_url(:title => 'foo')
+ # assert_redirected_to page_url(title: 'foo')
class TestCase < ActiveSupport::TestCase
-
- # Use AC::TestCase for the base class when describing a controller
- register_spec_type(self) do |desc|
- Class === desc && desc < ActionController::Metal
- end
- register_spec_type(/Controller( ?Test)?\z/i, self)
-
module Behavior
extend ActiveSupport::Concern
include ActionDispatch::TestProcess
@@ -416,41 +451,54 @@ module ActionController
end
- # Executes a request simulating GET HTTP method and set/volley the response
+ # 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
+ # (<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, HEAD, and OPTIONS requests with
+ # +post+, +patch+, +put+, +delete+, +head+, and +options+.
+ #
+ # 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)
end
- # Executes a request simulating POST HTTP method and set/volley the response
+ # 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)
end
- # Executes a request simulating PATCH HTTP method and set/volley the response
+ # 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)
end
- # Executes a request simulating PUT HTTP method and set/volley the response
+ # 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)
end
- # Executes a request simulating DELETE HTTP method and set/volley the response
+ # 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)
end
- # Executes a request simulating HEAD HTTP method and set/volley the response
+ # 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)
end
- # Executes a request simulating OPTIONS HTTP method and set/volley the response
- def options(action, *args)
- process(action, "OPTIONS", *args)
- end
-
def xml_http_request(request_method, action, parameters = nil, session = nil, flash = nil)
@request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest'
@request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ')
@@ -476,7 +524,6 @@ module ActionController
def process(action, http_method = 'GET', *args)
check_required_ivars
- http_method, args = handle_old_process_api(http_method, args)
if args.first.is_a?(String) && http_method != 'HEAD'
@request.env['RAW_POST_DATA'] = args.shift
@@ -504,12 +551,12 @@ module ActionController
parameters ||= {}
controller_class_name = @controller.class.anonymous? ?
"anonymous" :
- @controller.class.name.underscore.sub(/_controller$/, '')
+ @controller.class.controller_path
@request.assign_parameters(@routes, controller_class_name, action.to_s, parameters)
@request.session.update(session) if session
- @request.session["flash"] = @request.flash.update(flash || {})
+ @request.flash.update(flash || {})
@controller.request = @request
@controller.response = @response
@@ -526,6 +573,7 @@ module ActionController
@response.prepare!
@assigns = @controller.respond_to?(:view_assigns) ? @controller.view_assigns : {}
+ @request.session['flash'] = @request.flash.to_session_value
@request.session.delete('flash') if @request.session['flash'].blank?
@response
end
@@ -579,17 +627,6 @@ module ActionController
end
end
- def handle_old_process_api(http_method, args)
- # 4.0: Remove this method.
- if http_method.is_a?(Hash)
- ActiveSupport::Deprecation.warn("TestCase#process now expects the HTTP method as second argument: process(action, http_method, params, session, flash)")
- args.unshift(http_method)
- http_method = args.last.is_a?(String) ? args.last : "GET"
- end
-
- [http_method, args]
- end
-
def build_request_uri(action, parameters)
unless @request.env["PATH_INFO"]
options = @controller.respond_to?(:url_options) ? @controller.__send__(:url_options).merge(parameters) : parameters
diff --git a/actionpack/lib/action_controller/vendor/html-scanner.rb b/actionpack/lib/action_controller/vendor/html-scanner.rb
deleted file mode 100644
index 896208bc05..0000000000
--- a/actionpack/lib/action_controller/vendor/html-scanner.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'action_view/vendor/html-scanner'
-require 'active_support/deprecation'
-
-ActiveSupport::Deprecation.warn 'Vendored html-scanner was moved to action_view, please require "action_view/vendor/html-scanner" instead. ' +
- 'This file will be removed in Rails 4.1'