Merge pull request #2 from rails/master

merge master
This commit is contained in:
Joel Hawksley 2019-06-03 08:27:09 -06:00 committed by GitHub
commit 60af9db374
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
65 changed files with 488 additions and 295 deletions

View File

@ -1,5 +1,3 @@
# frozen_string_literal: true
require "test_helper"
class <%= class_name %>MailboxTest < ActionMailbox::TestCase

View File

@ -148,7 +148,7 @@ module ActionController
attr_internal :response, :request
delegate :session, to: "@_request"
delegate :headers, :status=, :location=, :content_type=,
:status, :location, :content_type, to: "@_response"
:status, :location, :content_type, :media_type, to: "@_response"
def initialize
@_request = nil

View File

@ -205,7 +205,7 @@ module ActionController #:nodoc:
yield collector if block_given?
if format = collector.negotiate_format(request)
if content_type && content_type != format
if media_type && media_type != format
raise ActionController::RespondToMismatchError
end
_process_format(format)

View File

@ -157,7 +157,7 @@ module ActionController
json = json.to_json(options) unless json.kind_of?(String)
if options[:callback].present?
if content_type.nil? || content_type == Mime[:json]
if media_type.nil? || media_type == Mime[:json]
self.content_type = Mime[:js]
end

View File

@ -73,7 +73,7 @@ module ActionController
end
def _set_rendered_content_type(format)
if format && !response.content_type
if format && !response.media_type
self.content_type = format.to_s
end
end

View File

