diff options
Diffstat (limited to 'actionpack/lib/action_controller')
11 files changed, 205 insertions, 67 deletions
diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index 2892e093af..cf2cda039d 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -70,12 +70,26 @@ module ActionController 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 end + def view_cache_dependencies + self.class._view_cache_dependencies.map { |dep| instance_exec(&dep) }.compact + end + protected # Convenience accessor. def cache(key, options = {}, &block) 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/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index 3f9b382a11..eddee08545 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -1,5 +1,3 @@ -require 'active_support/core_ext/class/attribute' - module ActionController module ConditionalGet extend ActiveSupport::Concern diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index 896238b7dc..e295002b16 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -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] diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 6bf306ac5b..d04fbae150 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -82,7 +82,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,7 +92,7 @@ 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| @@ -120,7 +120,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): @@ -227,7 +227,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 @@ -320,7 +320,7 @@ 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) diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index 091facfd8d..59b91a240e 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -32,7 +32,7 @@ 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 @@ -65,7 +65,6 @@ module ActionController 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) diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 0b3c438ec2..73e9b5660d 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -26,7 +26,7 @@ module ActionController #:nodoc: # # class PostsController # def index - # @posts = Post.scoped + # @posts = Post.all # render stream: true # end # end @@ -51,9 +51,9 @@ module ActionController #:nodoc: # # def dashboard # # Allow lazy execution of the queries - # @posts = Post.scoped - # @pages = Page.scoped - # @articles = Article.scoped + # @posts = Post.all + # @pages = Page.all + # @articles = Article.all # render stream: true # end # diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index 8faa5f8a13..0d84fbb62b 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -1,4 +1,3 @@ -require 'active_support/concern' require 'active_support/core_ext/hash/indifferent_access' require 'active_support/core_ext/array/wrap' require 'active_support/rescuable' @@ -20,6 +19,20 @@ module ActionController 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 @@ -41,13 +54,18 @@ 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. # # params = ActionController::Parameters.new # params.permitted? # => false @@ -57,6 +75,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>. @@ -66,6 +94,11 @@ module ActionController # params["key"] # => "value" class Parameters < ActiveSupport::HashWithIndifferentAccess cattr_accessor :permit_all_parameters, instance_accessor: false + 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 @@ -151,6 +184,20 @@ module ActionController # 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+, or +IO+. 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.permit(tags: []) + # # You can also use +permit+ on nested parameters, like: # # params = ActionController::Parameters.new({ @@ -197,32 +244,15 @@ module ActionController 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)}\(\d+[if]?\)\z/) { |key| params[key] = self[key] } + when Symbol, String + permitted_scalar_filter(params, filter) when Hash then - filter = filter.with_indifferent_access - - 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) + params.permit! end @@ -301,6 +331,97 @@ module ActionController yield object end end + + def unpermitted_parameters!(params) + unpermitted_keys = unpermitted_keys(params) + if unpermitted_keys.any? + case self.class.action_on_unpermitted_parameters + when :log + ActionController::Base.logger.debug "Unpermitted parameters: #{unpermitted_keys.join(", ")}" + 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, + ] + + 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 + + def hash_filter(params, filter) + filter = filter.with_indifferent_access + + # Slicing filters out non-declared keys. + slice(*filter.keys).each do |key, value| + return unless value + + if filter[key] == [] + # Declaration { comment_ids: [] }. + array_of_permitted_scalars_filter(params, key) + else + # Declaration { user: :name } or { user: [:name, :age, { adress: ... }] }. + 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 @@ -329,7 +450,7 @@ 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 # @@ -374,12 +495,6 @@ module ActionController 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/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 3e44155f73..5379547c57 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -20,22 +20,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_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 index b49128c184..d598bac467 100644 --- a/actionpack/lib/action_controller/record_identifier.rb +++ b/actionpack/lib/action_controller/record_identifier.rb @@ -1,19 +1,30 @@ -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.' + MODULE_MESSAGE = 'Calling ActionController::RecordIdentifier.%s is deprecated and ' \ + 'will be removed in Rails 4.1, please call using ActionView::RecordIdentifier instead.' + INSTANCE_MESSAGE = '%s 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::RecordIdentifier module.' def dom_id(record, prefix = nil) - ActiveSupport::Deprecation.warn('dom_id ' + MESSAGE) + ActiveSupport::Deprecation.warn(INSTANCE_MESSAGE % 'dom_id') ActionView::RecordIdentifier.dom_id(record, prefix) end def dom_class(record, prefix = nil) - ActiveSupport::Deprecation.warn('dom_class ' + MESSAGE) + ActiveSupport::Deprecation.warn(INSTANCE_MESSAGE % 'dom_class') + ActionView::RecordIdentifier.dom_class(record, prefix) + end + + def self.dom_id(record, prefix = nil) + ActiveSupport::Deprecation.warn(MODULE_MESSAGE % 'dom_id') + ActionView::RecordIdentifier.dom_id(record, prefix) + end + + def self.dom_class(record, prefix = nil) + ActiveSupport::Deprecation.warn(MODULE_MESSAGE % 'dom_class') ActionView::RecordIdentifier.dom_class(record, prefix) end end diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 331d15d403..d8206b573d 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -81,8 +81,7 @@ module ActionController # # assert that the "_customer" partial was rendered with a specific object # assert_template partial: '_customer', locals: { customer: @customer } def assert_template(options = {}, message = nil) - # Force body to be read in case the - # template is being streamed + # Force body to be read in case the template is being streamed. response.body case options |