aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/CHANGELOG.md63
-rw-r--r--actionpack/lib/action_controller.rb2
-rw-r--r--actionpack/lib/action_controller/base.rb25
-rw-r--r--actionpack/lib/action_controller/log_subscriber.rb3
-rw-r--r--actionpack/lib/action_controller/metal.rb25
-rw-r--r--actionpack/lib/action_controller/metal/params_wrapper.rb14
-rw-r--r--actionpack/lib/action_controller/metal/request_forgery_protection.rb93
-rw-r--r--actionpack/lib/action_controller/metal/strong_parameters.rb335
-rw-r--r--actionpack/lib/action_controller/railtie.rb4
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb11
-rw-r--r--actionpack/lib/action_dispatch/middleware/exception_wrapper.rb6
-rw-r--r--actionpack/lib/action_dispatch/routing/mapper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb2
-rw-r--r--actionpack/lib/action_view/lookup_context.rb2
-rw-r--r--actionpack/lib/action_view/template/handlers.rb11
-rw-r--r--actionpack/test/controller/log_subscriber_test.rb15
-rw-r--r--actionpack/test/controller/parameters/nested_parameters_test.rb113
-rw-r--r--actionpack/test/controller/parameters/parameters_permit_test.rb73
-rw-r--r--actionpack/test/controller/parameters/parameters_require_test.rb10
-rw-r--r--actionpack/test/controller/params_wrapper_test.rb40
-rw-r--r--actionpack/test/controller/permitted_params_test.rb25
-rw-r--r--actionpack/test/controller/request_forgery_protection_test.rb16
-rw-r--r--actionpack/test/controller/required_params_test.rb30
-rw-r--r--actionpack/test/dispatch/request_test.rb39
-rw-r--r--actionpack/test/dispatch/routing_test.rb7
-rw-r--r--actionpack/test/fixtures/company.rb1
-rw-r--r--actionpack/test/fixtures/ruby_template.ruby (renamed from actionpack/test/fixtures/ruby_template.rb)0
-rw-r--r--actionpack/test/template/render_test.rb15
28 files changed, 867 insertions, 115 deletions
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 36e8479441..9f0f214137 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,6 +1,67 @@
## Rails 4.0.0 (unreleased) ##
-* Add .rb template handler, this handler simply allows arbitrary Ruby code as a template. *Guillermo Iguaran*
+* Add `ActionController::StrongParameters`, this module converts `params` hash into
+ an instance of ActionController::Parameters that allows whitelisting of permitted
+ parameters. Non-permitted parameters are forbidden to be used in Active Model by default
+ For more details check the documentation of the module or the
+ [strong_parameters gem](https://github.com/rails/strong_parameters)
+
+ *DHH + Guillermo Iguaran*
+
+* Remove Integration between `attr_accessible`/`attr_protected` and
+ `ActionController::ParamsWrapper`. ParamWrapper now wraps all the parameters returned
+ by the class method attribute_names
+
+ *Guillermo Iguaran*
+
+* Fix #7646, the log now displays the correct status code when an exception is raised.
+
+ *Yves Senn*
+
+* Allow pass couple extensions to `ActionView::Template.register_template_handler` call. *Tima Maslyuchenko*
+
+* Fixed a bug with shorthand routes scoped with the `:module` option not
+ adding the module to the controller as described in issue #6497.
+ This should now work properly:
+
+ scope :module => "engine" do
+ get "api/version" # routes to engine/api#version
+ end
+
+ *Luiz Felipe Garcia Pereira*
+
+* Sprockets integration has been extracted from Action Pack and the `sprockets-rails`
+ gem should be added to Gemfile (under the assets group) in order to use Rails asset
+ pipeline in future versions of Rails.
+
+ *Guillermo Iguaran*
+
+* `ActionDispatch::Session::MemCacheStore` now uses `dalli` instead of the deprecated
+ `memcache-client` gem. As side effect the autoloading of unloaded classes objects
+ saved as values in session isn't supported anymore when mem_cache session store is
+ used, this can have an impact in apps only when config.cache_classes is false.
+
+ *Arun Agrawal + Guillermo Iguaran*
+
+* Support multiple etags in If-None-Match header. *Travis Warlick*
+
+* Allow to configure how unverified request will be handled using `:with`
+ option in `protect_from_forgery` method.
+
+ Valid unverified request handling methods are:
+
+ - `:exception` - Raises ActionController::InvalidAuthenticityToken exception.
+ - `:reset_session` - Resets the session.
+ - `:null_session` - Provides an empty session during request but doesn't
+ reset it completely. Used as default if `:with` option is not specified.
+
+ New applications are generated with:
+
+ protect_from_forgery :with => :exception
+
+ *Sergey Nartimov*
+
+* Add .ruby template handler, this handler simply allows arbitrary Ruby code as a template. *Guillermo Iguaran*
* Add `separator` option for `ActionView::Helpers::TextHelper#excerpt`:
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 8c1f548e47..1a13d7af29 100644
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -2,6 +2,7 @@ require 'active_support/rails'
require 'abstract_controller'
require 'action_dispatch'
require 'action_controller/metal/live'
+require 'action_controller/metal/strong_parameters'
module ActionController
extend ActiveSupport::Autoload
@@ -34,6 +35,7 @@ module ActionController
autoload :Rescue
autoload :Responder
autoload :Streaming
+ autoload :StrongParameters
autoload :Testing
autoload :UrlFor
end
diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb
index f829f5e8a2..6b8d9384d4 100644
--- a/actionpack/lib/action_controller/base.rb
+++ b/actionpack/lib/action_controller/base.rb
@@ -162,7 +162,24 @@ module ActionController
class Base < Metal
abstract!
- # Shortcut helper that returns all the ActionController::Base modules except the ones passed in the argument:
+ # We document the request and response methods here because albeit they are
+ # implemented in ActionController::Metal, the type of the returned objects
+ # is unknown at that level.
+
+ ##
+ # :method: request
+ #
+ # Returns an ActionDispatch::Request instance that represents the
+ # current request.
+
+ ##
+ # :method: response
+ #
+ # Returns an ActionDispatch::Response that represents the current
+ # response.
+
+ # Shortcut helper that returns all the modules included in
+ # ActionController::Base except the ones passed as arguments:
#
# class MetalController
# ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
@@ -170,8 +187,9 @@ module ActionController
# end
# end
#
- # This gives better control over what you want to exclude and makes it easier
- # to create a bare controller class, instead of listing the modules required manually.
+ # This gives better control over what you want to exclude and makes it
+ # easier to create a bare controller class, instead of listing the modules
+ # required manually.
def self.without_modules(*modules)
modules = modules.map do |m|
m.is_a?(Symbol) ? ActionController.const_get(m) : m
@@ -196,6 +214,7 @@ module ActionController
Caching,
MimeResponds,
ImplicitRender,
+ StrongParameters,
Cookies,
Flash,
diff --git a/actionpack/lib/action_controller/log_subscriber.rb b/actionpack/lib/action_controller/log_subscriber.rb
index a7c0e971e7..f41d1bb4b9 100644
--- a/actionpack/lib/action_controller/log_subscriber.rb
+++ b/actionpack/lib/action_controller/log_subscriber.rb
@@ -19,7 +19,8 @@ module ActionController
status = payload[:status]
if status.nil? && payload[:exception].present?
- status = ActionDispatch::ExceptionWrapper.new({}, payload[:exception]).status_code
+ exception_class_name = payload[:exception].first
+ status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
end
message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in %.0fms" % event.duration
message << " (#{additions.join(" | ")})" unless additions.blank?
diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb
index b38f990efa..f5ab1e2350 100644
--- a/actionpack/lib/action_controller/metal.rb
+++ b/actionpack/lib/action_controller/metal.rb
@@ -112,7 +112,7 @@ module ActionController
# ==== Returns
# * <tt>string</tt>
def self.controller_name
- @controller_name ||= self.name.demodulize.sub(/Controller$/, '').underscore
+ @controller_name ||= name.demodulize.sub(/Controller$/, '').underscore
end
# Delegates to the class' <tt>controller_name</tt>
@@ -203,36 +203,29 @@ module ActionController
class_attribute :middleware_stack
self.middleware_stack = ActionController::MiddlewareStack.new
- def self.inherited(base) #nodoc:
- base.middleware_stack = self.middleware_stack.dup
+ def self.inherited(base) # :nodoc:
+ base.middleware_stack = middleware_stack.dup
super
end
- # Adds given middleware class and its args to bottom of middleware_stack
+ # Pushes the given Rack middleware and its arguments to the bottom of the
+ # middleware stack.
def self.use(*args, &block)
middleware_stack.use(*args, &block)
end
- # Alias for middleware_stack
+ # Alias for +middleware_stack+.
def self.middleware
middleware_stack
end
- # Makes the controller a rack endpoint that points to the action in
- # the given env's action_dispatch.request.path_parameters key.
+ # Makes the controller a Rack endpoint that runs the action in the given
+ # +env+'s +action_dispatch.request.path_parameters+ key.
def self.call(env)
action(env['action_dispatch.request.path_parameters'][:action]).call(env)
end
- # Return a rack endpoint for the given action. Memoize the endpoint, so
- # multiple calls into MyController.action will return the same object
- # for the same action.
- #
- # ==== Parameters
- # * <tt>action</tt> - An action name
- #
- # ==== Returns
- # * <tt>proc</tt> - A rack application
+ # Returns a Rack endpoint for the given action name.
def self.action(name, klass = ActionDispatch::Request)
middleware_stack.build(name.to_s) do |env|
new.dispatch(name, klass.new(env))
diff --git a/actionpack/lib/action_controller/metal/params_wrapper.rb b/actionpack/lib/action_controller/metal/params_wrapper.rb
index 2736948ce0..88b9e78da7 100644
--- a/actionpack/lib/action_controller/metal/params_wrapper.rb
+++ b/actionpack/lib/action_controller/metal/params_wrapper.rb
@@ -15,7 +15,7 @@ module ActionController
# a non-empty array:
#
# class UsersController < ApplicationController
- # wrap_parameters :format => [:json, :xml]
+ # wrap_parameters format: [:json, :xml]
# end
#
# If you enable +ParamsWrapper+ for +:json+ format, instead of having to
@@ -38,13 +38,12 @@ module ActionController
# +:exclude+ options like this:
#
# class UsersController < ApplicationController
- # wrap_parameters :person, :include => [:username, :password]
+ # wrap_parameters :person, include: [:username, :password]
# end
#
# On ActiveRecord models with no +:include+ or +:exclude+ option set,
- # if attr_accessible is set on that model, it will only wrap the accessible
- # parameters, else it will only wrap the parameters returned by the class
- # method attribute_names.
+ # it will only wrap the parameters returned by the class method
+ # <tt>attribute_names</tt>.
#
# If you're going to pass the parameters to an +ActiveModel+ object (such as
# <tt>User.new(params[:user])</tt>), you might consider passing the model class to
@@ -165,10 +164,7 @@ module ActionController
unless options[:include] || options[:exclude]
model ||= _default_wrap_model
- role = options.fetch(:as, :default)
- if model.respond_to?(:accessible_attributes) && model.accessible_attributes(role).present?
- options[:include] = model.accessible_attributes(role).to_a
- elsif model.respond_to?(:attribute_names) && model.attribute_names.present?
+ if model.respond_to?(:attribute_names) && model.attribute_names.present?
options[:include] = model.attribute_names
end
end
diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
index d5f1cbc1a8..17d4a793ac 100644
--- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb
+++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb
@@ -1,3 +1,4 @@
+require 'rack/session/abstract/id'
require 'action_controller/metal/exceptions'
module ActionController #:nodoc:
@@ -49,10 +50,6 @@ module ActionController #:nodoc:
config_accessor :request_forgery_protection_token
self.request_forgery_protection_token ||= :authenticity_token
- # Controls how unverified request will be handled
- config_accessor :request_forgery_protection_method
- self.request_forgery_protection_method ||= :reset_session
-
# 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?
@@ -78,12 +75,80 @@ module ActionController #:nodoc:
# Valid Options:
#
# * <tt>:only/:except</tt> - Passed to the <tt>before_filter</tt> call. Set which actions are verified.
- # * <tt>:with</tt> - Set the method to handle unverified request. Valid values: <tt>:exception</tt> and <tt>:reset_session</tt> (default).
+ # * <tt>:with</tt> - Set the method to handle unverified request.
+ #
+ # Valid unverified request handling methods are:
+ # * <tt>:exception</tt> - Raises ActionController::InvalidAuthenticityToken exception.
+ # * <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.request_forgery_protection_token ||= :authenticity_token
- self.request_forgery_protection_method = options.delete(:with) if options.key?(:with)
prepend_before_filter :verify_authenticity_token, options
end
+
+ private
+
+ def protection_method_module(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'
+ end
+ end
+
+ module ProtectionMethods
+ module NullSession
+ protected
+
+ # 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.env['action_dispatch.request.flash_hash'] = nil
+ request.env['rack.session.options'] = { skip: true }
+ request.env['action_dispatch.cookies'] = NullCookieJar.build(request)
+ end
+
+ class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc:
+ def initialize
+ super(nil, nil)
+ @loaded = true
+ end
+
+ def exists?
+ true
+ end
+ end
+
+ class NullCookieJar < ActionDispatch::Cookies::CookieJar #:nodoc:
+ def self.build(request)
+ secret = request.env[ActionDispatch::Cookies::TOKEN_KEY]
+ host = request.host
+ secure = request.ssl?
+
+ new(secret, host, secure)
+ end
+
+ def write(*)
+ # nothing
+ end
+ end
+ end
+
+ module ResetSession
+ protected
+
+ def handle_unverified_request
+ reset_session
+ end
+ end
+
+ module Exception
+ protected
+
+ def handle_unverified_request
+ raise ActionController::InvalidAuthenticityToken
+ end
+ end
end
protected
@@ -95,22 +160,6 @@ module ActionController #:nodoc:
end
end
- # This is the method that defines the application behavior when a request is found to be unverified.
- # By default, \Rails uses <tt>request_forgery_protection_method</tt> when it finds an unverified request:
- #
- # * <tt>:reset_session</tt> - Resets the session.
- # * <tt>:exception</tt>: - Raises ActionController::InvalidAuthenticityToken exception.
- def handle_unverified_request
- case request_forgery_protection_method
- when :exception
- raise ActionController::InvalidAuthenticityToken
- when :reset_session
- reset_session
- else
- raise ArgumentError, 'Invalid request forgery protection method, use :exception or :reset_session'
- end
- end
-
# Returns true or false if a request is verified. Checks:
#
# * is it a GET request? Gets should be safe and idempotent
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
new file mode 100644
index 0000000000..24768b23a8
--- /dev/null
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -0,0 +1,335 @@
+require 'active_support/concern'
+require 'active_support/core_ext/hash/indifferent_access'
+require 'active_support/rescuable'
+
+module ActionController
+ # Raised when a required parameter is missing.
+ #
+ # params = ActionController::Parameters.new(a: {})
+ # params.fetch(:b)
+ # # => ActionController::ParameterMissing: key not found: b
+ # params.require(:a)
+ # # => ActionController::ParameterMissing: key not found: a
+ class ParameterMissing < KeyError
+ attr_reader :param # :nodoc:
+
+ def initialize(param) # :nodoc:
+ @param = param
+ super("key not found: #{param}")
+ 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.
+ #
+ # params = ActionController::Parameters.new({
+ # person: {
+ # name: 'Francesco',
+ # age: 22,
+ # role: 'admin'
+ # }
+ # })
+ #
+ # permitted = params.require(:person).permit(:name, :age)
+ # permitted # => {"name"=>"Francesco", "age"=>22}
+ # permitted.class # => ActionController::Parameters
+ # permitted.permitted? # => true
+ #
+ # Person.first.update_attributes!(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+.
+ #
+ # params = ActionController::Parameters.new
+ # params.permitted? # => false
+ #
+ # ActionController::Parameters.permit_all_parameters = true
+ #
+ # params = ActionController::Parameters.new
+ # params.permitted? # => true
+ #
+ # <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>.
+ #
+ # params = ActionController::Parameters.new(key: 'value')
+ # params[:key] # => "value"
+ # params["key"] # => "value"
+ class Parameters < ActiveSupport::HashWithIndifferentAccess
+ cattr_accessor :permit_all_parameters, instance_accessor: false
+ attr_accessor :permitted # :nodoc:
+
+ # 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
+ # end
+ #
+ # params = ActionController::Parameters.new(name: 'Francesco')
+ # params.permitted? # => false
+ # Person.new(params) # => ActiveModel::ForbiddenAttributesError
+ #
+ # ActionController::Parameters.permit_all_parameters = true
+ #
+ # params = ActionController::Parameters.new(name: 'Francesco')
+ # params.permitted? # => true
+ # Person.new(params) # => #<Person id: nil, name: "Francesco">
+ def initialize(attributes = nil)
+ super(attributes)
+ @permitted = self.class.permit_all_parameters
+ end
+
+ # Returns +true+ if the parameter is permitted, +false+ otherwise.
+ #
+ # params = ActionController::Parameters.new
+ # params.permitted? # => false
+ # params.permit!
+ # params.permitted? # => true
+ def permitted?
+ @permitted
+ end
+
+ # Sets the +permitted+ attribute to +true+. This can be used to pass
+ # mass assignment. Returns +self+.
+ #
+ # class Person < ActiveRecord::Base
+ # end
+ #
+ # params = ActionController::Parameters.new(name: 'Francesco')
+ # params.permitted? # => false
+ # Person.new(params) # => ActiveModel::ForbiddenAttributesError
+ # params.permit!
+ # params.permitted? # => true
+ # Person.new(params) # => #<Person id: nil, name: "Francesco">
+ def permit!
+ @permitted = true
+ 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.
+ #
+ # ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person)
+ # # => {"name"=>"Francesco"}
+ #
+ # ActionController::Parameters.new(person: nil).require(:person)
+ # # => ActionController::ParameterMissing: key not found: person
+ #
+ # ActionController::Parameters.new(person: {}).require(:person)
+ # # => ActionController::ParameterMissing: key not found: person
+ def require(key)
+ self[key].presence || raise(ParameterMissing.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
+ # 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.has_key?(:name) # => true
+ # permitted.has_key?(:age) # => true
+ # permitted.has_key?(:role) # => false
+ #
+ # You can also use +permit+ on nested parameters, like:
+ #
+ # params = ActionController::Parameters.new({
+ # person: {
+ # name: 'Francesco',
+ # age: 22,
+ # pets: [{
+ # name: 'Purplish',
+ # category: 'dogs'
+ # }]
+ # }
+ # })
+ #
+ # permitted = params.permit(person: [ :name, { pets: [ :name ] } ])
+ # permitted.permitted? # => true
+ # permitted[:person][:name] # => "Francesco"
+ # permitted[:person][:age] # => nil
+ # permitted[:person][:pets][0][:name] # => "Purplish"
+ # permitted[:person][:pets][0][:category] # => nil
+ def permit(*filters)
+ params = self.class.new
+
+ filters.each do |filter|
+ case filter
+ when Symbol, String then
+ params[filter] = self[filter] if has_key?(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
+ end
+ end
+
+ 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[:none] # => nil
+ def [](key)
+ convert_hashes_to_parameters(key, super)
+ end
+
+ # Returns a parameter for the given +key+. If the +key+
+ # can't be found, there are several options: With no other arguments,
+ # it will raise an <tt>ActionController::ParameterMissing</tt> error;
+ # if more arguments are given, then that will be returned; if a block
+ # 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: key not found: none
+ # params.fetch(:none, 'Francesco') # => "Francesco"
+ # params.fetch(:none) { 'Francesco' } # => "Francesco"
+ def fetch(key, *args)
+ convert_hashes_to_parameters(key, super)
+ rescue KeyError
+ raise ActionController::ParameterMissing.new(key)
+ end
+
+ # Returns a new <tt>ActionController::Parameters</tt> instance that
+ # includes only the given +keys+. If the given +keys+
+ # don't exist, returns an empty hash.
+ #
+ # params = ActionController::Parameters.new(a: 1, b: 2, c: 3)
+ # params.slice(:a, :b) # => {"a"=>1, "b"=>2}
+ # params.slice(:d) # => {}
+ def slice(*keys)
+ self.class.new(super)
+ end
+
+ # Returns an exact copy of the <tt>ActionController::Parameters</tt>
+ # instance. +permitted+ state is kept on the duped object.
+ #
+ # params = ActionController::Parameters.new(a: 1)
+ # params.permit!
+ # params.permitted? # => true
+ # copy_params = params.dup # => {"a"=>1}
+ # copy_params.permitted? # => true
+ def dup
+ super.tap do |duplicate|
+ duplicate.instance_variable_set :@permitted, @permitted
+ end
+ end
+
+ private
+ def convert_hashes_to_parameters(key, value)
+ if value.is_a?(Parameters) || !value.is_a?(Hash)
+ value
+ else
+ # Convert to Parameters on first access
+ self[key] = self.class.new(value)
+ end
+ end
+
+ 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/ }
+ hash = object.class.new
+ object.each { |k,v| hash[k] = yield v }
+ hash
+ else
+ yield object
+ end
+ end
+ end
+
+ # == Strong Parameters
+ #
+ # It provides an interface for proctecting attributes from end-user
+ # assignment. This makes Action Controller parameters are forbidden
+ # to be used in Active Model mass assignmets until they have been
+ # whitelisted.
+ #
+ # In addition, parameters can be marked as required and flow through a
+ # predefined raise/rescue flow to end up as a 400 Bad Request with no
+ # effort.
+ #
+ # class PeopleController < ActionController::Base
+ # # This will raise an ActiveModel::ForbiddenAttributes exception because
+ # # it's using mass assignment without an explicit permit step.
+ # def create
+ # Person.create(params[:person])
+ # end
+ #
+ # # This will pass with flying colors as long as there's a person key in the
+ # # parameters, otherwise it'll raise a ActionController::MissingParameter
+ # # exception, which will get caught by ActionController::Base and turned
+ # # into that 400 Bad Request reply.
+ # def update
+ # redirect_to current_account.people.find(params[:id]).tap { |person|
+ # person.update_attributes!(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
+ # # list between create and update. Also, you can specialize this method
+ # # with per-user checking of permissible attributes.
+ # def person_params
+ # params.require(:person).permit(:name, :age)
+ # 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
+ @_params ||= Parameters.new(request.parameters)
+ end
+
+ # Assigns the given +value+ to the +params+ hash. If +value+
+ # is a Hash, this will create an ActionController::Parameters
+ # object that has been instantiated with the given +value+ hash.
+ def params=(value)
+ @_params = value.is_a?(Hash) ? Parameters.new(value) : value
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb
index 3ecc105e22..f2c68432c1 100644
--- a/actionpack/lib/action_controller/railtie.rb
+++ b/actionpack/lib/action_controller/railtie.rb
@@ -19,6 +19,10 @@ module ActionController
ActionController::Helpers.helpers_path = app.helpers_paths
end
+ initializer "action_controller.parameters_config" do |app|
+ ActionController::Parameters.permit_all_parameters = app.config.action_controller.delete(:permit_all_parameters) { false }
+ end
+
initializer "action_controller.set_configs" do |app|
paths = app.config.paths
options = app.config.action_controller
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index a7f93b780e..0d6015d993 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -17,12 +17,21 @@ module ActionDispatch
env[HTTP_IF_NONE_MATCH]
end
+ def if_none_match_etags
+ (if_none_match ? if_none_match.split(/\s*,\s*/) : []).collect do |etag|
+ etag.gsub(/^\"|\"$/, "")
+ end
+ end
+
def not_modified?(modified_at)
if_modified_since && modified_at && if_modified_since >= modified_at
end
def etag_matches?(etag)
- if_none_match && if_none_match == etag
+ if etag
+ etag = etag.gsub(/^\"|\"$/, "")
+ if_none_match_etags.include?(etag)
+ end
end
# Check response freshness (Last-Modified and ETag) against request
diff --git a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
index 7349b578d2..ae38c56a67 100644
--- a/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
+++ b/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb
@@ -37,7 +37,7 @@ module ActionDispatch
end
def status_code
- Rack::Utils.status_code(@@rescue_responses[@exception.class.name])
+ self.class.status_code_for_exception(@exception.class.name)
end
def application_trace
@@ -52,6 +52,10 @@ module ActionDispatch
clean_backtrace(:all)
end
+ def self.status_code_for_exception(class_name)
+ Rack::Utils.status_code(@@rescue_responses[class_name])
+ end
+
private
def original_exception(exception)
diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb
index ddb34a2394..49afa01d25 100644
--- a/actionpack/lib/action_dispatch/routing/mapper.rb
+++ b/actionpack/lib/action_dispatch/routing/mapper.rb
@@ -182,7 +182,7 @@ module ActionDispatch
controller ||= default_controller
action ||= default_action
- unless controller.is_a?(Regexp) || to_shorthand
+ unless controller.is_a?(Regexp)
controller = [@scope[:module], controller].compact.join("/").presence
end
diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb
index 17fe7dc1b4..0bb08cd7ff 100644
--- a/actionpack/lib/action_view/helpers/form_helper.rb
+++ b/actionpack/lib/action_view/helpers/form_helper.rb
@@ -785,7 +785,7 @@ module ActionView
# Returns an input tag of the "password" type tailored for accessing a specified attribute (identified by +method+) on an object
# assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
# hash with +options+. These options will be tagged onto the HTML as an HTML element attribute as in the example
- # shown.
+ # shown. For security reasons this field is blank by default; pass in a value via +options+ if this is not desired.
#
# ==== Examples
# password_field(:login, :pass, :size => 20)
diff --git a/actionpack/lib/action_view/lookup_context.rb b/actionpack/lib/action_view/lookup_context.rb
index af367ac28d..76f4dea7b8 100644
--- a/actionpack/lib/action_view/lookup_context.rb
+++ b/actionpack/lib/action_view/lookup_context.rb
@@ -149,7 +149,7 @@ module ActionView
# as well as incorrectly putting part of the path in the template
# name instead of the prefix.
def normalize_name(name, prefixes) #:nodoc:
- prefixes = nil if prefixes.blank?
+ prefixes = prefixes.presence
parts = name.to_s.split('/')
parts.shift if parts.first.empty?
name = parts.pop
diff --git a/actionpack/lib/action_view/template/handlers.rb b/actionpack/lib/action_view/template/handlers.rb
index 52e032bbd8..d9cddc0040 100644
--- a/actionpack/lib/action_view/template/handlers.rb
+++ b/actionpack/lib/action_view/template/handlers.rb
@@ -10,7 +10,7 @@ module ActionView #:nodoc:
base.register_default_template_handler :erb, ERB.new
base.register_template_handler :builder, Builder.new
base.register_template_handler :raw, Raw.new
- base.register_template_handler :rb, :source.to_proc
+ base.register_template_handler :ruby, :source.to_proc
end
@@template_handlers = {}
@@ -21,11 +21,14 @@ module ActionView #:nodoc:
end
# Register an object that knows how to handle template files with the given
- # extension. This can be used to implement new template types.
+ # extensions. This can be used to implement new template types.
# The handler must respond to `:call`, which will be passed the template
# and should return the rendered template as a String.
- def register_template_handler(extension, handler)
- @@template_handlers[extension.to_sym] = handler
+ def register_template_handler(*extensions, handler)
+ raise(ArgumentError, "Extension is required") if extensions.empty?
+ extensions.each do |extension|
+ @@template_handlers[extension.to_sym] = handler
+ end
@@template_extensions = nil
end
diff --git a/actionpack/test/controller/log_subscriber_test.rb b/actionpack/test/controller/log_subscriber_test.rb
index 700fd788fa..a72b6dde1a 100644
--- a/actionpack/test/controller/log_subscriber_test.rb
+++ b/actionpack/test/controller/log_subscriber_test.rb
@@ -54,6 +54,10 @@ module Another
def with_rescued_exception
raise SpecialException
end
+
+ def with_action_not_found
+ raise AbstractController::ActionNotFound
+ end
end
end
@@ -225,6 +229,17 @@ class ACLogSubscriberTest < ActionController::TestCase
assert_match(/Completed 406/, logs.last)
end
+ def test_process_action_with_with_action_not_found_logs_404
+ begin
+ get :with_action_not_found
+ wait
+ rescue AbstractController::ActionNotFound
+ end
+
+ assert_equal 2, logs.size
+ assert_match(/Completed 404/, logs.last)
+ end
+
def logs
@logs ||= @logger.logged(:info)
end
diff --git a/actionpack/test/controller/parameters/nested_parameters_test.rb b/actionpack/test/controller/parameters/nested_parameters_test.rb
new file mode 100644
index 0000000000..41f5b6e127
--- /dev/null
+++ b/actionpack/test/controller/parameters/nested_parameters_test.rb
@@ -0,0 +1,113 @@
+require 'abstract_unit'
+require 'action_controller/metal/strong_parameters'
+
+class NestedParametersTest < ActiveSupport::TestCase
+ test "permitted nested parameters" do
+ params = ActionController::Parameters.new({
+ book: {
+ title: "Romeo and Juliet",
+ authors: [{
+ name: "William Shakespeare",
+ born: "1564-04-26"
+ }, {
+ name: "Christopher Marlowe"
+ }],
+ details: {
+ pages: 200,
+ genre: "Tragedy"
+ }
+ },
+ magazine: "Mjallo!"
+ })
+
+ permitted = params.permit book: [ :title, { authors: [ :name ] }, { details: :pages } ]
+
+ assert permitted.permitted?
+ assert_equal "Romeo and Juliet", permitted[:book][:title]
+ assert_equal "William Shakespeare", permitted[:book][:authors][0][:name]
+ assert_equal "Christopher Marlowe", permitted[:book][:authors][1][:name]
+ assert_equal 200, permitted[:book][:details][:pages]
+ assert_nil permitted[:book][:details][:genre]
+ assert_nil permitted[:book][:authors][0][:born]
+ assert_nil permitted[:magazine]
+ end
+
+ test "nested arrays with strings" do
+ params = ActionController::Parameters.new({
+ :book => {
+ :genres => ["Tragedy"]
+ }
+ })
+
+ permitted = params.permit :book => :genres
+ assert_equal ["Tragedy"], permitted[:book][:genres]
+ end
+
+ test "permit may specify symbols or strings" do
+ params = ActionController::Parameters.new({
+ :book => {
+ :title => "Romeo and Juliet",
+ :author => "William Shakespeare"
+ },
+ :magazine => "Shakespeare Today"
+ })
+
+ permitted = params.permit({:book => ["title", :author]}, "magazine")
+ assert_equal "Romeo and Juliet", permitted[:book][:title]
+ assert_equal "William Shakespeare", permitted[:book][:author]
+ assert_equal "Shakespeare Today", permitted[:magazine]
+ end
+
+ test "nested array with strings that should be hashes" do
+ params = ActionController::Parameters.new({
+ book: {
+ genres: ["Tragedy"]
+ }
+ })
+
+ permitted = params.permit book: { genres: :type }
+ assert_empty permitted[:book][:genres]
+ end
+
+ test "nested array with strings that should be hashes and additional values" do
+ params = ActionController::Parameters.new({
+ book: {
+ title: "Romeo and Juliet",
+ genres: ["Tragedy"]
+ }
+ })
+
+ permitted = params.permit book: [ :title, { genres: :type } ]
+ assert_equal "Romeo and Juliet", permitted[:book][:title]
+ assert_empty permitted[:book][:genres]
+ end
+
+ test "nested string that should be a hash" do
+ params = ActionController::Parameters.new({
+ book: {
+ genre: "Tragedy"
+ }
+ })
+
+ permitted = params.permit book: { genre: :type }
+ assert_nil permitted[:book][:genre]
+ end
+
+ test "fields_for-style nested params" do
+ params = ActionController::Parameters.new({
+ book: {
+ authors_attributes: {
+ :'0' => { name: 'William Shakespeare', age_of_death: '52' },
+ :'-1' => { name: 'Unattributed Assistant' }
+ }
+ }
+ })
+ permitted = params.permit book: { authors_attributes: [ :name ] }
+
+ assert_not_nil permitted[:book][:authors_attributes]['0']
+ assert_not_nil permitted[:book][:authors_attributes]['-1']
+ assert_nil permitted[:book][:authors_attributes]['0'][:age_of_death]
+ assert_equal 'William Shakespeare', permitted[:book][:authors_attributes]['0'][:name]
+ assert_equal 'Unattributed Assistant', permitted[:book][:authors_attributes]['-1'][:name]
+ end
+end
diff --git a/actionpack/test/controller/parameters/parameters_permit_test.rb b/actionpack/test/controller/parameters/parameters_permit_test.rb
new file mode 100644
index 0000000000..7fe8e6051b
--- /dev/null
+++ b/actionpack/test/controller/parameters/parameters_permit_test.rb
@@ -0,0 +1,73 @@
+require 'abstract_unit'
+require 'action_controller/metal/strong_parameters'
+
+class ParametersPermitTest < ActiveSupport::TestCase
+ setup do
+ @params = ActionController::Parameters.new({ person: {
+ age: "32", name: { first: "David", last: "Heinemeier Hansson" }
+ }})
+ end
+
+ test "fetch raises ParameterMissing exception" do
+ e = assert_raises(ActionController::ParameterMissing) do
+ @params.fetch :foo
+ end
+ assert_equal :foo, e.param
+ end
+
+ test "fetch doesnt raise ParameterMissing exception if there is a default" do
+ assert_equal "monkey", @params.fetch(:foo, "monkey")
+ assert_equal "monkey", @params.fetch(:foo) { "monkey" }
+ end
+
+ test "permitted is sticky on accessors" do
+ assert !@params.slice(:person).permitted?
+ assert !@params[:person][:name].permitted?
+
+ @params.each { |key, value| assert(value.permitted?) if key == :person }
+
+ assert !@params.fetch(:person).permitted?
+
+ assert !@params.values_at(:person).first.permitted?
+ end
+
+ test "permitted is sticky on mutators" do
+ assert !@params.delete_if { |k| k == :person }.permitted?
+ assert !@params.keep_if { |k,v| k == :person }.permitted?
+ end
+
+ test "permitted is sticky beyond merges" do
+ assert !@params.merge(a: "b").permitted?
+ end
+
+ test "modifying the parameters" do
+ @params[:person][:hometown] = "Chicago"
+ @params[:person][:family] = { brother: "Jonas" }
+
+ assert_equal "Chicago", @params[:person][:hometown]
+ assert_equal "Jonas", @params[:person][:family][:brother]
+ end
+
+ test "permitting parameters that are not there should not include the keys" do
+ assert !@params.permit(:person, :funky).has_key?(:funky)
+ end
+
+ test "permit state is kept on a dup" do
+ @params.permit!
+ assert_equal @params.permitted?, @params.dup.permitted?
+ end
+
+ test "permitted takes a default value when Parameters.permit_all_parameters is set" do
+ begin
+ ActionController::Parameters.permit_all_parameters = true
+ params = ActionController::Parameters.new({ person: {
+ age: "32", name: { first: "David", last: "Heinemeier Hansson" }
+ }})
+
+ assert params.slice(:person).permitted?
+ assert params[:person][:name].permitted?
+ ensure
+ ActionController::Parameters.permit_all_parameters = false
+ end
+ end
+end
diff --git a/actionpack/test/controller/parameters/parameters_require_test.rb b/actionpack/test/controller/parameters/parameters_require_test.rb
new file mode 100644
index 0000000000..bdaba8d2d8
--- /dev/null
+++ b/actionpack/test/controller/parameters/parameters_require_test.rb
@@ -0,0 +1,10 @@
+require 'abstract_unit'
+require 'action_controller/metal/strong_parameters'
+
+class ParametersRequireTest < ActiveSupport::TestCase
+ test "required parameters must be present not merely not nil" do
+ assert_raises(ActionController::ParameterMissing) do
+ ActionController::Parameters.new(person: {}).require(:person)
+ end
+ end
+end
diff --git a/actionpack/test/controller/params_wrapper_test.rb b/actionpack/test/controller/params_wrapper_test.rb
index 5b05f77045..209f021cf7 100644
--- a/actionpack/test/controller/params_wrapper_test.rb
+++ b/actionpack/test/controller/params_wrapper_test.rb
@@ -155,7 +155,6 @@ class ParamsWrapperTest < ActionController::TestCase
end
def test_derived_wrapped_keys_from_matching_model
- User.expects(:respond_to?).with(:accessible_attributes).returns(false)
User.expects(:respond_to?).with(:attribute_names).returns(true)
User.expects(:attribute_names).twice.returns(["username"])
@@ -168,7 +167,6 @@ class ParamsWrapperTest < ActionController::TestCase
def test_derived_wrapped_keys_from_specified_model
with_default_wrapper_options do
- Person.expects(:respond_to?).with(:accessible_attributes).returns(false)
Person.expects(:respond_to?).with(:attribute_names).returns(true)
Person.expects(:attribute_names).twice.returns(["username"])
@@ -179,46 +177,8 @@ class ParamsWrapperTest < ActionController::TestCase
assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }})
end
end
-
- def test_accessible_wrapped_keys_from_matching_model
- User.expects(:respond_to?).with(:accessible_attributes).returns(true)
- User.expects(:accessible_attributes).with(:default).twice.returns(["username"])
-
- with_default_wrapper_options do
- @request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
- assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'user' => { 'username' => 'sikachu' }})
- end
- end
-
- def test_accessible_wrapped_keys_from_specified_model
- with_default_wrapper_options do
- Person.expects(:respond_to?).with(:accessible_attributes).returns(true)
- Person.expects(:accessible_attributes).with(:default).twice.returns(["username"])
-
- UsersController.wrap_parameters Person
-
- @request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
- assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }})
- end
- end
-
- def test_accessible_wrapped_keys_with_role_from_specified_model
- with_default_wrapper_options do
- Person.expects(:respond_to?).with(:accessible_attributes).returns(true)
- Person.expects(:accessible_attributes).with(:admin).twice.returns(["username"])
-
- UsersController.wrap_parameters Person, :as => :admin
-
- @request.env['CONTENT_TYPE'] = 'application/json'
- post :parse, { 'username' => 'sikachu', 'title' => 'Developer' }
- assert_parameters({ 'username' => 'sikachu', 'title' => 'Developer', 'person' => { 'username' => 'sikachu' }})
- end
- end
def test_not_wrapping_abstract_model
- User.expects(:respond_to?).with(:accessible_attributes).returns(false)
User.expects(:respond_to?).with(:attribute_names).returns(true)
User.expects(:attribute_names).returns([])
diff --git a/actionpack/test/controller/permitted_params_test.rb b/actionpack/test/controller/permitted_params_test.rb
new file mode 100644
index 0000000000..f46249d712
--- /dev/null
+++ b/actionpack/test/controller/permitted_params_test.rb
@@ -0,0 +1,25 @@
+require 'abstract_unit'
+
+class PeopleController < ActionController::Base
+ def create
+ render text: params[:person].permitted? ? "permitted" : "forbidden"
+ end
+
+ def create_with_permit
+ render text: params[:person].permit(:name).permitted? ? "permitted" : "forbidden"
+ end
+end
+
+class ActionControllerPermittedParamsTest < ActionController::TestCase
+ tests PeopleController
+
+ test "parameters are forbidden" do
+ post :create, { person: { name: "Mjallo!" } }
+ assert_equal "forbidden", response.body
+ end
+
+ test "parameters can be permitted and are then not forbidden" do
+ post :create_with_permit, { person: { name: "Mjallo!" } }
+ assert_equal "permitted", response.body
+ end
+end
diff --git a/actionpack/test/controller/request_forgery_protection_test.rb b/actionpack/test/controller/request_forgery_protection_test.rb
index 0289f4070b..1f637eb791 100644
--- a/actionpack/test/controller/request_forgery_protection_test.rb
+++ b/actionpack/test/controller/request_forgery_protection_test.rb
@@ -56,22 +56,18 @@ module RequestForgeryProtectionActions
end
# sample controllers
-class RequestForgeryProtectionController < ActionController::Base
+class RequestForgeryProtectionControllerUsingResetSession < ActionController::Base
include RequestForgeryProtectionActions
- protect_from_forgery :only => %w(index meta)
+ protect_from_forgery :only => %w(index meta), :with => :reset_session
end
class RequestForgeryProtectionControllerUsingException < ActionController::Base
include RequestForgeryProtectionActions
- protect_from_forgery :only => %w(index meta)
-
- def handle_unverified_request
- raise(ActionController::InvalidAuthenticityToken)
- end
+ protect_from_forgery :only => %w(index meta), :with => :exception
end
-class FreeCookieController < RequestForgeryProtectionController
+class FreeCookieController < RequestForgeryProtectionControllerUsingResetSession
self.allow_forgery_protection = false
def index
@@ -83,7 +79,7 @@ class FreeCookieController < RequestForgeryProtectionController
end
end
-class CustomAuthenticityParamController < RequestForgeryProtectionController
+class CustomAuthenticityParamController < RequestForgeryProtectionControllerUsingResetSession
def form_authenticity_param
'foobar'
end
@@ -268,7 +264,7 @@ end
# OK let's get our test on
-class RequestForgeryProtectionControllerTest < ActionController::TestCase
+class RequestForgeryProtectionControllerUsingResetSessionTest < ActionController::TestCase
include RequestForgeryProtectionTests
setup do
diff --git a/actionpack/test/controller/required_params_test.rb b/actionpack/test/controller/required_params_test.rb
new file mode 100644
index 0000000000..661bcb3945
--- /dev/null
+++ b/actionpack/test/controller/required_params_test.rb
@@ -0,0 +1,30 @@
+require 'abstract_unit'
+
+class BooksController < ActionController::Base
+ def create
+ params.require(:book).require(:name)
+ head :ok
+ end
+end
+
+class ActionControllerRequiredParamsTest < ActionController::TestCase
+ tests BooksController
+
+ test "missing required parameters will raise exception" do
+ post :create, { magazine: { name: "Mjallo!" } }
+ assert_response :bad_request
+
+ post :create, { book: { title: "Mjallo!" } }
+ assert_response :bad_request
+ end
+
+ test "required parameters that are present will not raise" do
+ post :create, { book: { name: "Mjallo!" } }
+ assert_response :ok
+ end
+
+ test "missing parameters will be mentioned in the return" do
+ post :create, { magazine: { name: "Mjallo!" } }
+ assert_equal "Required parameter missing: book", response.body
+ end
+end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index a434e49dbd..a2b9571660 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -746,6 +746,45 @@ class RequestTest < ActiveSupport::TestCase
assert_equal "/foo?bar", path
end
+ test "if_none_match_etags none" do
+ request = stub_request
+
+ assert_equal nil, request.if_none_match
+ assert_equal [], request.if_none_match_etags
+ assert !request.etag_matches?("foo")
+ assert !request.etag_matches?(nil)
+ end
+
+ test "if_none_match_etags single" do
+ header = 'the-etag'
+ request = stub_request('HTTP_IF_NONE_MATCH' => header)
+
+ assert_equal header, request.if_none_match
+ assert_equal [header], request.if_none_match_etags
+ assert request.etag_matches?("the-etag")
+ end
+
+ test "if_none_match_etags quoted single" do
+ header = '"the-etag"'
+ request = stub_request('HTTP_IF_NONE_MATCH' => header)
+
+ assert_equal header, request.if_none_match
+ assert_equal ['the-etag'], request.if_none_match_etags
+ assert request.etag_matches?("the-etag")
+ end
+
+ test "if_none_match_etags multiple" do
+ header = 'etag1, etag2, "third etag", "etag4"'
+ expected = ['etag1', 'etag2', 'third etag', 'etag4']
+ request = stub_request('HTTP_IF_NONE_MATCH' => header)
+
+ assert_equal header, request.if_none_match
+ assert_equal expected, request.if_none_match_etags
+ expected.each do |etag|
+ assert request.etag_matches?(etag), etag
+ end
+ end
+
protected
def stub_request(env = {})
diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb
index 856248e2ac..4e83ad16d7 100644
--- a/actionpack/test/dispatch/routing_test.rb
+++ b/actionpack/test/dispatch/routing_test.rb
@@ -363,6 +363,7 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
resources :errors, :shallow => true do
resources :notices
end
+ get 'api/version'
end
scope :path => 'api' do
@@ -1280,6 +1281,12 @@ class TestRoutingMapper < ActionDispatch::IntegrationTest
assert_equal 'account#shorthand', @response.body
end
+ def test_match_shorthand_with_module
+ assert_equal '/api/version', api_version_path
+ get '/api/version'
+ assert_equal 'api/api#version', @response.body
+ end
+
def test_dynamically_generated_helpers_on_collection_do_not_clobber_resources_url_helper
assert_equal '/replies', replies_path
end
diff --git a/actionpack/test/fixtures/company.rb b/actionpack/test/fixtures/company.rb
index e29978801e..f3ac3642fa 100644
--- a/actionpack/test/fixtures/company.rb
+++ b/actionpack/test/fixtures/company.rb
@@ -1,6 +1,5 @@
class Company < ActiveRecord::Base
has_one :mascot
- attr_protected :rating
self.sequence_name = :companies_nonstd_seq
validates_presence_of :name
diff --git a/actionpack/test/fixtures/ruby_template.rb b/actionpack/test/fixtures/ruby_template.ruby
index 5097bce47c..5097bce47c 100644
--- a/actionpack/test/fixtures/ruby_template.rb
+++ b/actionpack/test/fixtures/ruby_template.ruby
diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb
index 6279abaae5..ddf5c6a1b3 100644
--- a/actionpack/test/template/render_test.rb
+++ b/actionpack/test/template/render_test.rb
@@ -97,12 +97,12 @@ module RenderTestCases
assert_equal %q;Here are some characters: !@#$%^&*()-="'}{`; + "\n", @view.render(:template => "plain_text_with_characters")
end
- def test_render_rb_template_with_handlers
+ def test_render_ruby_template_with_handlers
assert_equal "Hello from Ruby code", @view.render(:template => "ruby_template")
end
- def test_render_rb_template_inline
- assert_equal '4', @view.render(:inline => "(2**2).to_s", :type => :rb)
+ def test_render_ruby_template_inline
+ assert_equal '4', @view.render(:inline => "(2**2).to_s", :type => :ruby)
end
def test_render_file_with_localization_on_context_level
@@ -451,6 +451,15 @@ module RenderTestCases
assert_equal %(<title>David</title>),
@view.render(:file => "test/layout_render_object")
end
+
+ def test_render_with_passing_couple_extensions_to_one_register_template_handler_function_call
+ ActionView::Template.register_template_handler :foo1, :foo2, CustomHandler
+ assert_equal @view.render(:inline => "Hello, World!", :type => :foo1), @view.render(:inline => "Hello, World!", :type => :foo2)
+ end
+
+ def test_render_throws_exception_when_no_extensions_passed_to_register_template_handler_function_call
+ assert_raises(ArgumentError) { ActionView::Template.register_template_handler CustomHandler }
+ end
end
class CachedViewRenderTest < ActiveSupport::TestCase