@ -243,8 +243,12 @@ module ActionDispatch # :nodoc:
end
# Content type of response.
# It returns just MIME type and does NOT contain charset part.
def content_type
super.presence
end
# Media type of response.
def media_type
parsed_content_type_header.mime_type
end
@ -458,7 +462,7 @@ module ActionDispatch # :nodoc:
end
def assign_default_content_type_and_charset!
return if content_type
return if media_type
ct = parsed_content_type_header
set_content_type(ct.mime_type || Mime[:html].to_s,

View File

@ -4,8 +4,6 @@ require "action_dispatch/http/request"
require "action_dispatch/middleware/exception_wrapper"
require "action_dispatch/routing/inspector"
require "active_support/actionable_error"
require "action_view"
require "action_view/base"

View File

@ -14,7 +14,7 @@ module ActionDispatch
include Rails::Dom::Testing::Assertions
def html_document
@html_document ||= if @response.content_type.to_s.end_with?("xml")
@html_document ||= if @response.media_type.to_s.end_with?("xml")
Nokogiri::XML::Document.parse(@response.body)
else
Nokogiri::HTML::Document.parse(@response.body)

View File

@ -19,7 +19,7 @@ module ActionDispatch
end
def response_parser
@response_parser ||= RequestEncoder.parser(content_type)
@response_parser ||= RequestEncoder.parser(media_type)
end
end
end

View File

@ -66,68 +66,68 @@ class ContentTypeTest < ActionController::TestCase
def test_render_defaults
get :render_defaults
assert_equal "utf-8", @response.charset
assert_equal Mime[:text], @response.content_type
assert_equal Mime[:text], @response.media_type
end
def test_render_changed_charset_default
with_default_charset "utf-16" do
get :render_defaults
assert_equal "utf-16", @response.charset
assert_equal Mime[:text], @response.content_type
assert_equal Mime[:text], @response.media_type
end
end
# :ported:
def test_content_type_from_body
get :render_content_type_from_body
assert_equal Mime[:rss], @response.content_type
assert_equal Mime[:rss], @response.media_type
assert_equal "utf-8", @response.charset
end
# :ported:
def test_content_type_from_render
get :render_content_type_from_render
assert_equal Mime[:rss], @response.content_type
assert_equal Mime[:rss], @response.media_type
assert_equal "utf-8", @response.charset
end
# :ported:
def test_charset_from_body
get :render_charset_from_body
assert_equal Mime[:text], @response.content_type
assert_equal Mime[:text], @response.media_type
assert_equal "utf-16", @response.charset
end
# :ported:
def test_nil_charset_from_body
get :render_nil_charset_from_body
assert_equal Mime[:text], @response.content_type
assert_equal Mime[:text], @response.media_type
assert_equal "utf-8", @response.charset, @response.headers.inspect
end
def test_nil_default_for_erb
with_default_charset nil do
get :render_default_for_erb
assert_equal Mime[:html], @response.content_type
assert_equal Mime[:html], @response.media_type
assert_nil @response.charset, @response.headers.inspect
end
end
def test_default_for_erb
get :render_default_for_erb
assert_equal Mime[:html], @response.content_type
assert_equal Mime[:html], @response.media_type
assert_equal "utf-8", @response.charset
end
def test_default_for_builder
get :render_default_for_builder
assert_equal Mime[:xml], @response.content_type
assert_equal Mime[:xml], @response.media_type
assert_equal "utf-8", @response.charset
end
def test_change_for_builder
get :render_change_for_builder
assert_equal Mime[:html], @response.content_type
assert_equal Mime[:html], @response.media_type
assert_equal "utf-8", @response.charset
end
@ -148,22 +148,22 @@ class AcceptBasedContentTypeTest < ActionController::TestCase
def test_render_default_content_types_for_respond_to
@request.accept = Mime[:html].to_s
get :render_default_content_types_for_respond_to
assert_equal Mime[:html], @response.content_type
assert_equal Mime[:html], @response.media_type
@request.accept = Mime[:js].to_s
get :render_default_content_types_for_respond_to
assert_equal Mime[:js], @response.content_type
assert_equal Mime[:js], @response.media_type
end
def test_render_default_content_types_for_respond_to_with_template
@request.accept = Mime[:xml].to_s
get :render_default_content_types_for_respond_to
assert_equal Mime[:xml], @response.content_type
assert_equal Mime[:xml], @response.media_type
end
def test_render_default_content_types_for_respond_to_with_overwrite
@request.accept = Mime[:rss].to_s
get :render_default_content_types_for_respond_to
assert_equal Mime[:xml], @response.content_type
assert_equal Mime[:xml], @response.media_type
end
end

View File

@ -522,11 +522,11 @@ class IntegrationProcessTest < ActionDispatch::IntegrationTest
with_test_route_set do
get "/get", headers: { "Accept" => "application/json" }, xhr: true
assert_equal "application/json", request.accept
assert_equal "application/json", response.content_type
assert_equal "application/json", response.media_type
get "/get", headers: { "HTTP_ACCEPT" => "application/json" }, xhr: true
assert_equal "application/json", request.accept
assert_equal "application/json", response.content_type
assert_equal "application/json", response.media_type
end
end
@ -986,7 +986,7 @@ class IntegrationRequestEncodersTest < ActionDispatch::IntegrationTest
def test_encoding_as_json
post_to_foos as: :json do
assert_response :success
assert_equal "application/json", request.content_type
assert_equal "application/json", request.media_type
assert_equal "application/json", request.accepts.first.to_s
assert_equal :json, request.format.ref
assert_equal({ "foo" => "fighters" }, request.request_parameters)
@ -1025,7 +1025,7 @@ class IntegrationRequestEncodersTest < ActionDispatch::IntegrationTest
post_to_foos as: :wibble do
assert_response :success
assert_equal "/foos_wibble", request.path
assert_equal "text/wibble", request.content_type
assert_equal "text/wibble", request.media_type
assert_equal "text/wibble", request.accepts.first.to_s
assert_equal :wibble, request.format.ref
assert_equal Hash.new, request.request_parameters # Unregistered MIME Type can't be parsed.

View File

@ -43,6 +43,6 @@ class LocalizedTemplatesTest < ActionController::TestCase
I18n.locale = :it
get :hello_world
assert_equal "Ciao Mondo", @response.body
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
end
end

View File

@ -38,13 +38,13 @@ class RenderersMetalTest < ActionController::TestCase
get :one
assert_response :success
assert_equal({ a: "b" }.to_json, @response.body)
assert_equal "application/json", @response.content_type
assert_equal "application/json", @response.media_type
end
def test_render_xml
get :two
assert_response :success
assert_equal(" ", @response.body)
assert_equal "text/plain", @response.content_type
assert_equal "text/plain", @response.media_type
end
end

View File

@ -423,12 +423,12 @@ class RespondToControllerTest < ActionController::TestCase
def test_using_defaults
@request.accept = "*/*"
get :using_defaults
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "Hello world!", @response.body
@request.accept = "application/xml"
get :using_defaults
assert_equal "application/xml", @response.content_type
assert_equal "application/xml", @response.media_type
assert_equal "<p>Hello world!</p>\n", @response.body
end
@ -449,12 +449,12 @@ class RespondToControllerTest < ActionController::TestCase
def test_using_defaults_with_type_list
@request.accept = "*/*"
get :using_defaults_with_type_list
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "Hello world!", @response.body
@request.accept = "application/xml"
get :using_defaults_with_type_list
assert_equal "application/xml", @response.content_type
assert_equal "application/xml", @response.media_type
assert_equal "<p>Hello world!</p>\n", @response.body
end
@ -468,7 +468,7 @@ class RespondToControllerTest < ActionController::TestCase
def test_using_non_conflicting_nested_js_then_js
@request.accept = "*/*"
get :using_non_conflicting_nested_js_then_js
assert_equal "text/javascript", @response.content_type
assert_equal "text/javascript", @response.media_type
assert_equal "JS", @response.body
end
@ -499,12 +499,12 @@ class RespondToControllerTest < ActionController::TestCase
def test_custom_types
@request.accept = "application/fancy-xml"
get :custom_type_handling
assert_equal "application/fancy-xml", @response.content_type
assert_equal "application/fancy-xml", @response.media_type
assert_equal "Fancy XML", @response.body
@request.accept = "text/html"
get :custom_type_handling
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "HTML", @response.body
end
@ -595,7 +595,7 @@ class RespondToControllerTest < ActionController::TestCase
@request.accept = "application/json"
get :json_with_callback
assert_equal "/**/alert(JS)", @response.body
assert_equal "text/javascript", @response.content_type
assert_equal "text/javascript", @response.media_type
end
def test_xhr
@ -605,13 +605,13 @@ class RespondToControllerTest < ActionController::TestCase
def test_custom_constant
get :custom_constant_handling, format: "mobile"
assert_equal "text/x-mobile", @response.content_type
assert_equal "text/x-mobile", @response.media_type
assert_equal "Mobile", @response.body
end
def test_custom_constant_handling_without_block
get :custom_constant_handling_without_block, format: "mobile"
assert_equal "text/x-mobile", @response.content_type
assert_equal "text/x-mobile", @response.media_type
assert_equal "Mobile", @response.body
end
@ -664,7 +664,7 @@ class RespondToControllerTest < ActionController::TestCase
assert_equal '<html><div id="html">Hello future from Firefox!</div></html>', @response.body
get :iphone_with_html_response_type, format: "iphone"
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body
end
@ -672,7 +672,7 @@ class RespondToControllerTest < ActionController::TestCase
@request.accept = "text/iphone"
get :iphone_with_html_response_type
assert_equal '<html><div id="iphone">Hello iPhone future from iPhone!</div></html>', @response.body
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
end
def test_invalid_format
@ -702,7 +702,7 @@ class RespondToControllerTest < ActionController::TestCase
def test_variant_with_implicit_template_rendering
get :variant_with_implicit_template_rendering, params: { v: :mobile }
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "mobile", @response.body
end
@ -756,137 +756,137 @@ class RespondToControllerTest < ActionController::TestCase
def test_variant_with_format_and_custom_render
get :variant_with_format_and_custom_render, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "mobile", @response.body
end
def test_multiple_variants_for_format
get :multiple_variants_for_format, params: { v: :tablet }
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "tablet", @response.body
end
def test_no_variant_in_variant_setup
get :variant_plus_none_for_format
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "none", @response.body
end
def test_variant_inline_syntax
get :variant_inline_syntax
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "none", @response.body
get :variant_inline_syntax, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "phone", @response.body
end
def test_variant_inline_syntax_with_format
get :variant_inline_syntax, format: :js
assert_equal "text/javascript", @response.content_type
assert_equal "text/javascript", @response.media_type
assert_equal "js", @response.body
end
def test_variant_inline_syntax_without_block
get :variant_inline_syntax_without_block, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "phone", @response.body
end
def test_variant_any
get :variant_any, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "phone", @response.body
get :variant_any, params: { v: :tablet }
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "any", @response.body
get :variant_any, params: { v: :phablet }
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "any", @response.body
end
def test_variant_any_any
get :variant_any_any
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "any", @response.body
get :variant_any_any, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "phone", @response.body
get :variant_any_any, params: { v: :yolo }
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "any", @response.body
end
def test_variant_inline_any
get :variant_any, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "phone", @response.body
get :variant_inline_any, params: { v: :tablet }
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "any", @response.body
get :variant_inline_any, params: { v: :phablet }
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "any", @response.body
end
def test_variant_inline_any_any
get :variant_inline_any_any, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "phone", @response.body
get :variant_inline_any_any, params: { v: :yolo }
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "any", @response.body
end
def test_variant_any_implicit_render
get :variant_any_implicit_render, params: { v: :tablet }
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "tablet", @response.body
get :variant_any_implicit_render, params: { v: :phablet }
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "phablet", @response.body
end
def test_variant_any_with_none
get :variant_any_with_none
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "none or phone", @response.body
get :variant_any_with_none, params: { v: :phone }
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "none or phone", @response.body
end
def test_format_any_variant_any
get :format_any_variant_any, format: :js, params: { v: :tablet }
assert_equal "text/javascript", @response.content_type
assert_equal "text/javascript", @response.media_type
assert_equal "tablet", @response.body
end
def test_variant_negotiation_inline_syntax
get :variant_inline_syntax_without_block, params: { v: [:tablet, :phone] }
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "phone", @response.body
end
def test_variant_negotiation_block_syntax
get :variant_plus_none_for_format, params: { v: [:tablet, :phone] }
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "phone", @response.body
end
def test_variant_negotiation_without_block
get :variant_inline_syntax_without_block, params: { v: [:tablet, :phone] }
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
assert_equal "phone", @response.body
end
end

View File

@ -2,7 +2,6 @@
require "abstract_unit"
require "controller/fake_models"
require "pathname"
class RenderJSTest < ActionController::TestCase
class TestController < ActionController::Base
@ -26,7 +25,7 @@ class RenderJSTest < ActionController::TestCase
def test_render_vanilla_js
get :render_vanilla_js_hello, xhr: true
assert_equal "alert('hello')", @response.body
assert_equal "text/javascript", @response.content_type
assert_equal "text/javascript", @response.media_type
end
def test_should_render_js_partial

View File

@ -3,7 +3,6 @@
require "abstract_unit"
require "controller/fake_models"
require "active_support/logger"
require "pathname"
class RenderJsonTest < ActionController::TestCase
class JsonRenderable
@ -80,7 +79,7 @@ class RenderJsonTest < ActionController::TestCase
def test_render_json_nil
get :render_json_nil
assert_equal "null", @response.body
assert_equal "application/json", @response.content_type
assert_equal "application/json", @response.media_type
end
def test_render_json_render_to_string
@ -91,7 +90,7 @@ class RenderJsonTest < ActionController::TestCase
def test_render_json
get :render_json_hello_world
assert_equal '{"hello":"world"}', @response.body
assert_equal "application/json", @response.content_type
assert_equal "application/json", @response.media_type
end
def test_render_json_with_status
@ -103,31 +102,31 @@ class RenderJsonTest < ActionController::TestCase
def test_render_json_with_callback
get :render_json_hello_world_with_callback, xhr: true
assert_equal '/**/alert({"hello":"world"})', @response.body
assert_equal "text/javascript", @response.content_type
assert_equal "text/javascript", @response.media_type
end
def test_render_json_with_custom_content_type
get :render_json_with_custom_content_type, xhr: true
assert_equal '{"hello":"world"}', @response.body
assert_equal "text/javascript", @response.content_type
assert_equal "text/javascript", @response.media_type
end
def test_render_symbol_json
get :render_symbol_json
assert_equal '{"hello":"world"}', @response.body
assert_equal "application/json", @response.content_type
assert_equal "application/json", @response.media_type
end
def test_render_json_with_render_to_string
get :render_json_with_render_to_string
assert_equal '{"hello":"partial html"}', @response.body
assert_equal "application/json", @response.content_type
assert_equal "application/json", @response.media_type
end
def test_render_json_forwards_extra_options
get :render_json_with_extra_options
assert_equal '{"a":"b"}', @response.body
assert_equal "application/json", @response.content_type
assert_equal "application/json", @response.media_type
end
def test_render_json_calls_to_json_from_object

View File

@ -2,7 +2,6 @@
require "abstract_unit"
require "controller/fake_models"
require "pathname"
class RenderXmlTest < ActionController::TestCase
class XmlRenderable
@ -92,11 +91,11 @@ class RenderXmlTest < ActionController::TestCase
def test_should_render_xml_but_keep_custom_content_type
get :render_xml_with_custom_content_type
assert_equal "application/atomsvc+xml", @response.content_type
assert_equal "application/atomsvc+xml", @response.media_type
end
def test_should_use_implicit_content_type
get :implicit_content_type, format: "atom"
assert_equal Mime[:atom], @response.content_type
assert_equal Mime[:atom], @response.media_type
end
end

View File

@ -73,7 +73,7 @@ class RenderersTest < ActionController::TestCase
assert_raise ActionView::MissingTemplate do
get :respond_to_mime, format: "csv"
end
assert_equal Mime[:csv], @response.content_type
assert_equal Mime[:csv], @response.media_type
assert_equal "", @response.body
end
@ -83,7 +83,7 @@ class RenderersTest < ActionController::TestCase
end
@request.accept = "text/csv"
get :respond_to_mime, format: "csv"
assert_equal Mime[:csv], @response.content_type
assert_equal Mime[:csv], @response.media_type
assert_equal "c,s,v", @response.body
ensure
ActionController::Renderers.remove :csv

View File

@ -76,7 +76,7 @@ module ShowExceptions
@app = ShowExceptionsOverriddenController.action(:boom)
get "/", headers: { "HTTP_ACCEPT" => "application/json" }
assert_response :internal_server_error
assert_equal "application/json", response.content_type.to_s
assert_equal "application/json", response.media_type
assert_equal({ status: 500, error: "Internal Server Error" }.to_json, response.body)
end
@ -84,7 +84,7 @@ module ShowExceptions
@app = ShowExceptionsOverriddenController.action(:boom)
get "/", headers: { "HTTP_ACCEPT" => "application/xml" }
assert_response :internal_server_error
assert_equal "application/xml", response.content_type.to_s
assert_equal "application/xml", response.media_type
assert_equal({ status: 500, error: "Internal Server Error" }.to_xml, response.body)
end
@ -92,7 +92,7 @@ module ShowExceptions
@app = ShowExceptionsOverriddenController.action(:boom)
get "/", headers: { "HTTP_ACCEPT" => "text/csv" }
assert_response :internal_server_error
assert_equal "text/html", response.content_type.to_s
assert_equal "text/html", response.media_type
end
end
@ -106,7 +106,7 @@ module ShowExceptions
get "/", headers: { "HTTP_ACCEPT" => "text/json" }
assert_response :internal_server_error
assert_equal "text/plain", response.content_type.to_s
assert_equal "text/plain", response.media_type
ensure
middleware.instance_variable_set(:@exceptions_app, @exceptions_app)
$stderr = STDERR

View File

@ -208,7 +208,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
assert_response 500
assert_no_match(/<header>/, body)
assert_no_match(/<body>/, body)
assert_equal "text/plain", response.content_type
assert_equal "text/plain", response.media_type
assert_match(/RuntimeError\npuke/, body)
Rails.stub :root, Pathname.new(".") do
@ -222,31 +222,31 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
get "/not_found", headers: xhr_request_env
assert_response 404
assert_no_match(/<body>/, body)
assert_equal "text/plain", response.content_type
assert_equal "text/plain", response.media_type
assert_match(/#{AbstractController::ActionNotFound.name}/, body)
get "/method_not_allowed", headers: xhr_request_env
assert_response 405
assert_no_match(/<body>/, body)
assert_equal "text/plain", response.content_type
assert_equal "text/plain", response.media_type
assert_match(/ActionController::MethodNotAllowed/, body)
get "/unknown_http_method", headers: xhr_request_env
assert_response 405
assert_no_match(/<body>/, body)
assert_equal "text/plain", response.content_type
assert_equal "text/plain", response.media_type
assert_match(/ActionController::UnknownHttpMethod/, body)
get "/bad_request", headers: xhr_request_env
assert_response 400
assert_no_match(/<body>/, body)
assert_equal "text/plain", response.content_type
assert_equal "text/plain", response.media_type
assert_match(/ActionController::BadRequest/, body)
get "/parameter_missing", headers: xhr_request_env
assert_response 400
assert_no_match(/<body>/, body)
assert_equal "text/plain", response.content_type
assert_equal "text/plain", response.media_type
assert_match(/ActionController::ParameterMissing/, body)
end
@ -257,37 +257,37 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
assert_response 500
assert_no_match(/<header>/, body)
assert_no_match(/<body>/, body)
assert_equal "application/json", response.content_type
assert_equal "application/json", response.media_type
assert_match(/RuntimeError: puke/, body)
get "/not_found", headers: { "action_dispatch.show_exceptions" => true }, as: :json
assert_response 404
assert_no_match(/<body>/, body)
assert_equal "application/json", response.content_type
assert_equal "application/json", response.media_type
assert_match(/#{AbstractController::ActionNotFound.name}/, body)
get "/method_not_allowed", headers: { "action_dispatch.show_exceptions" => true }, as: :json
assert_response 405
assert_no_match(/<body>/, body)
assert_equal "application/json", response.content_type
assert_equal "application/json", response.media_type
assert_match(/ActionController::MethodNotAllowed/, body)
get "/unknown_http_method", headers: { "action_dispatch.show_exceptions" => true }, as: :json
assert_response 405
assert_no_match(/<body>/, body)
assert_equal "application/json", response.content_type
assert_equal "application/json", response.media_type
assert_match(/ActionController::UnknownHttpMethod/, body)
get "/bad_request", headers: { "action_dispatch.show_exceptions" => true }, as: :json
assert_response 400
assert_no_match(/<body>/, body)
assert_equal "application/json", response.content_type
assert_equal "application/json", response.media_type
assert_match(/ActionController::BadRequest/, body)
get "/parameter_missing", headers: { "action_dispatch.show_exceptions" => true }, as: :json
assert_response 400
assert_no_match(/<body>/, body)
assert_equal "application/json", response.content_type
assert_equal "application/json", response.media_type
assert_match(/ActionController::ParameterMissing/, body)
end
@ -298,7 +298,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
assert_response 500
assert_match(/<header>/, body)
assert_match(/<body>/, body)
assert_equal "text/html", response.content_type
assert_equal "text/html", response.media_type
assert_match(/puke/, body)
end
@ -307,7 +307,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
get "/index.xml", headers: { "action_dispatch.show_exceptions" => true }
assert_response 500
assert_equal "application/xml", response.content_type
assert_equal "application/xml", response.media_type
assert_match(/RuntimeError: puke/, body)
end
@ -321,7 +321,7 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
get "/index", headers: { "action_dispatch.show_exceptions" => true }, as: :wibble
assert_response 500
assert_equal "application/json", response.content_type
assert_equal "application/json", response.media_type
assert_match(/RuntimeError: puke/, body)
ensure

View File

@ -290,8 +290,8 @@ class ResponseTest < ActiveSupport::TestCase
resp.to_a
assert_equal("utf-16", resp.charset)
assert_equal(Mime[:xml], resp.content_type)
assert_equal(Mime[:xml], resp.media_type)
assert_equal("application/xml; charset=utf-16", resp.content_type)
assert_equal("application/xml; charset=utf-16", resp.headers["Content-Type"])
end
@ -503,8 +503,8 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal("utf-16", @response.charset)
assert_equal(Mime[:xml], @response.content_type)
assert_equal(Mime[:xml], @response.media_type)
assert_equal("application/xml; charset=utf-16", @response.content_type)
assert_equal("application/xml; charset=utf-16", @response.headers["Content-Type"])
end
@ -519,8 +519,8 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal("utf-16", @response.charset)
assert_equal(Mime[:xml], @response.content_type)
assert_equal(Mime[:xml], @response.media_type)
assert_equal("application/xml; charset=utf-16", @response.content_type)
assert_equal("application/xml; charset=utf-16", @response.headers["Content-Type"])
end
@ -553,7 +553,26 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal("text/csv; charset=utf-16; header=present", @response.headers["Content-Type"])
assert_equal("text/csv", @response.content_type)
assert_equal("text/csv; charset=utf-16; header=present", @response.content_type)
assert_equal("text/csv", @response.media_type)
assert_equal("utf-16", @response.charset)
end
test "response Content-Type with optional parameters that set before charset" do
@app = lambda { |env|
[
200,
{ "Content-Type" => "text/csv; header=present; charset=utf-16" },
["Hello"]
]
}
get "/"
assert_response :success
assert_equal("text/csv; header=present; charset=utf-16", @response.headers["Content-Type"])
assert_equal("text/csv; header=present; charset=utf-16", @response.content_type)
assert_equal("text/csv", @response.media_type)
assert_equal("utf-16", @response.charset)
end
@ -570,7 +589,8 @@ class ResponseIntegrationTest < ActionDispatch::IntegrationTest
assert_response :success
assert_equal('text/csv; header=present; charset="utf-16"', @response.headers["Content-Type"])
assert_equal("text/csv", @response.content_type)
assert_equal('text/csv; header=present; charset="utf-16"', @response.content_type)
assert_equal("text/csv", @response.media_type)
assert_equal("utf-16", @response.charset)
end
end

View File

@ -1,3 +1,6 @@
* Fix `select_tag` so that it doesn't change `options` when `include_blank` is present.
*Younes SERRAJ*
Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/actionview/CHANGELOG.md) for previous changes.

View File

@ -16,18 +16,21 @@ module ActionView
@watched_dirs = nil
@watcher_class = watcher
@watcher = nil
@mutex = Mutex.new
end
def clear_cache_if_necessary
watched_dirs = dirs_to_watch
if watched_dirs != @watched_dirs
@watched_dirs = watched_dirs
@watcher = @watcher_class.new([], watched_dirs) do
clear_cache
@mutex.synchronize do
watched_dirs = dirs_to_watch
if watched_dirs != @watched_dirs
@watched_dirs = watched_dirs
@watcher = @watcher_class.new([], watched_dirs) do
clear_cache
end
@watcher.execute
else
@watcher.execute_if_updated
end
@watcher.execute
else
@watcher.execute_if_updated
end
end

View File

@ -137,7 +137,8 @@ module ActionView
html_name = (options[:multiple] == true && !name.to_s.ends_with?("[]")) ? "#{name}[]" : name
if options.include?(:include_blank)
include_blank = options.delete(:include_blank)
include_blank = options[:include_blank]
options = options.except(:include_blank)
options_for_blank_options_tag = { value: "" }
if include_blank == true

View File

@ -1003,14 +1003,14 @@ class RenderTest < ActionController::TestCase
def test_render_xml
get :render_xml_hello
assert_equal "<html>\n <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", @response.body
assert_equal "application/xml", @response.content_type
assert_equal "application/xml", @response.media_type
end
# :ported:
def test_render_xml_as_string_template
get :render_xml_hello_as_string_template
assert_equal "<html>\n <p>Hello David</p>\n<p>This is grand!</p>\n</html>\n", @response.body
assert_equal "application/xml", @response.content_type
assert_equal "application/xml", @response.media_type
end
# :ported:
@ -1039,7 +1039,7 @@ class RenderTest < ActionController::TestCase
def test_rendered_format_without_format
get :inline_rendered_format_without_format
assert_equal "test", @response.body
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
end
def test_partials_list
@ -1077,7 +1077,7 @@ class RenderTest < ActionController::TestCase
def test_accessing_local_assigns_in_inline_template
get :accessing_local_assigns_in_inline_template, params: { local_name: "Local David" }
assert_equal "Goodbye, Local David", @response.body
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
end
def test_should_implicitly_render_html_template_from_xhr_request
@ -1264,13 +1264,13 @@ class RenderTest < ActionController::TestCase
def test_partial_only
get :partial_only
assert_equal "only partial", @response.body
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
end
def test_should_render_html_formatted_partial
get :partial
assert_equal "partial html", @response.body
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
end
def test_render_html_formatted_partial_even_with_other_mime_time_in_accept
@ -1279,20 +1279,20 @@ class RenderTest < ActionController::TestCase
get :partial_html_erb
assert_equal "partial.html.erb", @response.body.strip
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
end
def test_should_render_html_partial_with_formats
get :partial_formats_html
assert_equal "partial html", @response.body
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
end
def test_render_to_string_partial
get :render_to_string_with_partial
assert_equal "only partial", @controller.instance_variable_get(:@partial_only)
assert_equal "Hello: david", @controller.instance_variable_get(:@partial_with_locals)
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
end
def test_render_to_string_with_template_and_html_partial
@ -1300,21 +1300,21 @@ class RenderTest < ActionController::TestCase
assert_equal "**only partial**\n", @controller.instance_variable_get(:@text)
assert_equal "<strong>only partial</strong>\n", @controller.instance_variable_get(:@html)
assert_equal "<strong>only html partial</strong>\n", @response.body
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
end
def test_render_to_string_and_render_with_different_formats
get :render_to_string_and_render_with_different_formats
assert_equal "<strong>only partial</strong>\n", @controller.instance_variable_get(:@html)
assert_equal "**only partial**\n", @response.body
assert_equal "text/plain", @response.content_type
assert_equal "text/plain", @response.media_type
end
def test_render_template_within_a_template_with_other_format
get :render_template_within_a_template_with_other_format
expected = "only html partial<p>This is grand!</p>"
assert_equal expected, @response.body.strip
assert_equal "text/html", @response.content_type
assert_equal "text/html", @response.media_type
end
def test_partial_with_counter

View File

@ -301,6 +301,13 @@ class FormTagHelperTest < ActionView::TestCase
assert_dom_equal expected, actual
end
def test_select_tag_with_include_blank_doesnt_change_options
options = { include_blank: true, prompt: "string" }
expected_options = options.dup
select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), options
expected_options.each { |k, v| assert_equal v, options[k] }
end
def test_select_tag_with_include_blank_false
actual = select_tag "places", raw("<option>Home</option><option>Work</option><option>Pub</option>"), include_blank: false
expected = %(<select id="places" name="places"><option>Home</option><option>Work</option><option>Pub</option></select>)

View File

@ -22,10 +22,17 @@ module ActiveModel
end
def apply_seconds_precision(value)
return value unless precision && value.respond_to?(:usec)
number_of_insignificant_digits = 6 - precision
return value unless precision && value.respond_to?(:nsec)
number_of_insignificant_digits = 9 - precision
round_power = 10**number_of_insignificant_digits
value.change(usec: value.usec - value.usec % round_power)
rounded_off_nsec = value.nsec % round_power
if rounded_off_nsec > 0
value.change(nsec: value.nsec - rounded_off_nsec)
else
value
end
end
def type_cast_for_schema(value)

View File

@ -1,3 +1,13 @@
* Fix invalid schema when primary key column has a comment
Fixes #29966
*Guilherme Goettems Schneider*
* Fix table comment also being applied to the primary key column
*Guilherme Goettems Schneider*
* Allow generated `create_table` migrations to include or skip timestamps.
*Michael Duchemin*

View File

@ -185,12 +185,14 @@ module ActiveRecord
/ix
def disallow_raw_sql!(args, permit: COLUMN_NAME) # :nodoc:
unexpected = args.reject do |arg|
Arel.arel_node?(arg) ||
unexpected = nil
args.each do |arg|
next if arg.is_a?(Symbol) || Arel.arel_node?(arg) ||
arg.to_s.split(/\s*,\s*/).all? { |part| permit.match?(part) }
(unexpected ||= []) << arg
end
return if unexpected.none?
return unless unexpected
if allow_unsafe_raw_sql == :deprecated
ActiveSupport::Deprecation.warn(
@ -437,7 +439,7 @@ module ActiveRecord
def attributes_for_update(attribute_names)
attribute_names &= self.class.column_names
attribute_names.delete_if do |name|
readonly_attribute?(name)
self.class.readonly_attribute?(name)
end
end
@ -460,10 +462,6 @@ module ActiveRecord
end
end
def readonly_attribute?(name)
self.class.readonly_attributes.include?(name)
end
def pk_attribute?(name)
name == @primary_key
end

View File

@ -3,6 +3,7 @@
require "thread"
require "concurrent/map"
require "monitor"
require "weakref"
module ActiveRecord
# Raised when a connection could not be obtained within the connection
@ -294,28 +295,37 @@ module ActiveRecord
@frequency = frequency
end
@@mutex = Mutex.new
@@pools = {}
@mutex = Mutex.new
@pools = {}
def self.register_pool(pool, frequency) # :nodoc:
@@mutex.synchronize do
if @@pools.key?(frequency)
@@pools[frequency] << pool
else
@@pools[frequency] = [pool]
class << self
def register_pool(pool, frequency) # :nodoc:
@mutex.synchronize do
unless @pools.key?(frequency)
@pools[frequency] = []
spawn_thread(frequency)
end
@pools[frequency] << WeakRef.new(pool)
end
end
private
def spawn_thread(frequency)
Thread.new(frequency) do |t|
loop do
sleep t
@@mutex.synchronize do
@@pools[frequency].each do |p|
@mutex.synchronize do
@pools[frequency].select!(&:weakref_alive?)
@pools[frequency].each do |p|
p.reap
p.flush
rescue WeakRef::RefError
end
end
end
end
end
end
end
def run

View File

@ -264,8 +264,7 @@ module ActiveRecord
if_not_exists: false,
options: nil,
as: nil,
comment: nil,
**
comment: nil
)
@conn = conn
@columns_hash = {}

View File

@ -15,7 +15,7 @@ module ActiveRecord
def column_spec_for_primary_key(column)
return {} if default_primary_key?(column)
spec = { id: schema_type(column).inspect }
spec.merge!(prepare_column_options(column).except!(:null))
spec.merge!(prepare_column_options(column).except!(:null, :comment))
spec[:default] ||= "nil" if explicit_primary_key_default?(column)
spec
end

View File

@ -291,25 +291,25 @@ module ActiveRecord
# SELECT * FROM orders INNER JOIN line_items ON order_id=orders.id
#
# See also TableDefinition#column for details on how to create columns.
def create_table(table_name, **options)
td = create_table_definition(table_name, options)
def create_table(table_name, id: :primary_key, primary_key: nil, force: nil, **options)
td = create_table_definition(
table_name, options.extract!(:temporary, :if_not_exists, :options, :as, :comment)
)
if options[:id] != false && !options[:as]
pk = options.fetch(:primary_key) do
Base.get_primary_key table_name.to_s.singularize
end
if id && !td.as
pk = primary_key || Base.get_primary_key(table_name.to_s.singularize)
if pk.is_a?(Array)
td.primary_keys pk
else
td.primary_key pk, options.fetch(:id, :primary_key), options
td.primary_key pk, id, options
end
end
yield td if block_given?
if options[:force]
drop_table(table_name, options.merge(if_exists: true))
if force
drop_table(table_name, force: force, if_exists: true)
end
result = execute schema_creation.accept td
@ -321,7 +321,7 @@ module ActiveRecord
end
if supports_comments? && !supports_comments_in_create?
if table_comment = options[:comment].presence
if table_comment = td.comment.presence
change_table_comment(table_name, table_comment)
end
@ -518,14 +518,15 @@ module ActiveRecord
# Available options are (none of these exists by default):
# * <tt>:limit</tt> -
# Requests a maximum column length. This is the number of characters for a <tt>:string</tt> column
# and number of bytes for <tt>:text</tt>, <tt>:binary</tt> and <tt>:integer</tt> columns.
# and number of bytes for <tt>:text</tt>, <tt>:binary</tt>, and <tt>:integer</tt> columns.
# This option is ignored by some backends.
# * <tt>:default</tt> -
# The column's default value. Use +nil+ for +NULL+.
# * <tt>:null</tt> -
# Allows or disallows +NULL+ values in the column.
# * <tt>:precision</tt> -
# Specifies the precision for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
# Specifies the precision for the <tt>:decimal</tt>, <tt>:numeric</tt>,
# <tt>:datetime</tt>, and <tt>:time</tt> columns.
# * <tt>:scale</tt> -
# Specifies the scale for the <tt>:decimal</tt> and <tt>:numeric</tt> columns.
# * <tt>:collation</tt> -

View File

@ -93,7 +93,7 @@ module ActiveRecord
# cache_version, but this method can be overwritten to return something else.
#
# Note, this method will return nil if ActiveRecord::Base.cache_versioning is set to
# +false+ (which it is by default until Rails 6.0).
# +false+.
def cache_version
return unless cache_versioning

View File

@ -939,7 +939,7 @@ module ActiveRecord
end
def verify_readonly_attribute(name)
raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attributes.include?(name)
raise ActiveRecordError, "#{name} is marked as readonly" if self.class.readonly_attribute?(name)
end
def _raise_record_not_destroyed

View File

@ -19,6 +19,10 @@ module ActiveRecord
def readonly_attributes
_attr_readonly
end
def readonly_attribute?(name) # :nodoc:
_attr_readonly.include?(name)
end
end
end
end

View File

@ -59,19 +59,26 @@ module ActiveRecord
attribute_names.index_with(time || current_time_from_proper_timezone)
end
def timestamp_attributes_for_create_in_model
@timestamp_attributes_for_create_in_model ||=
(timestamp_attributes_for_create & column_names).freeze
end
def timestamp_attributes_for_update_in_model
@timestamp_attributes_for_update_in_model ||=
(timestamp_attributes_for_update & column_names).freeze
end
def all_timestamp_attributes_in_model
@all_timestamp_attributes_in_model ||=
(timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model).freeze
end
def current_time_from_proper_timezone
default_timezone == :utc ? Time.now.utc : Time.now
end
private
def timestamp_attributes_for_create_in_model
timestamp_attributes_for_create.select { |c| column_names.include?(c) }
end
def timestamp_attributes_for_update_in_model
timestamp_attributes_for_update.select { |c| column_names.include?(c) }
end
def all_timestamp_attributes_in_model
timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model
end
def timestamp_attributes_for_create
["created_at", "created_on"]
end
@ -80,8 +87,11 @@ module ActiveRecord
["updated_at", "updated_on"]
end
def current_time_from_proper_timezone
default_timezone == :utc ? Time.now.utc : Time.now
def reload_schema_from_cache
@timestamp_attributes_for_create_in_model = nil
@timestamp_attributes_for_update_in_model = nil
@all_timestamp_attributes_in_model = nil
super
end
end
@ -124,19 +134,19 @@ module ActiveRecord
end
def timestamp_attributes_for_create_in_model
self.class.send(:timestamp_attributes_for_create_in_model)
self.class.timestamp_attributes_for_create_in_model
end
def timestamp_attributes_for_update_in_model
self.class.send(:timestamp_attributes_for_update_in_model)
self.class.timestamp_attributes_for_update_in_model
end
def all_timestamp_attributes_in_model
self.class.send(:all_timestamp_attributes_in_model)
self.class.all_timestamp_attributes_in_model
end
def current_time_from_proper_timezone
self.class.send(:current_time_from_proper_timezone)
self.class.current_time_from_proper_timezone
end
def max_updated_column_timestamp

View File

@ -37,8 +37,8 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
def test_eager_association_loading_with_hmt_does_not_table_name_collide_when_joining_associations
authors = Author.joins(:posts).eager_load(:comments).where(posts: { tags_count: 1 }).order(:id).to_a
assert_equal 3, assert_no_queries { authors.size }
assert_equal 10, assert_no_queries { authors[0].comments.size }
assert_equal 3, assert_queries(0) { authors.size }
assert_equal 10, assert_queries(0) { authors[0].comments.size }
end
def test_eager_association_loading_grafts_stashed_associations_to_correct_parent
@ -103,14 +103,14 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
firms = Firm.all.merge!(includes: { account: { firm: :account } }, order: "companies.id").to_a
assert_equal 2, firms.size
assert_equal firms.first.account, firms.first.account.firm.account
assert_equal companies(:first_firm).account, assert_no_queries { firms.first.account.firm.account }
assert_equal companies(:first_firm).account.firm.account, assert_no_queries { firms.first.account.firm.account }
assert_equal companies(:first_firm).account, assert_queries(0) { firms.first.account.firm.account }
assert_equal companies(:first_firm).account.firm.account, assert_queries(0) { firms.first.account.firm.account }
end
def test_eager_association_loading_with_has_many_sti
topics = Topic.all.merge!(includes: :replies, order: "topics.id").to_a
first, second, = topics(:first).replies.size, topics(:second).replies.size
assert_no_queries do
assert_queries(0) do
assert_equal first, topics[0].replies.size
assert_equal second, topics[1].replies.size
end
@ -131,13 +131,13 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
replies = Reply.all.merge!(includes: :topic, order: "topics.id").to_a
assert_includes replies, topics(:second)
assert_not_includes replies, topics(:first)
assert_equal topics(:first), assert_no_queries { replies.first.topic }
assert_equal topics(:first), assert_queries(0) { replies.first.topic }
end
def test_eager_association_loading_with_multiple_stis_and_order
author = Author.all.merge!(includes: { posts: [ :special_comments, :very_special_comment ] }, order: ["authors.name", "comments.body", "very_special_comments_posts.body"], where: "posts.id = 4").first
assert_equal authors(:david), author
assert_no_queries do
assert_queries(0) do
author.posts.first.special_comments
author.posts.first.very_special_comment
end
@ -146,7 +146,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
def test_eager_association_loading_of_stis_with_multiple_references
authors = Author.all.merge!(includes: { posts: { special_comments: { post: [ :special_comments, :very_special_comment ] } } }, order: "comments.body, very_special_comments_posts.body", where: "posts.id = 4").to_a
assert_equal [authors(:david)], authors
assert_no_queries do
assert_queries(0) do
authors.first.posts.first.special_comments.first.post.special_comments
authors.first.posts.first.special_comments.first.post.very_special_comment
end
@ -155,14 +155,14 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
def test_eager_association_loading_where_first_level_returns_nil
authors = Author.all.merge!(includes: { post_about_thinking: :comments }, order: "authors.id DESC").to_a
assert_equal [authors(:bob), authors(:mary), authors(:david)], authors
assert_no_queries do
assert_queries(0) do
authors[2].post_about_thinking.comments.first
end
end
def test_preload_through_missing_records
post = Post.where.not(author_id: Author.select(:id)).preload(author: { comments: :post }).first!
assert_no_queries { assert_nil post.author }
assert_queries(0) { assert_nil post.author }
end
def test_eager_association_loading_with_missing_first_record
@ -172,12 +172,12 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase
def test_eager_association_loading_with_recursive_cascading_four_levels_has_many_through
source = Vertex.all.merge!(includes: { sinks: { sinks: { sinks: :sinks } } }, order: "vertices.id").first
assert_equal vertices(:vertex_4), assert_no_queries { source.sinks.first.sinks.first.sinks.first }
assert_equal vertices(:vertex_4), assert_queries(0) { source.sinks.first.sinks.first.sinks.first }
end
def test_eager_association_loading_with_recursive_cascading_four_levels_has_and_belongs_to_many
sink = Vertex.all.merge!(includes: { sources: { sources: { sources: :sources } } }, order: "vertices.id DESC").first
assert_equal vertices(:vertex_1), assert_no_queries { sink.sources.first.sources.first.sources.first.sources.first }
assert_equal vertices(:vertex_1), assert_queries(0) { sink.sources.first.sources.first.sources.first.sources.first }
end
def test_eager_association_loading_with_cascaded_interdependent_one_level_and_two_levels

View File

@ -1245,7 +1245,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
Post.all.merge!(select: "posts.*, authors.name as author_name", includes: :comments, joins: :author, order: "posts.id").to_a
end
assert_equal "David", posts[0].author_name
assert_equal posts(:welcome).comments, assert_no_queries { posts[0].comments }
assert_equal posts(:welcome).comments.sort_by(&:id), assert_no_queries { posts[0].comments.sort_by(&:id) }
end
def test_eager_loading_with_conditions_on_join_model_preloads
@ -1257,8 +1257,8 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
def test_preload_belongs_to_uses_exclusive_scope
people = Person.males.merge(includes: :primary_contact).to_a
assert_not_equal people.length, 0
people = Person.males.includes(:primary_contact).to_a
assert_equal 2, people.length
people.each do |person|
assert_no_queries { assert_not_nil person.primary_contact }
assert_equal Person.find(person.id).primary_contact, person.primary_contact
@ -1267,16 +1267,17 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_preload_has_many_uses_exclusive_scope
people = Person.males.includes(:agents).to_a
assert_equal 2, people.length
people.each do |person|
assert_equal Person.find(person.id).agents, person.agents
assert_equal Person.find(person.id).agents.sort_by(&:id), person.agents.sort_by(&:id)
end
end
def test_preload_has_many_using_primary_key
expected = Firm.first.clients_using_primary_key.to_a
expected = Firm.first.clients_using_primary_key.sort_by(&:id)
firm = Firm.includes(:clients_using_primary_key).first
assert_no_queries do
assert_equal expected, firm.clients_using_primary_key
assert_equal expected, firm.clients_using_primary_key.sort_by(&:id)
end
end

View File

@ -550,7 +550,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
developer = project.developers.first
assert_no_queries do
assert_queries(0) do
assert_predicate project.developers, :loaded?
assert_includes project.developers, developer
end
@ -745,7 +745,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_get_ids_for_loaded_associations
developer = developers(:david)
developer.projects.reload
assert_no_queries do
assert_queries(0) do
developer.project_ids
developer.project_ids
end
@ -873,7 +873,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
def test_has_and_belongs_to_many_associations_on_new_records_use_null_relations
projects = Developer.new.projects
assert_no_queries do
assert_queries(0) do
assert_equal [], projects
assert_equal [], projects.where(title: "omg")
assert_equal [], projects.pluck(:title)

View File

@ -837,7 +837,7 @@ class CalculationsTest < ActiveRecord::TestCase
def test_pluck_columns_with_same_name
expected = [["The First Topic", "The Second Topic of the day"], ["The Third Topic of the day", "The Fourth Topic of the day"]]
actual = Topic.joins(:replies)
actual = Topic.joins(:replies).order(:id)
.pluck("topics.title", "replies_topics.title")
assert_equal expected, actual
end

View File

@ -14,6 +14,9 @@ if ActiveRecord::Base.connection.supports_comments?
class BlankComment < ActiveRecord::Base
end
class PkCommented < ActiveRecord::Base
end
setup do
@connection = ActiveRecord::Base.connection
@ -35,8 +38,13 @@ if ActiveRecord::Base.connection.supports_comments?
t.index :absent_comment
end
@connection.create_table("pk_commenteds", comment: "Table comment", id: false, force: true) do |t|
t.integer :id, comment: "Primary key comment", primary_key: true
end
Commented.reset_column_information
BlankComment.reset_column_information
PkCommented.reset_column_information
end
teardown do
@ -44,6 +52,11 @@ if ActiveRecord::Base.connection.supports_comments?
@connection.drop_table "blank_comments", if_exists: true
end
def test_default_primary_key_comment
column = Commented.columns_hash["id"]
assert_nil column.comment
end
def test_column_created_in_block
column = Commented.columns_hash["name"]
assert_equal :string, column.type
@ -164,5 +177,17 @@ if ActiveRecord::Base.connection.supports_comments?
column = Commented.columns_hash["name"]
assert_nil column.comment
end
def test_comment_on_primary_key
column = PkCommented.columns_hash["id"]
assert_equal "Primary key comment", column.comment
assert_equal "Table comment", @connection.table_comment("pk_commenteds")
end
def test_schema_dump_with_primary_key_comment
output = dump_table_schema "pk_commenteds"
assert_match %r[create_table "pk_commenteds",.*\s+comment: "Table comment"], output
assert_no_match %r[create_table "pk_commenteds",.*\s+comment: "Primary key comment"], output
end
end
end

View File

@ -6,7 +6,7 @@ class Face < ActiveRecord::Base
belongs_to :polymorphic_man, polymorphic: true, inverse_of: :polymorphic_face
# Oracle identifier length is limited to 30 bytes or less, `polymorphic` renamed `poly`
belongs_to :poly_man_without_inverse, polymorphic: true
# These is a "broken" inverse_of for the purposes of testing
# These are "broken" inverse_of associations for the purposes of testing
belongs_to :horrible_man, class_name: "Man", inverse_of: :horrible_face
belongs_to :horrible_polymorphic_man, polymorphic: true, inverse_of: :horrible_polymorphic_face

View File

@ -1,6 +1,15 @@
* Image analysis is skipped if ImageMagick returns an error.
`ActiveStorage::Analyzer::ImageAnalyzer#metadata` would previously raise a
`MiniMagick::Error`, which caused persistent `ActiveStorage::AnalyzeJob`
failures. It now logs the error and returns `{}`, resulting in no metadata
being added to the offending image blob.
*George Claghorn*
* Method calls on singular attachments return `nil` when no file is attached.
Previously, assuming the following User model, `user.avatar.filename` would
Previously, assuming the following User model, `user.avatar.filename` would
raise a `Module::DelegationError` if no avatar was attached:
```ruby
@ -8,7 +17,7 @@
has_one_attached :avatar
end
```
They now return `nil`.
*Matthew Tanous*

View File

@ -43,6 +43,9 @@ module ActiveStorage
rescue LoadError
logger.info "Skipping image analysis because the mini_magick gem isn't installed"
{}
rescue MiniMagick::Error => error
logger.error "Skipping image analysis due to an ImageMagick error: #{error.message}"
{}
end
def rotated_image?(image)

View File

@ -25,4 +25,5 @@
*Jordan Thomas*
Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activesupport/CHANGELOG.md) for previous changes.

