diff options
52 files changed, 378 insertions, 89 deletions
diff --git a/actionmailer/bin/test b/actionmailer/bin/test index 404cabba51..84a05bba08 100755 --- a/actionmailer/bin/test +++ b/actionmailer/bin/test @@ -1,4 +1,6 @@ #!/usr/bin/env ruby -COMPONENT_ROOT = File.expand_path("../../", __FILE__) + +COMPONENT_ROOT = File.expand_path("..", __dir__) require File.expand_path("../tools/test", COMPONENT_ROOT) + exit Minitest.run(ARGV) diff --git a/actionpack/bin/test b/actionpack/bin/test index 404cabba51..84a05bba08 100755 --- a/actionpack/bin/test +++ b/actionpack/bin/test @@ -1,4 +1,6 @@ #!/usr/bin/env ruby -COMPONENT_ROOT = File.expand_path("../../", __FILE__) + +COMPONENT_ROOT = File.expand_path("..", __dir__) require File.expand_path("../tools/test", COMPONENT_ROOT) + exit Minitest.run(ARGV) diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index b9ef11a7e0..fc86a907b3 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -40,6 +40,7 @@ module ActionController autoload :Rescue autoload :Streaming autoload :StrongParameters + autoload :ParameterEncoding autoload :Testing autoload :UrlFor end diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 2c7a223971..68a526eccb 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -217,7 +217,7 @@ module ActionController MimeResponds, ImplicitRender, StrongParameters, - + ParameterEncoding, Cookies, Flash, FormBuilder, diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 0364500944..075e4504c2 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -139,6 +139,10 @@ module ActionController end end + def self.encoding_for_param(action, param) # :nodoc: + ::Encoding::UTF_8 + end + # Delegates to the class' <tt>controller_name</tt> def controller_name self.class.controller_name diff --git a/actionpack/lib/action_controller/metal/parameter_encoding.rb b/actionpack/lib/action_controller/metal/parameter_encoding.rb new file mode 100644 index 0000000000..a278c5d011 --- /dev/null +++ b/actionpack/lib/action_controller/metal/parameter_encoding.rb @@ -0,0 +1,30 @@ +module ActionController + # Allows encoding to be specified per parameter per action. + module ParameterEncoding + extend ActiveSupport::Concern + + module ClassMethods + def inherited(klass) # :nodoc: + super + klass.setup_param_encode + end + + def setup_param_encode # :nodoc: + @_parameter_encodings = {} + end + + def encoding_for_param(action, param) # :nodoc: + if @_parameter_encodings[action.to_s] && @_parameter_encodings[action.to_s][param.to_s] + @_parameter_encodings[action.to_s][param.to_s] + else + ::Encoding::UTF_8 + end + end + + def parameter_encoding(action, param_name, encoding) + @_parameter_encodings[action.to_s] ||= {} + @_parameter_encodings[action.to_s][param_name.to_s] = encoding + end + end + end +end diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index ccedecd6f4..3a6bc92b3c 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -33,12 +33,14 @@ module ActionController TestSession.new end + attr_reader :controller_class + # Create a new test request with default `env` values - def self.create + def self.create(controller_class) env = {} env = Rails.application.env_config.merge(env) if defined?(Rails.application) && Rails.application env["rack.request.cookie_hash"] = {}.with_indifferent_access - new(default_env.merge(env), new_session) + new(default_env.merge(env), new_session, controller_class) end def self.default_env @@ -46,11 +48,12 @@ module ActionController end private_class_method :default_env - def initialize(env, session) + def initialize(env, session, controller_class) super(env) self.session = session self.session_options = TestSession::DEFAULT_OPTIONS + @controller_class = controller_class @custom_param_parsers = { xml: lambda { |raw_post| Hash.from_xml(raw_post)["hash"] } } @@ -497,7 +500,7 @@ module ActionController @request.set_header "HTTP_COOKIE", cookies.to_header @request.delete_header "action_dispatch.cookies" - @request = TestRequest.new scrub_env!(@request.env), @request.session + @request = TestRequest.new scrub_env!(@request.env), @request.session, @controller.class @response = build_response @response_klass @response.request = @request @controller.recycle! @@ -591,7 +594,7 @@ module ActionController end end - @request = TestRequest.create + @request = TestRequest.create(@controller.class) @response = build_response @response_klass @response.request = @request @@ -668,11 +671,6 @@ module ActionController end end end - - def html_format?(parameters) - return true unless parameters.key?(:format) - Mime.fetch(parameters[:format]) { Mime["html"] }.html? - end end include Behavior diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb index 91c45767ef..d5eef2987d 100644 --- a/actionpack/lib/action_dispatch/http/headers.rb +++ b/actionpack/lib/action_dispatch/http/headers.rb @@ -3,7 +3,7 @@ module ActionDispatch # Provides access to the request's HTTP headers from the environment. # # env = { "CONTENT_TYPE" => "text/plain", "HTTP_USER_AGENT" => "curl/7.43.0" } - # headers = ActionDispatch::Http::Headers.new(env) + # headers = ActionDispatch::Http::Headers.from_hash(env) # headers["Content-Type"] # => "text/plain" # headers["User-Agent"] # => "curl/7.43.0" # diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index f25e50f9f3..42e80b9bf5 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -37,6 +37,7 @@ module ActionDispatch query_parameters.dup end params.merge!(path_parameters) + params = set_custom_encoding(params) set_header("action_dispatch.request.parameters", params) params end @@ -64,6 +65,21 @@ module ActionDispatch private + def set_custom_encoding(params) + action = params[:action] + params.each do |k, v| + if v.is_a?(String) && v.encoding != encoding_template(action, k) + params[k] = v.force_encoding(encoding_template(action, k)) + end + end + + params + end + + def encoding_template(action, param) + controller_class.encoding_for_param(action, param) + end + def parse_formatted_parameters(parsers) return yield if content_length.zero? || content_mime_type.nil? diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index 46409a325e..e7cc6d5f31 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -69,6 +69,7 @@ module ActionDispatch PASS_NOT_FOUND = Class.new { # :nodoc: def self.action(_); self; end def self.call(_); [404, {"X-Cascade" => "pass"}, []]; end + def self.encoding_for_param(action, param); ::Encoding::UTF_8; end } def controller_class diff --git a/actionpack/lib/action_dispatch/testing/assertions/routing.rb b/actionpack/lib/action_dispatch/testing/assertions/routing.rb index cba67b2839..2ea4a6c130 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/routing.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/routing.rb @@ -184,7 +184,7 @@ module ActionDispatch end # Assume given controller - request = ActionController::TestRequest.create + request = ActionController::TestRequest.create @controller.class if path =~ %r{://} fail_on(URI::InvalidURIError, msg) do diff --git a/actionpack/test/abstract/translation_test.rb b/actionpack/test/abstract/translation_test.rb index 1e17cb9777..0c4071df8d 100644 --- a/actionpack/test/abstract/translation_test.rb +++ b/actionpack/test/abstract/translation_test.rb @@ -9,7 +9,8 @@ module AbstractController class TranslationControllerTest < ActiveSupport::TestCase def setup @controller = TranslationController.new - I18n.backend.store_translations(:en, one: { + I18n.backend.store_translations(:en, + one: { two: "bar", }, abstract_controller: { diff --git a/actionpack/test/abstract_unit.rb b/actionpack/test/abstract_unit.rb index 0c77de3c16..7bff21b5a2 100644 --- a/actionpack/test/abstract_unit.rb +++ b/actionpack/test/abstract_unit.rb @@ -122,27 +122,19 @@ class ActionDispatch::IntegrationTest < ActiveSupport::TestCase # Stub Rails dispatcher so it does not get controller references and # simply return the controller#action as Rack::Body. class NullController < ::ActionController::Metal - def initialize(controller_name) - @controller = controller_name - end - - def make_response!(request) - self.class.make_response! request - end - - def dispatch(action, req, res) - [200, {"Content-Type" => "text/html"}, ["#{@controller}##{action}"]] + def self.dispatch(action, req, res) + [200, {"Content-Type" => "text/html"}, ["#{req.params[:controller]}##{action}"]] end end - class NullControllerRequest < DelegateClass(ActionDispatch::Request) + class NullControllerRequest < ActionDispatch::Request def controller_class - NullController.new params[:controller] + NullController end end def make_request(env) - NullControllerRequest.new super + NullControllerRequest.new env end end diff --git a/actionpack/test/controller/new_base/render_action_test.rb b/actionpack/test/controller/new_base/render_action_test.rb index e88f83b594..4b59a3d676 100644 --- a/actionpack/test/controller/new_base/render_action_test.rb +++ b/actionpack/test/controller/new_base/render_action_test.rb @@ -258,7 +258,8 @@ end module RenderActionWithBothLayouts class BasicController < ActionController::Base - self.view_paths = [ActionView::FixtureResolver.new( "render_action_with_both_layouts/basic/hello_world.html.erb" => "Hello World!", + self.view_paths = [ActionView::FixtureResolver.new( + "render_action_with_both_layouts/basic/hello_world.html.erb" => "Hello World!", "layouts/application.html.erb" => "Oh Hi <%= yield %> Bye", "layouts/render_action_with_both_layouts/basic.html.erb" => "With Controller Layout! <%= yield %> Bye")] diff --git a/actionpack/test/controller/parameter_encoding_test.rb b/actionpack/test/controller/parameter_encoding_test.rb new file mode 100644 index 0000000000..69a72c000b --- /dev/null +++ b/actionpack/test/controller/parameter_encoding_test.rb @@ -0,0 +1,73 @@ +require "abstract_unit" + +class ParameterEncodingController < ActionController::Base + parameter_encoding :test_bar, :bar, Encoding::ASCII_8BIT + parameter_encoding :test_baz, :baz, Encoding::ISO_8859_1 + parameter_encoding :test_baz_to_ascii, :baz, Encoding::ASCII_8BIT + + def test_foo + render body: params[:foo].encoding + end + + def test_bar + render body: params[:bar].encoding + end + + def test_baz + render body: params[:baz].encoding + end + + def test_no_change_to_baz + render body: params[:baz].encoding + end + + def test_baz_to_ascii + render body: params[:baz].encoding + end +end + +class ParameterEncodingTest < ActionController::TestCase + tests ParameterEncodingController + + test "properly transcodes UTF8 parameters into declared encodings" do + post :test_foo, params: {"foo" => "foo", "bar" => "bar", "baz" => "baz"} + + assert_response :success + assert_equal "UTF-8", @response.body + end + + test "properly transcodes ASCII_8BIT parameters into declared encodings" do + post :test_bar, params: {"foo" => "foo", "bar" => "bar", "baz" => "baz"} + + assert_response :success + assert_equal "ASCII-8BIT", @response.body + end + + test "properly transcodes ISO_8859_1 parameters into declared encodings" do + post :test_baz, params: {"foo" => "foo", "bar" => "bar", "baz" => "baz"} + + assert_response :success + assert_equal "ISO-8859-1", @response.body + end + + test "does not transcode parameters when not specified" do + post :test_no_change_to_baz, params: {"foo" => "foo", "bar" => "bar", "baz" => "baz"} + + assert_response :success + assert_equal "UTF-8", @response.body + end + + test "respects different encoding declarations for a param per action" do + post :test_baz_to_ascii, params: {"foo" => "foo", "bar" => "bar", "baz" => "baz"} + + assert_response :success + assert_equal "ASCII-8BIT", @response.body + end + + test "does not raise an error when passed a param declared as ASCII-8BIT that contains invalid bytes" do + get :test_bar, params: { "bar" => URI.parser.escape("bar\xE2baz".b) } + + assert_response :success + assert_equal "ASCII-8BIT", @response.body + end +end diff --git a/actionpack/test/controller/parameters/always_permitted_parameters_test.rb b/actionpack/test/controller/parameters/always_permitted_parameters_test.rb index 9c9749c037..cd7c98f112 100644 --- a/actionpack/test/controller/parameters/always_permitted_parameters_test.rb +++ b/actionpack/test/controller/parameters/always_permitted_parameters_test.rb @@ -19,7 +19,8 @@ class AlwaysPermittedParametersTest < ActiveSupport::TestCase end test "permits parameters that are whitelisted" do - params = ActionController::Parameters.new( book: { pages: 65 }, + params = ActionController::Parameters.new( + book: { pages: 65 }, format: "json") permitted = params.permit book: [:pages] assert permitted.permitted? diff --git a/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb b/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb index bf2c3d1ed2..0358fd9976 100644 --- a/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb +++ b/actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb @@ -11,7 +11,8 @@ class LogOnUnpermittedParamsTest < ActiveSupport::TestCase end test "logs on unexpected param" do - params = ActionController::Parameters.new( book: { pages: 65 }, + params = ActionController::Parameters.new( + book: { pages: 65 }, fishing: "Turnips") assert_logged("Unpermitted parameter: fishing") do @@ -20,7 +21,8 @@ class LogOnUnpermittedParamsTest < ActiveSupport::TestCase end test "logs on unexpected params" do - params = ActionController::Parameters.new( book: { pages: 65 }, + params = ActionController::Parameters.new( + book: { pages: 65 }, fishing: "Turnips", car: "Mersedes") @@ -30,7 +32,8 @@ class LogOnUnpermittedParamsTest < ActiveSupport::TestCase end test "logs on unexpected nested param" do - params = ActionController::Parameters.new( book: { pages: 65, title: "Green Cats and where to find then." }) + params = ActionController::Parameters.new( + book: { pages: 65, title: "Green Cats and where to find then." }) assert_logged("Unpermitted parameter: title") do params.permit(book: [:pages]) @@ -38,7 +41,8 @@ class LogOnUnpermittedParamsTest < ActiveSupport::TestCase end test "logs on unexpected nested params" do - params = ActionController::Parameters.new( book: { pages: 65, title: "Green Cats and where to find then.", author: "G. A. Dog" }) + params = ActionController::Parameters.new( + book: { pages: 65, title: "Green Cats and where to find then.", author: "G. A. Dog" }) assert_logged("Unpermitted parameters: title, author") do params.permit(book: [:pages]) diff --git a/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb b/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb index 44e39135a2..88fb477c10 100644 --- a/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb +++ b/actionpack/test/controller/parameters/multi_parameter_attributes_test.rb @@ -3,7 +3,8 @@ require "action_controller/metal/strong_parameters" class MultiParameterAttributesTest < ActiveSupport::TestCase test "permitted multi-parameter attribute keys" do - params = ActionController::Parameters.new( book: { + params = ActionController::Parameters.new( + book: { "shipped_at(1i)" => "2012", "shipped_at(2i)" => "3", "shipped_at(3i)" => "25", diff --git a/actionpack/test/controller/parameters/nested_parameters_permit_test.rb b/actionpack/test/controller/parameters/nested_parameters_permit_test.rb index e3f1ba5f0a..f0155477c4 100644 --- a/actionpack/test/controller/parameters/nested_parameters_permit_test.rb +++ b/actionpack/test/controller/parameters/nested_parameters_permit_test.rb @@ -7,7 +7,8 @@ class NestedParametersPermitTest < ActiveSupport::TestCase end test "permitted nested parameters" do - params = ActionController::Parameters.new( book: { + params = ActionController::Parameters.new( + book: { title: "Romeo and Juliet", authors: [{ name: "William Shakespeare", @@ -43,7 +44,8 @@ class NestedParametersPermitTest < ActiveSupport::TestCase end test "permitted nested parameters with a string or a symbol as a key" do - params = ActionController::Parameters.new( book: { + params = ActionController::Parameters.new( + book: { "authors" => [ { name: "William Shakespeare", born: "1564-04-26" }, { name: "Christopher Marlowe" } @@ -66,7 +68,8 @@ class NestedParametersPermitTest < ActiveSupport::TestCase end test "nested arrays with strings" do - params = ActionController::Parameters.new( book: { + params = ActionController::Parameters.new( + book: { genres: ["Tragedy"] }) @@ -75,7 +78,8 @@ class NestedParametersPermitTest < ActiveSupport::TestCase end test "permit may specify symbols or strings" do - params = ActionController::Parameters.new( book: { + params = ActionController::Parameters.new( + book: { title: "Romeo and Juliet", author: "William Shakespeare" }, @@ -88,7 +92,8 @@ class NestedParametersPermitTest < ActiveSupport::TestCase end test "nested array with strings that should be hashes" do - params = ActionController::Parameters.new( book: { + params = ActionController::Parameters.new( + book: { genres: ["Tragedy"] }) @@ -97,7 +102,8 @@ class NestedParametersPermitTest < ActiveSupport::TestCase end test "nested array with strings that should be hashes and additional values" do - params = ActionController::Parameters.new( book: { + params = ActionController::Parameters.new( + book: { title: "Romeo and Juliet", genres: ["Tragedy"] }) @@ -108,7 +114,8 @@ class NestedParametersPermitTest < ActiveSupport::TestCase end test "nested string that should be a hash" do - params = ActionController::Parameters.new( book: { + params = ActionController::Parameters.new( + book: { genre: "Tragedy" }) @@ -117,7 +124,8 @@ class NestedParametersPermitTest < ActiveSupport::TestCase end test "fields_for-style nested params" do - params = ActionController::Parameters.new( book: { + params = ActionController::Parameters.new( + book: { authors_attributes: { '0': { name: "William Shakespeare", age_of_death: "52" }, '1': { name: "Unattributed Assistant" }, @@ -136,7 +144,8 @@ class NestedParametersPermitTest < ActiveSupport::TestCase end test "fields_for-style nested params with negative numbers" do - params = ActionController::Parameters.new( book: { + params = ActionController::Parameters.new( + book: { authors_attributes: { '-1': { name: "William Shakespeare", age_of_death: "52" }, '-2': { name: "Unattributed Assistant" } @@ -153,7 +162,8 @@ class NestedParametersPermitTest < ActiveSupport::TestCase end test "nested number as key" do - params = ActionController::Parameters.new( product: { + params = ActionController::Parameters.new( + product: { properties: { "0" => "prop0", "1" => "prop1" diff --git a/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb b/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb index bcb16eaf89..8fab7b28e9 100644 --- a/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb +++ b/actionpack/test/controller/parameters/raise_on_unpermitted_params_test.rb @@ -11,7 +11,8 @@ class RaiseOnUnpermittedParamsTest < ActiveSupport::TestCase end test "raises on unexpected params" do - params = ActionController::Parameters.new( book: { pages: 65 }, + params = ActionController::Parameters.new( + book: { pages: 65 }, fishing: "Turnips") assert_raises(ActionController::UnpermittedParameters) do @@ -20,7 +21,8 @@ class RaiseOnUnpermittedParamsTest < ActiveSupport::TestCase end test "raises on unexpected nested params" do - params = ActionController::Parameters.new( book: { pages: 65, title: "Green Cats and where to find then." }) + params = ActionController::Parameters.new( + book: { pages: 65, title: "Green Cats and where to find then." }) assert_raises(ActionController::UnpermittedParameters) do params.permit(book: [:pages]) diff --git a/actionpack/test/controller/routing_test.rb b/actionpack/test/controller/routing_test.rb index 05293dd94c..9f0e3bff15 100644 --- a/actionpack/test/controller/routing_test.rb +++ b/actionpack/test/controller/routing_test.rb @@ -29,7 +29,8 @@ class UriReservedCharactersRoutingTest < ActiveSupport::TestCase def test_route_generation_escapes_unsafe_path_characters assert_equal "/content/act#{@escaped}ion/var#{@escaped}iable/add#{@escaped}itional-1/add#{@escaped}itional-2", - url_for(@set, controller: "content", + url_for(@set, + controller: "content", action: "act#{@segment}ion", variable: "var#{@segment}iable", additional: ["add#{@segment}itional-1", "add#{@segment}itional-2"]) @@ -45,7 +46,8 @@ class UriReservedCharactersRoutingTest < ActiveSupport::TestCase def test_route_generation_allows_passing_non_string_values_to_generated_helper assert_equal "/content/action/variable/1/2", - url_for(@set, controller: "content", + url_for(@set, + controller: "content", action: "action", variable: "variable", additional: [1, 2]) @@ -776,7 +778,8 @@ class LegacyRouteSetTests < ActiveSupport::TestCase end end - assert_equal "/journal", url_for(rs, controller: "content", + assert_equal "/journal", url_for(rs, + controller: "content", action: "list_journal", date: nil, user_id: nil) diff --git a/actionpack/test/journey/route_test.rb b/actionpack/test/journey/route_test.rb index cce5c2ae37..886cf857e8 100644 --- a/actionpack/test/journey/route_test.rb +++ b/actionpack/test/journey/route_test.rb @@ -50,7 +50,8 @@ module ActionDispatch path = Path::Pattern.from_string "/:controller/*extra" route = Route.build("name", nil, path, {}, [], controller: "foo", action: "bar") - assert_equal "/foo/himom", route.format( controller: "foo", + assert_equal "/foo/himom", route.format( + controller: "foo", extra: "himom") end @@ -58,7 +59,8 @@ module ActionDispatch path = Path::Pattern.from_string "/:controller(/:action(/:id(.:format)))" route = Route.build("name", nil, path, {action: "bar"}, [], controller: "foo") - assert_equal "/foo/bar/10", route.format( controller: "foo", + assert_equal "/foo/bar/10", route.format( + controller: "foo", action: "bar", id: 10) end diff --git a/actionview/bin/test b/actionview/bin/test index 404cabba51..84a05bba08 100755 --- a/actionview/bin/test +++ b/actionview/bin/test @@ -1,4 +1,6 @@ #!/usr/bin/env ruby -COMPONENT_ROOT = File.expand_path("../../", __FILE__) + +COMPONENT_ROOT = File.expand_path("..", __dir__) require File.expand_path("../tools/test", COMPONENT_ROOT) + exit Minitest.run(ARGV) diff --git a/actionview/lib/action_view/helpers/tag_helper.rb b/actionview/lib/action_view/helpers/tag_helper.rb index 030d07845b..4950f272a4 100644 --- a/actionview/lib/action_view/helpers/tag_helper.rb +++ b/actionview/lib/action_view/helpers/tag_helper.rb @@ -88,9 +88,9 @@ module ActionView if value.is_a?(Array) value = escape ? safe_join(value, " ") : value.join(" ") else - value = escape ? ERB::Util.unwrapped_html_escape(value) : value + value = escape ? ERB::Util.unwrapped_html_escape(value) : value.to_s end - %(#{key}="#{value}") + %(#{key}="#{value.gsub(/"/, '"'.freeze)}") end private diff --git a/actionview/lib/action_view/test_case.rb b/actionview/lib/action_view/test_case.rb index 42fa17d1e0..2945aceb3e 100644 --- a/actionview/lib/action_view/test_case.rb +++ b/actionview/lib/action_view/test_case.rb @@ -24,7 +24,7 @@ module ActionView def initialize super self.class.controller_path = "" - @request = ActionController::TestRequest.create + @request = ActionController::TestRequest.create(self.class) @response = ActionDispatch::TestResponse.new @request.env.delete("PATH_INFO") diff --git a/actionview/test/actionpack/controller/view_paths_test.rb b/actionview/test/actionpack/controller/view_paths_test.rb index e676a2ecd4..9471c76921 100644 --- a/actionview/test/actionpack/controller/view_paths_test.rb +++ b/actionview/test/actionpack/controller/view_paths_test.rb @@ -23,9 +23,9 @@ class ViewLoadPathsTest < ActionController::TestCase end def setup - @request = ActionController::TestRequest.create - @response = ActionDispatch::TestResponse.new @controller = TestController.new + @request = ActionController::TestRequest.create(@controller.class) + @response = ActionDispatch::TestResponse.new @paths = TestController.view_paths end @@ -131,7 +131,7 @@ class ViewLoadPathsTest < ActionController::TestCase "Decorated body", template.identifier, template.handler, - virtual_path: template.virtual_path, + virtual_path: template.virtual_path, format: template.formats ) end diff --git a/actionview/test/template/form_helper_test.rb b/actionview/test/template/form_helper_test.rb index 590e79d114..70f690478d 100644 --- a/actionview/test/template/form_helper_test.rb +++ b/actionview/test/template/form_helper_test.rb @@ -16,7 +16,8 @@ class FormHelperTest < ActionView::TestCase setup do # Create "label" locale for testing I18n label helpers - I18n.backend.store_translations "label", activemodel: { + I18n.backend.store_translations "label", + activemodel: { attributes: { post: { cost: "Total cost" @@ -47,7 +48,8 @@ class FormHelperTest < ActionView::TestCase } # Create "submit" locale for testing I18n submit helpers - I18n.backend.store_translations "submit", helpers: { + I18n.backend.store_translations "submit", + helpers: { submit: { create: "Create %{model}", update: "Confirm %{model} changes", @@ -58,7 +60,8 @@ class FormHelperTest < ActionView::TestCase } } - I18n.backend.store_translations "placeholder", activemodel: { + I18n.backend.store_translations "placeholder", + activemodel: { attributes: { post: { cost: "Total cost" diff --git a/actionview/test/template/tag_helper_test.rb b/actionview/test/template/tag_helper_test.rb index 281fec7291..d07312ace3 100644 --- a/actionview/test/template/tag_helper_test.rb +++ b/actionview/test/template/tag_helper_test.rb @@ -59,6 +59,14 @@ class TagHelperTest < ActionView::TestCase assert_equal "<p included=\"\"></p>", tag.p(included: "") end + def test_tag_options_accepts_symbol_option_when_not_escaping + assert_equal "<p value=\"symbol\" />", tag("p", { value: :symbol }, false, false) + end + + def test_tag_options_accepts_integer_option_when_not_escaping + assert_equal "<p value=\"42\" />", tag("p", { value: 42 }, false, false) + end + def test_tag_options_converts_boolean_option assert_dom_equal '<p disabled="disabled" itemscope="itemscope" multiple="multiple" readonly="readonly" allowfullscreen="allowfullscreen" seamless="seamless" typemustmatch="typemustmatch" sortable="sortable" default="default" inert="inert" truespeed="truespeed" />', tag("p", disabled: true, itemscope: true, multiple: true, readonly: true, allowfullscreen: true, seamless: true, typemustmatch: true, sortable: true, default: true, inert: true, truespeed: true) @@ -274,6 +282,16 @@ class TagHelperTest < ActionView::TestCase assert_equal '<p class="song> play>"></p>', tag.p(class: [raw("song>"), "play>"]) end + def test_tag_does_not_honor_html_safe_double_quotes_as_attributes + assert_dom_equal '<p title=""">content</p>', + content_tag('p', "content", title: '"'.html_safe) + end + + def test_data_tag_does_not_honor_html_safe_double_quotes_as_attributes + assert_dom_equal '<p data-title=""">content</p>', + content_tag('p', "content", data: { title: '"'.html_safe }) + end + def test_skip_invalid_escaped_attributes ["&1;", "dfa3;", "& #123;"].each do |escaped| assert_equal %(<a href="#{escaped.gsub(/&/, '&')}" />), tag("a", href: escaped) diff --git a/activemodel/bin/test b/activemodel/bin/test index 404cabba51..84a05bba08 100755 --- a/activemodel/bin/test +++ b/activemodel/bin/test @@ -1,4 +1,6 @@ #!/usr/bin/env ruby -COMPONENT_ROOT = File.expand_path("../../", __FILE__) + +COMPONENT_ROOT = File.expand_path("..", __dir__) require File.expand_path("../tools/test", COMPONENT_ROOT) + exit Minitest.run(ARGV) diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb index b6ab7ab6b0..45ef14013a 100644 --- a/activemodel/lib/active_model/errors.rb +++ b/activemodel/lib/active_model/errors.rb @@ -439,7 +439,8 @@ module ActiveModel return message if attribute == :base attr_name = attribute.to_s.tr(".", "_").humanize attr_name = @base.class.human_attribute_name(attribute, default: attr_name) - I18n.t(:"errors.format", default: "%{attribute} %{message}", + I18n.t(:"errors.format", + default: "%{attribute} %{message}", attribute: attr_name, message: message) end diff --git a/activemodel/test/cases/validations/confirmation_validation_test.rb b/activemodel/test/cases/validations/confirmation_validation_test.rb index a980628765..c13017d825 100644 --- a/activemodel/test/cases/validations/confirmation_validation_test.rb +++ b/activemodel/test/cases/validations/confirmation_validation_test.rb @@ -55,7 +55,8 @@ class ConfirmationValidationTest < ActiveModel::TestCase @old_load_path, @old_backend = I18n.load_path.dup, I18n.backend I18n.load_path.clear I18n.backend = I18n::Backend::Simple.new - I18n.backend.store_translations("en", errors: { messages: { confirmation: "doesn't match %{attribute}" } }, + I18n.backend.store_translations("en", + errors: { messages: { confirmation: "doesn't match %{attribute}" } }, activemodel: { attributes: { topic: { title: "Test Title"} } }) Topic.validates_confirmation_of(:title) diff --git a/activerecord/bin/test b/activerecord/bin/test index 822e303ad8..23add35d45 100755 --- a/activerecord/bin/test +++ b/activerecord/bin/test @@ -1,6 +1,8 @@ #!/usr/bin/env ruby -COMPONENT_ROOT = File.expand_path("../../", __FILE__) + +COMPONENT_ROOT = File.expand_path("..", __dir__) require File.expand_path("../tools/test", COMPONENT_ROOT) + module Minitest def self.plugin_active_record_options(opts, options) opts.separator "" diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb index d2ebc36fff..d0aefcef68 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb @@ -1208,7 +1208,7 @@ module ActiveRecord checks << lambda { |i| i.columns.join("_and_") == column_names.join("_and_") } end - raise ArgumentError "No name or columns specified" if checks.none? + raise ArgumentError, "No name or columns specified" if checks.none? matching_indexes = indexes(table_name).select { |i| checks.all? { |check| check[i] } } diff --git a/activerecord/lib/active_record/connection_adapters/connection_specification.rb b/activerecord/lib/active_record/connection_adapters/connection_specification.rb index a7869f44ea..be6b55e53c 100644 --- a/activerecord/lib/active_record/connection_adapters/connection_specification.rb +++ b/activerecord/lib/active_record/connection_adapters/connection_specification.rb @@ -78,10 +78,12 @@ module ActiveRecord def raw_config if uri.opaque - query_hash.merge( "adapter" => @adapter, + query_hash.merge( + "adapter" => @adapter, "database" => uri.opaque) else - query_hash.merge( "adapter" => @adapter, + query_hash.merge( + "adapter" => @adapter, "username" => uri.user, "password" => uri.password, "port" => uri.port, diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb index a4a06a2da4..d3c65f3d94 100644 --- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb @@ -81,6 +81,12 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase assert_equal expected, remove_index(:people, name: "index_people_on_last_name", algorithm: :concurrently) end + def test_remove_index_with_wrong_option + assert_raises ArgumentError do + remove_index(:people, coulmn: :last_name) + end + end + private def method_missing(method_symbol, *arguments) ActiveRecord::Base.connection.send(method_symbol, *arguments) diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index c4f174e470..ec6ae39835 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -283,7 +283,8 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase end def test_habtm_collection_size_from_params - devel = Developer.new( projects_attributes: { + devel = Developer.new( + projects_attributes: { "0" => {} }) diff --git a/activerecord/test/cases/base_test.rb b/activerecord/test/cases/base_test.rb index 73ca83f21b..cd896e5948 100644 --- a/activerecord/test/cases/base_test.rb +++ b/activerecord/test/cases/base_test.rb @@ -279,7 +279,8 @@ class BasicsTest < ActiveRecord::TestCase end def test_initialize_with_attributes - topic = Topic.new( "title" => "initialized from attributes", "written_on" => "2003-12-12 23:23") + topic = Topic.new( + "title" => "initialized from attributes", "written_on" => "2003-12-12 23:23") assert_equal("initialized from attributes", topic.title) end diff --git a/activerecord/test/cases/nested_attributes_test.rb b/activerecord/test/cases/nested_attributes_test.rb index f0cf02ce54..788277faea 100644 --- a/activerecord/test/cases/nested_attributes_test.rb +++ b/activerecord/test/cases/nested_attributes_test.rb @@ -596,7 +596,8 @@ module NestedAttributesOnACollectionAssociationTests end def test_should_save_only_one_association_on_create - pirate = Pirate.create!( :catchphrase => "Arr", + pirate = Pirate.create!( + :catchphrase => "Arr", association_getter => { "foo" => { name: "Grace OMalley" } }) assert_equal 1, pirate.reload.send(@association_name).count diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index 57dc963b62..e293770725 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -967,7 +967,8 @@ class PersistenceTest < ActiveRecord::TestCase self.table_name = :widgets end - instance = widget.create!( name: "Bob", + instance = widget.create!( + name: "Bob", created_at: 1.day.ago, updated_at: 1.day.ago) diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb index 13007e2e73..a46123f451 100644 --- a/activerecord/test/cases/scoping/relation_scoping_test.rb +++ b/activerecord/test/cases/scoping/relation_scoping_test.rb @@ -246,7 +246,7 @@ class NestedRelationScopingTest < ActiveRecord::TestCase devs = Developer.all sql = devs.to_sql assert_match "(salary = 80000)", sql - assert_match /LIMIT 10|ROWNUM <= 10|FETCH FIRST 10 ROWS ONLY/, sql + assert_match(/LIMIT 10|ROWNUM <= 10|FETCH FIRST 10 ROWS ONLY/, sql) end end end diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index 76510cb80d..5d9aa99497 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -156,17 +156,15 @@ class ValidationsTest < ActiveRecord::TestCase end def test_numericality_validation_with_mutation - Topic.class_eval do + klass = Class.new(Topic) do attribute :wibble, :string validates_numericality_of :wibble, only_integer: true end - topic = Topic.new(wibble: "123-4567") + topic = klass.new(wibble: "123-4567") topic.wibble.gsub!("-", "") assert topic.valid? - ensure - Topic.reset_column_information end def test_acceptance_validator_doesnt_require_db_connection diff --git a/activesupport/bin/test b/activesupport/bin/test index 404cabba51..84a05bba08 100755 --- a/activesupport/bin/test +++ b/activesupport/bin/test @@ -1,4 +1,6 @@ #!/usr/bin/env ruby -COMPONENT_ROOT = File.expand_path("../../", __FILE__) + +COMPONENT_ROOT = File.expand_path("..", __dir__) require File.expand_path("../tools/test", COMPONENT_ROOT) + exit Minitest.run(ARGV) diff --git a/guides/rails_guides/markdown.rb b/guides/rails_guides/markdown.rb index e6c9fea87e..33563d669c 100644 --- a/guides/rails_guides/markdown.rb +++ b/guides/rails_guides/markdown.rb @@ -54,7 +54,8 @@ module RailsGuides end def engine - @engine ||= Redcarpet::Markdown.new(Renderer, no_intra_emphasis: true, + @engine ||= Redcarpet::Markdown.new(Renderer, + no_intra_emphasis: true, fenced_code_blocks: true, autolink: true, strikethrough: true, diff --git a/guides/source/action_cable_overview.md b/guides/source/action_cable_overview.md index 02db86888c..118b0b52b2 100644 --- a/guides/source/action_cable_overview.md +++ b/guides/source/action_cable_overview.md @@ -242,10 +242,10 @@ WebNotificationsChannel.broadcast_to( The `WebNotificationsChannel.broadcast_to` call places a message in the current subscription adapter (Redis by default)'s pubsub queue under a separate broadcasting name for each user. For a user with an ID of 1, the broadcasting -name would be `web_notifications_1`. +name would be `web_notifications:1`. The channel has been instructed to stream everything that arrives at -`web_notifications_1` directly to the client by invoking the `received` +`web_notifications:1` directly to the client by invoking the `received` callback. ### Subscriptions @@ -313,7 +313,7 @@ App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" }, ```ruby # Somewhere in your app this is called, perhaps # from a NewCommentJob. -ChatChannel.broadcast_to( +ActionCable.server.broadcast( "chat_#{room}", sent_by: 'Paul', body: 'This is a cool chat app.' diff --git a/guides/source/active_model_basics.md b/guides/source/active_model_basics.md index e834aeadb1..732e553c62 100644 --- a/guides/source/active_model_basics.md +++ b/guides/source/active_model_basics.md @@ -416,7 +416,6 @@ the Active Model API. ```ruby class Person include ActiveModel::Model - end ``` @@ -467,7 +466,7 @@ In order to make this work, the model must have an accessor named `password_dige The `has_secure_password` will add the following validations on the `password` accessor: 1. Password should be present. -2. Password should be equal to its confirmation. +2. Password should be equal to its confirmation (provided +password_confirmation+ is passed along). 3. The maximum length of a password is 72 (required by `bcrypt` on which ActiveModel::SecurePassword depends) #### Examples @@ -493,6 +492,10 @@ person.valid? # => false person.password = person.password_confirmation = 'a' * 100 person.valid? # => false +# When only password is supplied with no password_confirmation. +person.password = 'aditya' +person.valid? # => true + # When all validations are passed. person.password = person.password_confirmation = 'aditya' person.valid? # => true diff --git a/guides/source/active_record_querying.md b/guides/source/active_record_querying.md index 8ffd0d033d..6f941d0e4e 100644 --- a/guides/source/active_record_querying.md +++ b/guides/source/active_record_querying.md @@ -1251,8 +1251,9 @@ articles, all the articles would still be loaded. By using `joins` (an INNER JOIN), the join conditions **must** match, otherwise no records will be returned. - - +NOTE: If an association is eager loaded as part of a join, any fields from a custom select clause will not present be on the loaded models. +This is because it is ambiguous whether they should appear on the parent record, or the child. + Scopes ------ diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index cc84ecb216..a1b0029c47 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -512,6 +512,30 @@ class ProductsController < ApplicationController end ``` +Sometimes we want to cache response, for example a static page, that never gets +expired. To achieve this, we can use `http_cache_forever` helper and by doing +so browser and proxies will cache it indefinitely. + +By default cached responses will be private, cached only on the user's web +browser. To allow proxies to cache the response, set `public: true` to indicate +that they can serve the cached response to all users. + +Using this helper, `last_modified` header is set to `Time.new(2011, 1, 1).utc` +and `expires` header is set to a 100 years. + +WARNING: Use this method carefully as browser/proxy won't be able to invalidate +the cached response unless browser cache is forcefully cleared. + +```ruby +class HomeController < ApplicationController + def index + http_cache_forever(public: true) do + render + end + end +end +``` + ### Strong v/s Weak ETags Rails generates weak ETags by default. Weak ETags allow semantically equivalent diff --git a/guides/source/debugging_rails_applications.md b/guides/source/debugging_rails_applications.md index e4fc7f4743..df3003a6a8 100644 --- a/guides/source/debugging_rails_applications.md +++ b/guides/source/debugging_rails_applications.md @@ -946,6 +946,7 @@ development that will end your tailing of development.log. Have all information about your Rails app requests in the browser — in the Developer Tools panel. Provides insight to db/rendering/total times, parameter list, rendered views and more. +* [Pry](https://github.com/pry/pry) An IRB alternative and runtime developer console. References ---------- diff --git a/railties/lib/rails/commands/server.rb b/railties/lib/rails/commands/server.rb index bf8ae0a5ee..0339849bfe 100644 --- a/railties/lib/rails/commands/server.rb +++ b/railties/lib/rails/commands/server.rb @@ -88,7 +88,8 @@ module Rails end def default_options - super.merge( Port: ENV.fetch("PORT", 3000).to_i, + super.merge( + Port: ENV.fetch("PORT", 3000).to_i, Host: ENV.fetch("HOST", "localhost").dup, DoNotReverseLookup: true, environment: (ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development").dup, diff --git a/railties/lib/rails/engine/commands_tasks.rb b/railties/lib/rails/engine/commands_tasks.rb index ac0861328b..e21a7183fc 100644 --- a/railties/lib/rails/engine/commands_tasks.rb +++ b/railties/lib/rails/engine/commands_tasks.rb @@ -103,6 +103,8 @@ In addition to those commands, there are: end def rake_tasks + require_rake + return @rake_tasks if defined?(@rake_tasks) load_generators diff --git a/railties/test/engine/commands_tasks_test.rb b/railties/test/engine/commands_tasks_test.rb new file mode 100644 index 0000000000..817175b9ef --- /dev/null +++ b/railties/test/engine/commands_tasks_test.rb @@ -0,0 +1,24 @@ +require "abstract_unit" + +class Rails::Engine::CommandsTasksTest < ActiveSupport::TestCase + def setup + @destination_root = Dir.mktmpdir("bukkits") + Dir.chdir(@destination_root) { `bundle exec rails plugin new bukkits --mountable` } + end + + def teardown + FileUtils.rm_rf(@destination_root) + end + + def test_help_command_work_inside_engine + output = capture(:stderr) do + Dir.chdir(plugin_path) { `bin/rails --help` } + end + assert_no_match "NameError", output + end + + private + def plugin_path + "#{@destination_root}/bukkits" + end +end diff --git a/railties/test/json_params_parsing_test.rb b/railties/test/json_params_parsing_test.rb new file mode 100644 index 0000000000..eac731a942 --- /dev/null +++ b/railties/test/json_params_parsing_test.rb @@ -0,0 +1,47 @@ +require "abstract_unit" +require "action_dispatch" +require "active_record" + +class JsonParamsParsingTest < ActionDispatch::IntegrationTest + test "prevent null query" do + # Make sure we have data to find + klass = Class.new(ActiveRecord::Base) do + def self.name; 'Foo'; end + establish_connection adapter: "sqlite3", database: ":memory:" + connection.create_table "foos" do |t| + t.string :title + t.timestamps null: false + end + end + klass.create + assert klass.first + + app = ->(env) { + request = ActionDispatch::Request.new env + params = ActionController::Parameters.new request.parameters + if params[:t] + klass.find_by_title(params[:t]) + else + nil + end + } + + assert_nil app.call(make_env({ 't' => nil })) + assert_nil app.call(make_env({ 't' => [nil] })) + + [[[nil]], [[[nil]]]].each do |data| + assert_nil app.call(make_env({ 't' => data })) + end + end + + private + def make_env json + data = JSON.dump json + content_length = data.length + { + 'CONTENT_LENGTH' => content_length, + 'CONTENT_TYPE' => 'application/json', + 'rack.input' => StringIO.new(data) + } + end +end |