View File

@ -1,8 +1,8 @@
# frozen_string_literal: true
class Hash
# Returns a new hash with all keys converted by the block operation.
# This includes the keys from the root hash and from all
# Returns a new hash with all values converted by the block operation.
# This includes the values from the root hash and from all
# nested hashes and arrays.
#
# hash = { person: { name: 'Rob', age: '28' } }

View File

@ -297,7 +297,7 @@ class Module
rescue NoMethodError
if #{target}.nil?
if #{allow_nil == true}
return nil
nil
else
raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil"
end

View File

@ -107,6 +107,7 @@ module ActiveSupport
private
def boot!
normalize_dirs!
Listen.to(*@dtw, &method(:changed)).start
end
@ -114,6 +115,12 @@ module ActiveSupport
Listen.stop
end
def normalize_dirs!
@dirs.transform_keys! do |dir|
dir.exist? ? dir.realpath : dir
end
end
def changed(modified, added, removed)
unless updated?
@updated.make_true if (modified + added + removed).any? { |f| watching?(f) }
@ -187,13 +194,6 @@ module ActiveSupport
lcsp
end
# Returns the deepest existing ascendant, which could be the argument itself.
def existing_parent(dir)
dir.ascend do |ascendant|
break ascendant if ascendant.directory?
end
end
# Filters out directories which are descendants of others in the collection (stable).
def filter_out_descendants(dirs)
return dirs if dirs.length < 2

View File

@ -231,18 +231,16 @@ module ActiveSupport
# ActiveSupport::Notifications.subscribe(/render/) do |event|
# @event = event
# end
def subscribe(*args, &block)
pattern, callback = *args
notifier.subscribe(pattern, callback, false, &block)
def subscribe(pattern = nil, callback = nil, &block)
notifier.subscribe(pattern, callback, monotonic: false, &block)
end
def monotonic_subscribe(*args, &block)
pattern, callback = *args
notifier.subscribe(pattern, callback, true, &block)
def monotonic_subscribe(pattern = nil, callback = nil, &block)
notifier.subscribe(pattern, callback, monotonic: true, &block)
end
def subscribed(callback, pattern, monotonic: false, &block)
subscriber = notifier.subscribe(pattern, callback, monotonic)
def subscribed(callback, pattern = nil, monotonic: false, &block)
subscriber = notifier.subscribe(pattern, callback, monotonic: monotonic)
yield
ensure
unsubscribe(subscriber)

View File

@ -20,8 +20,8 @@ module ActiveSupport
super
end
def subscribe(pattern = nil, callable = nil, monotonic = false, &block)
subscriber = Subscribers.new(monotonic, pattern, callable || block)
def subscribe(pattern = nil, callable = nil, monotonic: false, &block)
subscriber = Subscribers.new(pattern, callable || block, monotonic)
synchronize do
if String === pattern
@string_subscribers[pattern] << subscriber
@ -84,7 +84,7 @@ module ActiveSupport
end
module Subscribers # :nodoc:
def self.new(monotonic, pattern, listener)
def self.new(pattern, listener, monotonic)
subscriber_class = monotonic ? MonotonicTimed : Timed
if listener.respond_to?(:start) && listener.respond_to?(:finish)

View File

@ -27,6 +27,10 @@ module ActiveSupport
@queue << o
end
def length
@queue.length
end
def pop; @queue.pop; end
end
@ -109,6 +113,10 @@ module ActiveSupport
def shutdown
@queue_size.times { @queue << nil }
@pool.each { |pid| Process.waitpid pid }
if @queue.length > 0
raise "Queue not empty, but all workers have finished. This probably means that a worker crashed and #{@queue.length} tests were missed."
end
end
private

View File

@ -6,13 +6,8 @@ require "active_support/core_ext/object/duplicable"
require "active_support/core_ext/numeric/time"
class DuplicableTest < ActiveSupport::TestCase
if RUBY_VERSION >= "2.5.0"
RAISE_DUP = [method(:puts)]
ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("4.56"), nil, false, true, 1, 2.3, Complex(1), Rational(1)]
else
RAISE_DUP = [method(:puts), Complex(1), Rational(1)]
ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("4.56"), nil, false, true, 1, 2.3]
end
RAISE_DUP = [method(:puts)]
ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("4.56"), nil, false, true, 1, 2.3, Complex(1), Rational(1)]
def test_duplicable
rubinius_skip "* Method#dup is allowed at the moment on Rubinius\n" \

View File

@ -481,17 +481,14 @@ class DependenciesTest < ActiveSupport::TestCase
end
end
# This raises only on 2.5.. (warns on ..2.4)
if RUBY_VERSION > "2.5"
def test_access_thru_and_upwards_fails
with_autoloading_fixtures do
assert_not defined?(ModuleFolder)
assert_raise(NameError) { ModuleFolder::Object }
assert_raise(NameError) { ModuleFolder::NestedClass::Object }
end
ensure
remove_constants(:ModuleFolder)
def test_access_thru_and_upwards_fails
with_autoloading_fixtures do
assert_not defined?(ModuleFolder)
assert_raise(NameError) { ModuleFolder::Object }
assert_raise(NameError) { ModuleFolder::NestedClass::Object }
end
ensure
remove_constants(:ModuleFolder)
end
def test_non_existing_const_raises_name_error_with_fully_qualified_name

View File

@ -77,32 +77,48 @@ class EventedFileUpdateCheckerTest < ActiveSupport::TestCase
Process.wait(pid)
end
test "should detect changes through symlink" do
actual_dir = File.join(tmpdir, "actual")
linked_dir = File.join(tmpdir, "linked")
Dir.mkdir(actual_dir)
FileUtils.ln_s(actual_dir, linked_dir)
checker = new_checker([], linked_dir => ".rb") { }
assert_not_predicate checker, :updated?
FileUtils.touch(File.join(actual_dir, "a.rb"))
wait
assert_predicate checker, :updated?
assert checker.execute_if_updated
end
test "updated should become true when nonexistent directory is added later" do
Dir.mktmpdir do |dir|
watched_dir = File.join(dir, "app")
unwatched_dir = File.join(dir, "node_modules")
not_exist_watched_dir = File.join(dir, "test")
watched_dir = File.join(tmpdir, "app")
unwatched_dir = File.join(tmpdir, "node_modules")
not_exist_watched_dir = File.join(tmpdir, "test")
Dir.mkdir(watched_dir)
Dir.mkdir(unwatched_dir)
Dir.mkdir(watched_dir)
Dir.mkdir(unwatched_dir)
checker = new_checker([], watched_dir => ".rb", not_exist_watched_dir => ".rb") { }
checker = new_checker([], watched_dir => ".rb", not_exist_watched_dir => ".rb") { }
FileUtils.touch(File.join(watched_dir, "a.rb"))
wait
assert_predicate checker, :updated?
assert checker.execute_if_updated
FileUtils.touch(File.join(watched_dir, "a.rb"))
wait
assert_predicate checker, :updated?
assert checker.execute_if_updated
Dir.mkdir(not_exist_watched_dir)
wait
assert_predicate checker, :updated?
assert checker.execute_if_updated
Dir.mkdir(not_exist_watched_dir)
wait
assert_predicate checker, :updated?
assert checker.execute_if_updated
FileUtils.touch(File.join(unwatched_dir, "a.rb"))
wait
assert_not_predicate checker, :updated?
assert_not checker.execute_if_updated
end
FileUtils.touch(File.join(unwatched_dir, "a.rb"))
wait
assert_not_predicate checker, :updated?
assert_not checker.execute_if_updated
end
end
@ -156,14 +172,6 @@ class EventedFileUpdateCheckerPathHelperTest < ActiveSupport::TestCase
assert_nil @ph.longest_common_subpath([])
end
test "#existing_parent returns the most specific existing ascendant" do
wd = Pathname.getwd
assert_equal wd, @ph.existing_parent(wd)
assert_equal wd, @ph.existing_parent(wd.join("non-existing/directory"))
assert_equal pn("/"), @ph.existing_parent(pn("/non-existing/directory"))
end
test "#filter_out_descendants returns the same collection if there are no descendants (empty)" do
assert_equal [], @ph.filter_out_descendants([])
end

View File

@ -113,6 +113,24 @@ module Notifications
assert_equal expected, events
end
def test_subscribed_all_messages
name = "foo"
name2 = name * 2
expected = [name, name2, name]
events = []
callback = lambda { |*_| events << _.first }
ActiveSupport::Notifications.subscribed(callback) do
ActiveSupport::Notifications.instrument(name)
ActiveSupport::Notifications.instrument(name2)
ActiveSupport::Notifications.instrument(name)
end
assert_equal expected, events
ActiveSupport::Notifications.instrument(name)
assert_equal expected, events
end
def test_subscribing_to_instrumentation_while_inside_it
# the repro requires that there are no evented subscribers for the "foo" event,
# so we have to duplicate some of the setup code

View File

@ -215,6 +215,10 @@ Please refer to the [Changelog][action-pack] for detailed changes.
### Notable changes
* Change `ActionDispatch::Response#content_type` returning Content-Type
header as it is.
([Pull Request](https://github.com/rails/rails/pull/36034))
* Raise an `ArgumentError` if a resource param contains a colon.
([Pull Request](https://github.com/rails/rails/pull/35236))

View File

@ -1821,6 +1821,21 @@ Client.limit(1).pluck(:name)
# => ["David"]
```
NOTE: You should also know that using `pluck` will trigger eager loading if the relation object contains include values, even if the eager loading is not necessary for the query. For example:
```ruby
# store association for reusing it
assoc = Company.includes(:account)
assoc.pluck(:id)
# SELECT "companies"."id" FROM "companies" LEFT OUTER JOIN "accounts" ON "accounts"."id" = "companies"."account_id"
```
One way to avoid this is to `unscope` the includes:
```ruby
assoc.unscope(:includes).pluck(:id)
```
### `ids`
`ids` can be used to pluck all the IDs for the relation using the table's primary key.

View File

@ -2633,14 +2633,12 @@ The method `stringify_keys` returns a hash that has a stringified version of the
# => {"" => nil, "1" => 1, "a" => :a}
```
In case of key collision, one of the values will be chosen. The chosen value may not always be the same given the same hash:
In case of key collision, the value will be the one most recently inserted into the hash:
```ruby
{"a" => 1, a: 2}.stringify_keys
# The result could either be
# The result will be
# => {"a"=>2}
# or
# => {"a"=>1}
```
This method may be useful for example to easily accept both symbols and strings as options. For instance `ActionView::Helpers::FormHelper` defines:
@ -2677,14 +2675,12 @@ The method `symbolize_keys` returns a hash that has a symbolized version of the
WARNING. Note in the previous example only one key was symbolized.
In case of key collision, one of the values will be chosen. The chosen value may not always be the same given the same hash:
In case of key collision, the value will be the one most recently inserted into the hash:
```ruby
{"a" => 1, a: 2}.symbolize_keys
# The result could either be
# The result will be
# => {:a=>2}
# or
# => {:a=>1}
```
This method may be useful for example to easily accept both symbols and strings as options. For instance `ActionController::UrlRewriter` defines

View File

@ -69,7 +69,7 @@ These configuration methods are to be called on a `Rails::Railtie` object, such
* `config.cache_classes` controls whether or not application classes and modules should be reloaded on each request. Defaults to `false` in development mode, and `true` in test and production modes.
* `config.beginning_of_week` sets the default beginning of week for the
application. Accepts a valid week day symbol (e.g. `:monday`).
application. Accepts a valid day of week as a symbol (e.g. `:monday`).
* `config.cache_store` configures which cache store to use for Rails caching. Options include one of the symbols `:memory_store`, `:file_store`, `:mem_cache_store`, `:null_store`, `:redis_cache_store`, or an object that implements the cache API. Defaults to `:file_store`.

View File

@ -1144,7 +1144,7 @@ test "ajax request" do
get article_url(article), xhr: true
assert_equal 'hello world', @response.body
assert_equal "text/javascript", @response.content_type
assert_equal "text/javascript", @response.media_type
end
```

View File

@ -134,6 +134,28 @@ Action Cable JavaScript API:
+ ActionCable.logger.enabled = false
```
### `ActionDispatch::Response#content_type` now returned Content-Type header as it is.
Previously, `ActionDispatch::Response#content_type` returned value does NOT contain charset part.
This behavior changed to returned Content-Type header containing charset part as it is.
If you want just MIME type, please use `ActionDispatch::Response#media_type` instead.
Before:
```ruby
resp = ActionDispatch::Response.new(200, "Content-Type" => "text/csv; header=present; charset=utf-16")
resp.content_type #=> "text/csv; header=present"
```
After:
```ruby
resp = ActionDispatch::Response.new(200, "Content-Type" => "text/csv; header=present; charset=utf-16")
resp.content_type #=> "text/csv; header=present; charset=utf-16"
resp.media_type #=> "text/csv"
```
### Autoloading
The default configuration for Rails 6

View File

@ -243,7 +243,10 @@ module Rails
# can change in Ruby 1.8.7 when we FileUtils.cd.
RAILS_DEV_PATH = File.expand_path("../../../../../..", __dir__)
class AppGenerator < AppBase # :nodoc:
class AppGenerator < AppBase
# :stopdoc:
WEBPACKS = %w( react vue angular elm stimulus )
add_shared_options_for "application"
@ -492,6 +495,8 @@ module Rails
"rails new #{arguments.map(&:usage).join(' ')} [options]"
end
# :startdoc:
private
# Define file as an alias to create_file for backwards compatibility.

View File

@ -6,7 +6,7 @@
<%%= csp_meta_tag %>
<%- if options[:skip_javascript] -%>
<%%= stylesheet_link_tag 'application', media: 'all' %>
<%%= stylesheet_link_tag 'application', media: 'all' %>
<%- else -%>
<%- unless options[:skip_turbolinks] -%>
<%%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>

View File

@ -564,6 +564,24 @@ module ApplicationTests
assert_no_match "create_table(:users)", output
end
def test_run_in_parallel_with_process_worker_crash
exercise_parallelization_regardless_of_machine_core_count(with: :processes)
file_name = app_file("test/models/parallel_test.rb", <<-RUBY)
require 'test_helper'
class ParallelTest < ActiveSupport::TestCase
def test_crash
Kernel.exit 1
end
end
RUBY
output = run_test_command(file_name)
assert_match %r{Queue not empty, but all workers have finished. This probably means that a worker crashed and 1 tests were missed.}, output
end
def test_run_in_parallel_with_threads
exercise_parallelization_regardless_of_machine_core_count(with: :threads)