diff options
112 files changed, 695 insertions, 536 deletions
diff --git a/.rubocop.yml b/.rubocop.yml index 6eca479d9d..4fb4650bb9 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,5 @@ AllCops: - TargetRubyVersion: 2.4 + TargetRubyVersion: 2.5 # RuboCop has a bunch of cops enabled by default. This setting tells RuboCop # to ignore them, so only the ones explicitly set in this file are enabled. DisabledByDefault: true diff --git a/.travis.yml b/.travis.yml index 6acba41c0e..2e00860c95 100644 --- a/.travis.yml +++ b/.travis.yml @@ -64,21 +64,11 @@ env: - "GEM=ac:integration" rvm: - - 2.4.5 - 2.5.3 - ruby-head matrix: include: - - rvm: 2.4.5 - env: "GEM=railties" - sudo: required - before_install: - - "rm ${BUNDLE_GEMFILE}.lock" - - "travis_retry gem update --system" - - "travis_retry gem install bundler" - - "sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/10/main/postgresql.conf" - - "sudo service postgresql restart 10" - rvm: 2.5.3 env: "GEM=railties" sudo: required @@ -99,19 +89,6 @@ matrix: - "sudo service postgresql restart 10" - rvm: 2.5.3 env: "GEM=av:ujs" - - rvm: 2.4.5 - sudo: required - env: "GEM=aj:integration" - services: - - memcached - - redis-server - - rabbitmq - before_install: - - sudo sed -i -e '/local.*peer/s/postgres/all/' -e 's/peer\|md5/trust/g' /etc/postgresql/*/main/pg_hba.conf - - "sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/10/main/postgresql.conf" - - "sudo service postgresql restart 10" - - "[ -f /tmp/beanstalkd-1.10/Makefile ] || (curl -L https://github.com/beanstalkd/beanstalkd/archive/v1.10.tar.gz | tar xz -C /tmp)" - - "pushd /tmp/beanstalkd-1.10 && make && (./beanstalkd &); popd" - rvm: 2.5.3 sudo: required env: "GEM=aj:integration" @@ -138,13 +115,6 @@ matrix: - "sudo service postgresql restart 10" - "[ -f /tmp/beanstalkd-1.10/Makefile ] || (curl -L https://github.com/beanstalkd/beanstalkd/archive/v1.10.tar.gz | tar xz -C /tmp)" - "pushd /tmp/beanstalkd-1.10 && make && (./beanstalkd &); popd" - - rvm: 2.4.5 - env: "GEM=ar:mysql2" - sudo: required - before_install: - - "sudo mysql -e \"use mysql; update user set authentication_string='' where User='root'; update user set plugin='mysql_native_password';FLUSH PRIVILEGES;\"" - - "sudo mysql_upgrade" - - "sudo service mysql restart" - rvm: 2.5.3 env: "GEM=ar:mysql2" sudo: required @@ -167,12 +137,6 @@ matrix: - rvm: 2.5.3 env: - "GEM=ar:sqlite3_mem" - - rvm: 2.4.5 - env: "GEM=ar:postgresql" - sudo: required - before_install: - - "sudo sed -i 's/port = 5433/port = 5432/' /etc/postgresql/10/main/postgresql.conf" - - "sudo service postgresql restart 10" - rvm: 2.5.3 env: "GEM=ar:postgresql" sudo: required diff --git a/Gemfile.lock b/Gemfile.lock index 368e5ce01c..cfa64ee24c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -382,7 +382,7 @@ GEM redis (4.0.3) redis-namespace (1.6.0) redis (>= 3.0.4) - regexp_parser (1.2.0) + regexp_parser (1.3.0) representable (3.0.4) declarative (< 0.1.0) declarative-option (< 0.2.0) diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md index 88a74a521f..162de0df0b 100644 --- a/actioncable/CHANGELOG.md +++ b/actioncable/CHANGELOG.md @@ -66,9 +66,9 @@ *Ilia Kasianenko* -* Rails 6 requires Ruby 2.4.1 or newer. +* Rails 6 requires Ruby 2.5.0 or newer. - *Jeremy Daer* + *Jeremy Daer*, *Kasper Timm Hansen* Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/actioncable/CHANGELOG.md) for previous changes. diff --git a/actioncable/actioncable.gemspec b/actioncable/actioncable.gemspec index 31c6fb41f2..29836f012f 100644 --- a/actioncable/actioncable.gemspec +++ b/actioncable/actioncable.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.summary = "WebSocket framework for Rails." s.description = "Structure many real-time application concerns into channels over a single WebSocket connection." - s.required_ruby_version = ">= 2.4.1" + s.required_ruby_version = ">= 2.5.0" s.license = "MIT" diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index 2de83e611b..5f3fc44e6e 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -57,9 +57,9 @@ *Claudio Ortolina*, *Kota Miyake* -* Rails 6 requires Ruby 2.4.1 or newer. +* Rails 6 requires Ruby 2.5.0 or newer. - *Jeremy Daer* + *Jeremy Daer*, *Kasper Timm Hansen* Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/actionmailer/CHANGELOG.md) for previous changes. diff --git a/actionmailer/actionmailer.gemspec b/actionmailer/actionmailer.gemspec index 6f8d6d0699..a6c482f1a0 100644 --- a/actionmailer/actionmailer.gemspec +++ b/actionmailer/actionmailer.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.summary = "Email composition, delivery, and receiving framework (part of Rails)." s.description = "Email on Rails. Compose, deliver, receive, and test emails using the familiar controller/view pattern. First-class support for multipart email and attachments." - s.required_ruby_version = ">= 2.4.1" + s.required_ruby_version = ">= 2.5.0" s.license = "MIT" diff --git a/actionmailer/test/base_test.rb b/actionmailer/test/base_test.rb index 86c0172772..5fb93b2a59 100644 --- a/actionmailer/test/base_test.rb +++ b/actionmailer/test/base_test.rb @@ -90,18 +90,18 @@ class BaseTest < ActiveSupport::TestCase test "can pass random headers in as a hash to mail" do hash = { "X-Special-Domain-Specific-Header" => "SecretValue", - "In-Reply-To" => "1234@mikel.me.com" } + "In-Reply-To" => "<1234@mikel.me.com>" } mail = BaseMailer.welcome(hash) assert_equal("SecretValue", mail["X-Special-Domain-Specific-Header"].decoded) - assert_equal("1234@mikel.me.com", mail["In-Reply-To"].decoded) + assert_equal("<1234@mikel.me.com>", mail["In-Reply-To"].decoded) end test "can pass random headers in as a hash to headers" do hash = { "X-Special-Domain-Specific-Header" => "SecretValue", - "In-Reply-To" => "1234@mikel.me.com" } + "In-Reply-To" => "<1234@mikel.me.com>" } mail = BaseMailer.welcome_with_headers(hash) assert_equal("SecretValue", mail["X-Special-Domain-Specific-Header"].decoded) - assert_equal("1234@mikel.me.com", mail["In-Reply-To"].decoded) + assert_equal("<1234@mikel.me.com>", mail["In-Reply-To"].decoded) end # Attachments diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 740c6db06f..95da4265a4 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,31 @@ +* Introduce ActionDispatch::HostAuthorization + + This is a new middleware that guards against DNS rebinding attacks by + white-listing the allowed hosts a request can be made to. + + Each host is checked with the case operator (`#===`) to support `RegExp`, + `Proc`, `IPAddr` and custom objects as host allowances. + + *Genadi Samokovarov* + +* Allow using `parsed_body` in `ActionController::TestCase`. + + In addition to `ActionDispatch::IntegrationTest`, allow using + `parsed_body` in `ActionController::TestCase`: + + ``` + class SomeControllerTest < ActionController::TestCase + def test_some_action + post :action, body: { foo: 'bar' } + assert_equal({ "foo" => "bar" }, response.parsed_body) + end + end + ``` + + Fixes #34676. + + *Tobias Bühlmann* + * Raise an error on root route naming conflicts. Raises an ArgumentError when multiple root routes are defined in the @@ -165,9 +193,9 @@ *Derek Prior* -* Rails 6 requires Ruby 2.4.1 or newer. +* Rails 6 requires Ruby 2.5.0 or newer. - *Jeremy Daer* + *Jeremy Daer*, *Kasper Timm Hansen* Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/actionpack/CHANGELOG.md) for previous changes. diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index ec56de18f1..c4c755b7ac 100644 --- a/actionpack/actionpack.gemspec +++ b/actionpack/actionpack.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.summary = "Web-flow and rendering framework putting the VC in MVC (part of Rails)." s.description = "Web apps on Rails. Simple, battle-tested conventions for building and testing MVC web applications. Works with any Rack-compatible server." - s.required_ruby_version = ">= 2.4.1" + s.required_ruby_version = ">= 2.5.0" s.license = "MIT" diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index d6911ee2b5..29d1919ec5 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "active_support/core_ext/hash/keys" - module ActionController module ConditionalGet extend ActiveSupport::Concern diff --git a/actionpack/lib/action_controller/renderer.rb b/actionpack/lib/action_controller/renderer.rb index 2b4559c760..cf8c0159e2 100644 --- a/actionpack/lib/action_controller/renderer.rb +++ b/actionpack/lib/action_controller/renderer.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "active_support/core_ext/hash/keys" - module ActionController # ActionController::Renderer allows you to render arbitrary templates # without requirement of being in controller actions. diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index 5d784ceb31..a643484d96 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -457,7 +457,7 @@ module ActionController # respectively which will make tests more expressive. # # Note that the request method is not verified. - def process(action, method: "GET", params: {}, session: nil, body: nil, flash: {}, format: nil, xhr: false, as: nil) + def process(action, method: "GET", params: nil, session: nil, body: nil, flash: {}, format: nil, xhr: false, as: nil) check_required_ivars http_method = method.to_s.upcase @@ -485,7 +485,7 @@ module ActionController format ||= as end - parameters = params.symbolize_keys + parameters = (params || {}).symbolize_keys if format parameters[:format] = format diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb index 0822cdc0a6..a92d336214 100644 --- a/actionpack/lib/action_dispatch.rb +++ b/actionpack/lib/action_dispatch.rb @@ -49,11 +49,13 @@ module ActionDispatch end autoload_under "middleware" do + autoload :HostAuthorization autoload :RequestId autoload :Callbacks autoload :Cookies autoload :DebugExceptions autoload :DebugLocks + autoload :DebugView autoload :ExceptionWrapper autoload :Executor autoload :Flash diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index 7669767ae3..eadb59173d 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -3,53 +3,14 @@ require "action_dispatch/http/request" require "action_dispatch/middleware/exception_wrapper" require "action_dispatch/routing/inspector" + require "action_view" require "action_view/base" -require "pp" - module ActionDispatch # This middleware is responsible for logging exceptions and # showing a debugging page in case the request is local. class DebugExceptions - RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__) - - class DebugView < ActionView::Base - def debug_params(params) - clean_params = params.clone - clean_params.delete("action") - clean_params.delete("controller") - - if clean_params.empty? - "None" - else - PP.pp(clean_params, +"", 200) - end - end - - def debug_headers(headers) - if headers.present? - headers.inspect.gsub(",", ",\n") - else - "None" - end - end - - def debug_hash(object) - object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n") - end - - def render(*) - logger = ActionView::Base.logger - - if logger && logger.respond_to?(:silence) - logger.silence { super } - else - super - end - end - end - cattr_reader :interceptors, instance_accessor: false, default: [] def self.register_interceptor(object = nil, &block) @@ -152,7 +113,7 @@ module ActionDispatch end def create_template(request, wrapper) - DebugView.new([RESCUES_TEMPLATE_PATH], + DebugView.new( request: request, exception_wrapper: wrapper, exception: wrapper.exception, diff --git a/actionpack/lib/action_dispatch/middleware/debug_view.rb b/actionpack/lib/action_dispatch/middleware/debug_view.rb new file mode 100644 index 0000000000..ac12dc13a1 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/debug_view.rb @@ -0,0 +1,50 @@ +# frozen_string_literal: true + +require "pp" + +require "action_view" +require "action_view/base" + +module ActionDispatch + class DebugView < ActionView::Base # :nodoc: + RESCUES_TEMPLATE_PATH = File.expand_path("templates", __dir__) + + def initialize(assigns) + super([RESCUES_TEMPLATE_PATH], assigns) + end + + def debug_params(params) + clean_params = params.clone + clean_params.delete("action") + clean_params.delete("controller") + + if clean_params.empty? + "None" + else + PP.pp(clean_params, +"", 200) + end + end + + def debug_headers(headers) + if headers.present? + headers.inspect.gsub(",", ",\n") + else + "None" + end + end + + def debug_hash(object) + object.to_hash.sort_by { |k, _| k.to_s }.map { |k, v| "#{k}: #{v.inspect rescue $!.message}" }.join("\n") + end + + def render(*) + logger = ActionView::Base.logger + + if logger && logger.respond_to?(:silence) + logger.silence { super } + else + super + end + end + end +end diff --git a/actionpack/lib/action_dispatch/middleware/host_authorization.rb b/actionpack/lib/action_dispatch/middleware/host_authorization.rb new file mode 100644 index 0000000000..48f7c25216 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/host_authorization.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +require "action_dispatch/http/request" + +module ActionDispatch + # This middleware guards from DNS rebinding attacks by white-listing the + # hosts a request can be sent to. + # + # When a request comes to an unauthorized host, the +response_app+ + # application will be executed and rendered. If no +response_app+ is given, a + # default one will run, which responds with +403 Forbidden+. + class HostAuthorization + class Permissions # :nodoc: + def initialize(hosts) + @hosts = sanitize_hosts(hosts) + end + + def empty? + @hosts.empty? + end + + def allows?(host) + @hosts.any? do |allowed| + begin + allowed === host + rescue + # IPAddr#=== raises an error if you give it a hostname instead of + # IP. Treat similar errors as blocked access. + false + end + end + end + + private + + def sanitize_hosts(hosts) + Array(hosts).map do |host| + case host + when Regexp then sanitize_regexp(host) + when String then sanitize_string(host) + else host + end + end + end + + def sanitize_regexp(host) + /\A#{host}\z/ + end + + def sanitize_string(host) + if host.start_with?(".") + /\A(.+\.)?#{Regexp.escape(host[1..-1])}\z/ + else + host + end + end + end + + DEFAULT_RESPONSE_APP = -> env do + request = Request.new(env) + + format = request.xhr? ? "text/plain" : "text/html" + template = DebugView.new(host: request.host) + body = template.render(template: "rescues/blocked_host", layout: "rescues/layout") + + [403, { + "Content-Type" => "#{format}; charset=#{Response.default_charset}", + "Content-Length" => body.bytesize.to_s, + }, [body]] + end + + def initialize(app, hosts, response_app = nil) + @app = app + @permissions = Permissions.new(hosts) + @response_app = response_app || DEFAULT_RESPONSE_APP + end + + def call(env) + return @app.call(env) if @permissions.empty? + + request = Request.new(env) + + if authorized?(request) + mark_as_authorized(request) + @app.call(env) + else + @response_app.call(env) + end + end + + private + + def authorized?(request) + origin_host = request.get_header("HTTP_HOST").to_s.sub(/:\d+\z/, "") + forwarded_host = request.x_forwarded_host.to_s.split(/,\s?/).last.to_s.sub(/:\d+\z/, "") + + @permissions.allows?(origin_host) && + (forwarded_host.blank? || @permissions.allows?(forwarded_host)) + end + + def mark_as_authorized(request) + request.set_header("action_dispatch.authorized_host", request.host) + end + end +end diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb new file mode 100644 index 0000000000..2fa78dd385 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.html.erb @@ -0,0 +1,7 @@ +<header> + <h1>Blocked host: <%= @host %></h1> +</header> +<div id="container"> + <h2>To allow requests to <%= @host %>, add the following configuration:</h2> + <pre>Rails.application.config.hosts << "<%= @host %>"</pre> +</div> diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb new file mode 100644 index 0000000000..4e2d1d0b08 --- /dev/null +++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/blocked_host.text.erb @@ -0,0 +1,5 @@ +Blocked host: <%= @host %> + +To allow requests to <%= @host %>, add the following configuration: + + Rails.application.config.hosts << "<%= @host %>" diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 2ae75b0da8..2966c969f6 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -90,11 +90,11 @@ module ActionDispatch def clear! @path_helpers.each do |helper| - @path_helpers_module.send :remove_method, helper + @path_helpers_module.remove_method helper end @url_helpers.each do |helper| - @url_helpers_module.send :remove_method, helper + @url_helpers_module.remove_method helper end @routes.clear @@ -108,8 +108,8 @@ module ActionDispatch url_name = :"#{name}_url" if routes.key? key - @path_helpers_module.send :undef_method, path_name - @url_helpers_module.send :undef_method, url_name + @path_helpers_module.undef_method path_name + @url_helpers_module.undef_method url_name end routes[key] = route define_url_helper @path_helpers_module, route, path_name, route.defaults, name, PATH diff --git a/actionpack/lib/action_dispatch/testing/test_response.rb b/actionpack/lib/action_dispatch/testing/test_response.rb index 1e6b21f235..7c1202dc0e 100644 --- a/actionpack/lib/action_dispatch/testing/test_response.rb +++ b/actionpack/lib/action_dispatch/testing/test_response.rb @@ -14,11 +14,6 @@ module ActionDispatch new response.status, response.headers, response.body end - def initialize(*) # :nodoc: - super - @response_parser = RequestEncoder.parser(content_type) - end - # Was the response successful? def success? ActiveSupport::Deprecation.warn(<<-MSG.squish) @@ -47,7 +42,11 @@ module ActionDispatch end def parsed_body - @parsed_body ||= @response_parser.call(body) + @parsed_body ||= response_parser.call(body) + end + + def response_parser + @response_parser ||= RequestEncoder.parser(content_type) end end end diff --git a/actionpack/test/abstract/collector_test.rb b/actionpack/test/abstract/collector_test.rb index a4770b66e1..6db045fcd7 100644 --- a/actionpack/test/abstract/collector_test.rb +++ b/actionpack/test/abstract/collector_test.rb @@ -30,7 +30,7 @@ module AbstractController end test "register mime types on method missing" do - AbstractController::Collector.send(:remove_method, :js) + AbstractController::Collector.remove_method :js begin collector = MyCollector.new assert_not_respond_to collector, :js diff --git a/actionpack/test/controller/filters_test.rb b/actionpack/test/controller/filters_test.rb index 425a6e25cc..8e117528e2 100644 --- a/actionpack/test/controller/filters_test.rb +++ b/actionpack/test/controller/filters_test.rb @@ -457,6 +457,7 @@ class FilterTest < ActionController::TestCase prepend_before_action :before_all prepend_after_action :after_all before_action :between_before_all_and_after_all + after_action :between_before_all_and_after_all def before_all @ran_filter ||= [] @@ -472,6 +473,7 @@ class FilterTest < ActionController::TestCase @ran_filter ||= [] @ran_filter << "between_before_all_and_after_all" end + def show render plain: "hello" end @@ -765,7 +767,7 @@ class FilterTest < ActionController::TestCase def test_running_prepended_before_and_after_action test_process(PrependingBeforeAndAfterController) - assert_equal %w( before_all between_before_all_and_after_all after_all ), @controller.instance_variable_get(:@ran_filter) + assert_equal %w( before_all between_before_all_and_after_all between_before_all_and_after_all after_all ), @controller.instance_variable_get(:@ran_filter) end def test_skipping_and_limiting_controller diff --git a/actionpack/test/controller/integration_test.rb b/actionpack/test/controller/integration_test.rb index 39ede1442a..b5503a9c64 100644 --- a/actionpack/test/controller/integration_test.rb +++ b/actionpack/test/controller/integration_test.rb @@ -152,7 +152,7 @@ class IntegrationTestTest < ActiveSupport::TestCase assert_equal "pass", @test.foo ensure # leave other tests as unaffected as possible - mixin.__send__(:remove_method, :method_missing) + mixin.remove_method :method_missing end end end diff --git a/actionpack/test/controller/test_case_test.rb b/actionpack/test/controller/test_case_test.rb index 6fc70d6248..d1cd190747 100644 --- a/actionpack/test/controller/test_case_test.rb +++ b/actionpack/test/controller/test_case_test.rb @@ -156,6 +156,10 @@ XML render html: '<body class="foo"></body>'.html_safe end + def render_json + render json: request.raw_post + end + def boom raise "boom!" end @@ -474,6 +478,18 @@ XML ) end + def test_nil_params + get :test_params, params: nil + parsed_params = JSON.parse(@response.body) + assert_equal( + { + "action" => "test_params", + "controller" => "test_case_test/test" + }, + parsed_params + ) + end + def test_query_param_named_action get :test_query_parameters, params: { action: "foobar" } parsed_params = JSON.parse(@response.body) @@ -965,6 +981,16 @@ XML assert_equal "q=test2", @response.body end + + def test_parsed_body_without_as_option + post :render_json, body: { foo: "heyo" } + assert_equal({ "foo" => "heyo" }, response.parsed_body) + end + + def test_parsed_body_with_as_option + post :render_json, body: { foo: "heyo" }.to_json, as: :json + assert_equal({ "foo" => "heyo" }, response.parsed_body) + end end class ResponseDefaultHeadersTest < ActionController::TestCase diff --git a/actionpack/test/dispatch/host_authorization_test.rb b/actionpack/test/dispatch/host_authorization_test.rb new file mode 100644 index 0000000000..dcb59ddb94 --- /dev/null +++ b/actionpack/test/dispatch/host_authorization_test.rb @@ -0,0 +1,160 @@ +# frozen_string_literal: true + +require "abstract_unit" + +class HostAuthorizationTest < ActionDispatch::IntegrationTest + App = -> env { [200, {}, %w(Success)] } + + test "blocks requests to unallowed host" do + @app = ActionDispatch::HostAuthorization.new(App, %w(only.com)) + + get "/" + + assert_response :forbidden + assert_match "Blocked host: www.example.com", response.body + end + + test "passes all requests to if the whitelist is empty" do + @app = ActionDispatch::HostAuthorization.new(App, nil) + + get "/" + + assert_response :ok + assert_equal "Success", body + end + + test "passes requests to allowed host" do + @app = ActionDispatch::HostAuthorization.new(App, %w(www.example.com)) + + get "/" + + assert_response :ok + assert_equal "Success", body + end + + test "the whitelist could be a single element" do + @app = ActionDispatch::HostAuthorization.new(App, "www.example.com") + + get "/" + + assert_response :ok + assert_equal "Success", body + end + + test "passes requests to allowed hosts with domain name notation" do + @app = ActionDispatch::HostAuthorization.new(App, ".example.com") + + get "/" + + assert_response :ok + assert_equal "Success", body + end + + test "does not allow domain name notation in the HOST header itself" do + @app = ActionDispatch::HostAuthorization.new(App, ".example.com") + + get "/", env: { + "HOST" => ".example.com", + } + + assert_response :forbidden + assert_match "Blocked host: .example.com", response.body + end + + test "checks for requests with #=== to support wider range of host checks" do + @app = ActionDispatch::HostAuthorization.new(App, [-> input { input == "www.example.com" }]) + + get "/" + + assert_response :ok + assert_equal "Success", body + end + + test "mark the host when authorized" do + @app = ActionDispatch::HostAuthorization.new(App, ".example.com") + + get "/" + + assert_equal "www.example.com", request.get_header("action_dispatch.authorized_host") + end + + test "sanitizes regular expressions to prevent accidental matches" do + @app = ActionDispatch::HostAuthorization.new(App, [/w.example.co/]) + + get "/" + + assert_response :forbidden + assert_match "Blocked host: www.example.com", response.body + end + + test "blocks requests to unallowed host supporting custom responses" do + @app = ActionDispatch::HostAuthorization.new(App, ["w.example.co"], -> env do + [401, {}, %w(Custom)] + end) + + get "/" + + assert_response :unauthorized + assert_equal "Custom", body + end + + test "blocks requests with spoofed X-FORWARDED-HOST" do + @app = ActionDispatch::HostAuthorization.new(App, [IPAddr.new("127.0.0.1")]) + + get "/", env: { + "HTTP_X_FORWARDED_HOST" => "127.0.0.1", + "HOST" => "www.example.com", + } + + assert_response :forbidden + assert_match "Blocked host: 127.0.0.1", response.body + end + + test "does not consider IP addresses in X-FORWARDED-HOST spoofed when disabled" do + @app = ActionDispatch::HostAuthorization.new(App, nil) + + get "/", env: { + "HTTP_X_FORWARDED_HOST" => "127.0.0.1", + "HOST" => "www.example.com", + } + + assert_response :ok + assert_equal "Success", body + end + + test "detects localhost domain spoofing" do + @app = ActionDispatch::HostAuthorization.new(App, "localhost") + + get "/", env: { + "HTTP_X_FORWARDED_HOST" => "localhost", + "HOST" => "www.example.com", + } + + assert_response :forbidden + assert_match "Blocked host: localhost", response.body + end + + test "forwarded hosts should be permitted" do + @app = ActionDispatch::HostAuthorization.new(App, "domain.com") + + get "/", env: { + "HTTP_X_FORWARDED_HOST" => "sub.domain.com", + "HOST" => "domain.com", + } + + assert_response :forbidden + assert_match "Blocked host: sub.domain.com", response.body + end + + test "forwarded hosts are allowed when permitted" do + @app = ActionDispatch::HostAuthorization.new(App, ".domain.com") + + get "/", env: { + "HTTP_X_FORWARDED_HOST" => "sub.domain.com", + "HOST" => "domain.com", + } + + assert_response :ok + assert_equal "Success", body + end +end diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 0f37d074af..60817c1c4d 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -42,7 +42,7 @@ class ResponseTest < ActiveSupport::TestCase def test_each_isnt_called_if_str_body_is_written # Controller writes and reads response body each_counter = 0 - @response.body = Object.new.tap { |o| o.singleton_class.send(:define_method, :each) { |&block| each_counter += 1; block.call "foo" } } + @response.body = Object.new.tap { |o| o.singleton_class.define_method(:each) { |&block| each_counter += 1; block.call "foo" } } @response["X-Foo"] = @response.body assert_equal 1, each_counter, "#each was not called once" diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index f32bc3ab40..df4036a5a7 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,9 @@ +* Fix UJS permanently showing disabled text in a[data-remote][data-disable-with] elements within forms. + Fixes #33889 + + *Wolfgang Hobmaier* + + * Prevent non-primary mouse keys from triggering Rails UJS click handlers. Firefox fires click events even if the click was triggered by non-primary mouse keys such as right- or scroll-wheel-clicks. For example, right-clicking a link such as the one described below (with an underlying ajax request registered on click) should not cause that request to occur. @@ -169,9 +175,9 @@ *Rui Onodera* -* Rails 6 requires Ruby 2.4.1 or newer. +* Rails 6 requires Ruby 2.5.0 or newer. - *Jeremy Daer* + *Jeremy Daer*, *Kasper Timm Hansen* Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/actionview/CHANGELOG.md) for previous changes. diff --git a/actionview/actionview.gemspec b/actionview/actionview.gemspec index 5f1e746421..d8bd233ceb 100644 --- a/actionview/actionview.gemspec +++ b/actionview/actionview.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.summary = "Rendering framework putting the V in MVC (part of Rails)." s.description = "Simple, battle-tested conventions and helpers for building web pages." - s.required_ruby_version = ">= 2.4.1" + s.required_ruby_version = ">= 2.5.0" s.license = "MIT" diff --git a/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee b/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee index a8c692ee62..4cfaead078 100644 --- a/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee +++ b/actionview/app/assets/javascripts/rails-ujs/features/disable.coffee @@ -34,6 +34,7 @@ Rails.disableElement = (e) -> # Replace element's html with the 'data-disable-with' after storing original html # and prevent clicking on it disableLinkElement = (element) -> + return if getData(element, 'ujs:disabled') replacement = element.getAttribute('data-disable-with') if replacement? setData(element, 'ujs:enable-with', element.innerHTML) # store enabled state @@ -58,6 +59,7 @@ disableFormElements = (form) -> formElements(form, Rails.formDisableSelector).forEach(disableFormElement) disableFormElement = (element) -> + return if getData(element, 'ujs:disabled') replacement = element.getAttribute('data-disable-with') if replacement? if matches(element, 'button') diff --git a/actionview/lib/action_view/helpers/text_helper.rb b/actionview/lib/action_view/helpers/text_helper.rb index 3d378dcb2f..c282505e13 100644 --- a/actionview/lib/action_view/helpers/text_helper.rb +++ b/actionview/lib/action_view/helpers/text_helper.rb @@ -228,7 +228,7 @@ module ActionView # pluralize(2, 'Person', locale: :de) # # => 2 Personen def pluralize(count, singular, plural_arg = nil, plural: plural_arg, locale: I18n.locale) - word = if count == 1 || count =~ /^1(\.0+)?$/ + word = if count == 1 || count.to_s =~ /^1(\.0+)?$/ singular else plural || singular.pluralize(locale) diff --git a/actionview/lib/action_view/lookup_context.rb b/actionview/lib/action_view/lookup_context.rb index af67ffa12d..554d223c0e 100644 --- a/actionview/lib/action_view/lookup_context.rb +++ b/actionview/lib/action_view/lookup_context.rb @@ -24,7 +24,7 @@ module ActionView registered_details << name Accessors::DEFAULT_PROCS[name] = block - Accessors.send :define_method, :"default_#{name}", &block + Accessors.define_method(:"default_#{name}", &block) Accessors.module_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{name} @details.fetch(:#{name}, []) diff --git a/actionview/lib/action_view/rendering.rb b/actionview/lib/action_view/rendering.rb index 4e5fdfbb2d..cb4327cf16 100644 --- a/actionview/lib/action_view/rendering.rb +++ b/actionview/lib/action_view/rendering.rb @@ -64,10 +64,11 @@ module ActionView # An instance of a view class. The default view class is ActionView::Base. # # The view class must have the following methods: - # View.new[lookup_context, assigns, controller] - # Create a new ActionView instance for a controller and we can also pass the arguments. - # View#render(option) - # Returns String with the rendered template + # + # * <tt>View.new(lookup_context, assigns, controller)</tt> — Create a new + # ActionView instance for a controller and we can also pass the arguments. + # + # * <tt>View#render(option)</tt> — Returns String with the rendered template. # # Override this method in a module to change the default behavior. def view_context diff --git a/actionview/lib/action_view/template/handlers/erb.rb b/actionview/lib/action_view/template/handlers/erb.rb index 270be0a380..7d1a6767d7 100644 --- a/actionview/lib/action_view/template/handlers/erb.rb +++ b/actionview/lib/action_view/template/handlers/erb.rb @@ -17,8 +17,8 @@ module ActionView class_attribute :escape_ignore_list, default: ["text/plain"] [self, singleton_class].each do |base| - base.send(:alias_method, :escape_whitelist, :escape_ignore_list) - base.send(:alias_method, :escape_whitelist=, :escape_ignore_list=) + base.alias_method :escape_whitelist, :escape_ignore_list + base.alias_method :escape_whitelist=, :escape_ignore_list= base.deprecate( escape_whitelist: "use #escape_ignore_list instead", diff --git a/actionview/test/template/form_options_helper_test.rb b/actionview/test/template/form_options_helper_test.rb index a2d1474a94..4ccd3ae336 100644 --- a/actionview/test/template/form_options_helper_test.rb +++ b/actionview/test/template/form_options_helper_test.rb @@ -36,6 +36,7 @@ class FormOptionsHelperTest < ActionView::TestCase module FakeZones FakeZone = Struct.new(:name) do def to_s; name; end + def =~(_re); end end module ClassMethods diff --git a/actionview/test/ujs/public/test/data-disable-with.js b/actionview/test/ujs/public/test/data-disable-with.js index 0654484711..10b8870171 100644 --- a/actionview/test/ujs/public/test/data-disable-with.js +++ b/actionview/test/ujs/public/test/data-disable-with.js @@ -95,6 +95,27 @@ asyncTest('form button with "data-disable-with" attribute', 6, function() { App.checkDisabledState(button, 'submitting ...') }) +asyncTest('a[data-remote][data-disable-with] within a form disables and re-enables', 6, function() { + var form = $('form:not([data-remote])'), + link = $('<a data-remote="true" data-disable-with="clicking...">Click me</a>') + form.append(link) + + App.checkEnabledState(link, 'Click me') + + link + .bindNative('ajax:beforeSend', function() { + App.checkDisabledState(link, 'clicking...') + }) + .bindNative('ajax:complete', function() { + setTimeout( function() { + App.checkEnabledState(link, 'Click me') + link.remove() + start() + }, 15) + }) + .triggerNative('click') +}) + asyncTest('form input[type=submit][data-disable-with] disables', 6, function() { var form = $('form:not([data-remote])'), input = form.find('input[type=submit]') diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index 4acbe9a4e9..31253855d7 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -107,9 +107,9 @@ *Andrew White* -* Rails 6 requires Ruby 2.4.1 or newer. +* Rails 6 requires Ruby 2.5.0 or newer. - *Jeremy Daer* + *Jeremy Daer*, *Kasper Timm Hansen* * Add support to define custom argument serializers. diff --git a/activejob/activejob.gemspec b/activejob/activejob.gemspec index 20b9d4ccdd..c3c0447d8e 100644 --- a/activejob/activejob.gemspec +++ b/activejob/activejob.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.summary = "Job framework with pluggable queues." s.description = "Declare job classes that can be run by a variety of queuing backends." - s.required_ruby_version = ">= 2.4.1" + s.required_ruby_version = ">= 2.5.0" s.license = "MIT" diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb index e0a71f4da8..f03780b91e 100644 --- a/activejob/lib/active_job/test_helper.rb +++ b/activejob/lib/active_job/test_helper.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "active_support/core_ext/class/subclasses" -require "active_support/core_ext/hash/keys" module ActiveJob # Provides helper methods for testing Active Job diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 38c4d0c3c7..84567dcc18 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -54,9 +54,9 @@ *Martin Larochelle* -* Rails 6 requires Ruby 2.4.1 or newer. +* Rails 6 requires Ruby 2.5.0 or newer. - *Jeremy Daer* + *Jeremy Daer*, *Kasper Timm Hansen* Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/activemodel/CHANGELOG.md) for previous changes. diff --git a/activemodel/activemodel.gemspec b/activemodel/activemodel.gemspec index 493fca698a..4deb76814b 100644 --- a/activemodel/activemodel.gemspec +++ b/activemodel/activemodel.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.summary = "A toolkit for building modeling frameworks (part of Rails)." s.description = "A toolkit for building modeling frameworks like Active Record. Rich support for attributes, callbacks, validations, serialization, internationalization, and testing." - s.required_ruby_version = ">= 2.4.1" + s.required_ruby_version = ">= 2.5.0" s.license = "MIT" diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index d8352343e9..5c4670f393 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -507,8 +507,8 @@ module ActiveModel temp_method_name = "__temp__#{safe_name}#{'=' if writer}" attr_name_expr = "::ActiveModel::AttributeMethods::AttrNames::#{const_name}" yield temp_method_name, attr_name_expr - mod.send(:alias_method, method_name, temp_method_name) - mod.send(:undef_method, temp_method_name) + mod.alias_method method_name, temp_method_name + mod.undef_method temp_method_name end end end diff --git a/activemodel/lib/active_model/attribute_set.rb b/activemodel/lib/active_model/attribute_set.rb index a890ee3932..4679b33852 100644 --- a/activemodel/lib/active_model/attribute_set.rb +++ b/activemodel/lib/active_model/attribute_set.rb @@ -37,16 +37,8 @@ module ActiveModel attributes.each_key.select { |name| self[name].initialized? } end - if defined?(JRUBY_VERSION) - # This form is significantly faster on JRuby, and this is one of our biggest hotspots. - # https://github.com/jruby/jruby/pull/2562 - def fetch_value(name, &block) - self[name].value(&block) - end - else - def fetch_value(name) - self[name].value { |n| yield n if block_given? } - end + def fetch_value(name, &block) + self[name].value(&block) end def write_from_database(name, value) diff --git a/activemodel/lib/active_model/validations.rb b/activemodel/lib/active_model/validations.rb index 7f14d102dd..f18f9a601a 100644 --- a/activemodel/lib/active_model/validations.rb +++ b/activemodel/lib/active_model/validations.rb @@ -1,8 +1,6 @@ # frozen_string_literal: true require "active_support/core_ext/array/extract_options" -require "active_support/core_ext/hash/keys" -require "active_support/core_ext/hash/except" module ActiveModel # == Active \Model \Validations diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb index ea3a6b52ab..6fd54270f2 100644 --- a/activemodel/lib/active_model/validations/acceptance.rb +++ b/activemodel/lib/active_model/validations/acceptance.rb @@ -54,8 +54,9 @@ module ActiveModel def define_on(klass) attr_readers = attributes.reject { |name| klass.attribute_method?(name) } attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") } - klass.send(:attr_reader, *attr_readers) - klass.send(:attr_writer, *attr_writers) + klass.define_attribute_methods + klass.attr_reader(*attr_readers) + klass.attr_writer(*attr_writers) end private diff --git a/activemodel/lib/active_model/validations/confirmation.rb b/activemodel/lib/active_model/validations/confirmation.rb index 1b5d5b09ab..b549755ba4 100644 --- a/activemodel/lib/active_model/validations/confirmation.rb +++ b/activemodel/lib/active_model/validations/confirmation.rb @@ -19,11 +19,11 @@ module ActiveModel private def setup!(klass) - klass.send(:attr_reader, *attributes.map do |attribute| + klass.attr_reader(*attributes.map do |attribute| :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation") end.compact) - klass.send(:attr_writer, *attributes.map do |attribute| + klass.attr_writer(*attributes.map do |attribute| :"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=") end.compact) end diff --git a/activemodel/lib/active_model/validator.rb b/activemodel/lib/active_model/validator.rb index e17c3ca7b3..94d53b8dd1 100644 --- a/activemodel/lib/active_model/validator.rb +++ b/activemodel/lib/active_model/validator.rb @@ -90,7 +90,7 @@ module ActiveModel # class MyValidator < ActiveModel::Validator # def initialize(options={}) # super - # options[:class].send :attr_accessor, :custom_attribute + # options[:class].attr_accessor :custom_attribute # end # end class Validator diff --git a/activemodel/test/cases/validations/length_validation_test.rb b/activemodel/test/cases/validations/length_validation_test.rb index 774a2cde74..37e10f783c 100644 --- a/activemodel/test/cases/validations/length_validation_test.rb +++ b/activemodel/test/cases/validations/length_validation_test.rb @@ -427,7 +427,7 @@ class LengthValidationTest < ActiveModel::TestCase end def test_validates_length_of_using_proc_as_maximum_with_model_method - Topic.send(:define_method, :max_title_length, lambda { 5 }) + Topic.define_method(:max_title_length) { 5 } Topic.validates_length_of :title, maximum: Proc.new(&:max_title_length) t = Topic.new("title" => "valid", "content" => "whatever") diff --git a/activemodel/test/cases/validations/numericality_validation_test.rb b/activemodel/test/cases/validations/numericality_validation_test.rb index ca22e38c2d..eb4b02df93 100644 --- a/activemodel/test/cases/validations/numericality_validation_test.rb +++ b/activemodel/test/cases/validations/numericality_validation_test.rb @@ -66,7 +66,7 @@ class NumericalityValidationTest < ActiveModel::TestCase end def test_validates_numericality_of_with_integer_only_and_proc_as_value - Topic.send(:define_method, :allow_only_integers?, lambda { false }) + Topic.define_method(:allow_only_integers?) { false } Topic.validates_numericality_of :approved, only_integer: Proc.new(&:allow_only_integers?) invalid!(NIL + BLANK + JUNK) @@ -214,23 +214,23 @@ class NumericalityValidationTest < ActiveModel::TestCase end def test_validates_numericality_with_proc - Topic.send(:define_method, :min_approved, lambda { 5 }) + Topic.define_method(:min_approved) { 5 } Topic.validates_numericality_of :approved, greater_than_or_equal_to: Proc.new(&:min_approved) invalid!([3, 4]) valid!([5, 6]) ensure - Topic.send(:remove_method, :min_approved) + Topic.remove_method :min_approved end def test_validates_numericality_with_symbol - Topic.send(:define_method, :max_approved, lambda { 5 }) + Topic.define_method(:max_approved) { 5 } Topic.validates_numericality_of :approved, less_than_or_equal_to: :max_approved invalid!([6]) valid!([4, 5]) ensure - Topic.send(:remove_method, :max_approved) + Topic.remove_method :max_approved end def test_validates_numericality_with_numeric_message diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index 6f2b49c428..a7aff164c5 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -466,9 +466,9 @@ *Tan Huynh*, *Yukio Mizuta* -* Rails 6 requires Ruby 2.4.1 or newer. +* Rails 6 requires Ruby 2.5.0 or newer. - *Jeremy Daer* + *Jeremy Daer*, *Kasper Timm Hansen* * Deprecate `update_attributes`/`!` in favor of `update`/`!`. diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index bcdd82052c..1e198c3a55 100644 --- a/activerecord/activerecord.gemspec +++ b/activerecord/activerecord.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.summary = "Object-relational mapper framework (part of Rails)." s.description = "Databases on Rails. Build a persistent domain model by mapping database tables to Ruby classes. Strong conventions for associations, validations, aggregations, migrations, and testing come baked-in." - s.required_ruby_version = ">= 2.4.1" + s.required_ruby_version = ">= 2.5.0" s.license = "MIT" diff --git a/activerecord/lib/active_record/attribute_methods/read.rb b/activerecord/lib/active_record/attribute_methods/read.rb index 6e1275e990..ffac5313ad 100644 --- a/activerecord/lib/active_record/attribute_methods/read.rb +++ b/activerecord/lib/active_record/attribute_methods/read.rb @@ -42,16 +42,8 @@ module ActiveRecord # This method exists to avoid the expensive primary_key check internally, without # breaking compatibility with the read_attribute API - if defined?(JRUBY_VERSION) - # This form is significantly faster on JRuby, and this is one of our biggest hotspots. - # https://github.com/jruby/jruby/pull/2562 - def _read_attribute(attr_name, &block) # :nodoc: - @attributes.fetch_value(attr_name.to_s, &block) - end - else - def _read_attribute(attr_name) # :nodoc: - @attributes.fetch_value(attr_name.to_s) { |n| yield n if block_given? } - end + def _read_attribute(attr_name, &block) # :nodoc + @attributes.fetch_value(attr_name.to_s, &block) end alias :attribute :_read_attribute diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb index 2d7f34ef24..dbc6614b93 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -116,14 +116,6 @@ module ActiveRecord true end - def supports_longer_index_key_prefix? - if mariadb? - version >= "10.2.2" - else - version >= "5.7.9" - end - end - def get_advisory_lock(lock_name, timeout = 0) # :nodoc: query_value("SELECT GET_LOCK(#{quote(lock_name.to_s)}, #{timeout})") == 1 end @@ -250,7 +242,7 @@ module ActiveRecord execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT COLLATE #{quote_table_name(options[:collation])}" elsif options[:charset] execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET #{quote_table_name(options[:charset])}" - elsif supports_longer_index_key_prefix? + elsif row_format_dynamic_by_default? execute "CREATE DATABASE #{quote_table_name(name)} DEFAULT CHARACTER SET `utf8mb4`" else raise "Configure a supported :charset and ensure innodb_large_prefix is enabled to support indexes on varchar(255) string columns." diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb index 4894fd1c08..66db4e4149 100644 --- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb @@ -79,7 +79,7 @@ module ActiveRecord def internal_string_options_for_primary_key super.tap do |options| - if CHARSETS_OF_4BYTES_MAXLEN.include?(charset) && (mariadb? || version < "8.0.0") + if !row_format_dynamic_by_default? && CHARSETS_OF_4BYTES_MAXLEN.include?(charset) options[:collation] = collation.sub(/\A[^_]+/, "utf8") end end @@ -96,6 +96,14 @@ module ActiveRecord private CHARSETS_OF_4BYTES_MAXLEN = ["utf8mb4", "utf16", "utf16le", "utf32"] + def row_format_dynamic_by_default? + if mariadb? + version >= "10.2.2" + else + version >= "5.7.9" + end + end + def schema_creation MySQL::SchemaCreation.new(self) end diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index d7cb7691e0..e6dba66a08 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -158,7 +158,7 @@ module ActiveRecord # def self.statuses() statuses end detect_enum_conflict!(name, name.pluralize, true) - singleton_class.send(:define_method, name.pluralize) { enum_values } + singleton_class.define_method(name.pluralize) { enum_values } defined_enums[name] = enum_values detect_enum_conflict!(name, name) diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 383dc1bf4b..6f67dd3784 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -54,7 +54,7 @@ module ActiveRecord end RUBY else - generated_relation_methods.send(:define_method, method) do |*args, &block| + generated_relation_methods.define_method(method) do |*args, &block| scoping { klass.public_send(method, *args, &block) } end end diff --git a/activerecord/lib/active_record/scoping/named.rb b/activerecord/lib/active_record/scoping/named.rb index d5cc5db97e..5278eb29a9 100644 --- a/activerecord/lib/active_record/scoping/named.rb +++ b/activerecord/lib/active_record/scoping/named.rb @@ -179,13 +179,13 @@ module ActiveRecord extension = Module.new(&block) if block if body.respond_to?(:to_proc) - singleton_class.send(:define_method, name) do |*args| + singleton_class.define_method(name) do |*args| scope = all._exec_scope(*args, &body) scope = scope.extending(extension) if extension scope end else - singleton_class.send(:define_method, name) do |*args| + singleton_class.define_method(name) do |*args| scope = body.call(*args) || all scope = scope.extending(extension) if extension scope diff --git a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb index afd422881b..62efaf3bfe 100644 --- a/activerecord/test/cases/adapters/postgresql/active_schema_test.rb +++ b/activerecord/test/cases/adapters/postgresql/active_schema_test.rb @@ -29,7 +29,7 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase def test_add_index # add_index calls index_name_exists? which can't work since execute is stubbed - ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_exists?) { |*| false } + ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.define_method(:index_name_exists?) { |*| false } expected = %(CREATE UNIQUE INDEX "index_people_on_last_name" ON "people" ("last_name") WHERE state = 'active') assert_equal expected, add_index(:people, :last_name, unique: true, where: "state = 'active'") @@ -74,12 +74,12 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase add_index(:people, :last_name, algorithm: :copy) end - ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send :remove_method, :index_name_exists? + ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.remove_method :index_name_exists? end def test_remove_index # remove_index calls index_name_for_remove which can't work since execute is stubbed - ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send(:define_method, :index_name_for_remove) do |*| + ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.define_method(:index_name_for_remove) do |*| "index_people_on_last_name" end @@ -90,7 +90,7 @@ class PostgresqlActiveSchemaTest < ActiveRecord::PostgreSQLTestCase add_index(:people, :last_name, algorithm: :copy) end - ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.send :remove_method, :index_name_for_remove + ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.remove_method :index_name_for_remove end def test_remove_index_when_name_is_specified diff --git a/activerecord/test/cases/callbacks_test.rb b/activerecord/test/cases/callbacks_test.rb index 0ea3fb86a6..4d6a112af5 100644 --- a/activerecord/test/cases/callbacks_test.rb +++ b/activerecord/test/cases/callbacks_test.rb @@ -21,7 +21,7 @@ class CallbackDeveloper < ActiveRecord::Base def callback_object(callback_method) klass = Class.new - klass.send(:define_method, callback_method) do |model| + klass.define_method(callback_method) do |model| model.history << [callback_method, :object] end klass.new diff --git a/activerecord/test/cases/finder_respond_to_test.rb b/activerecord/test/cases/finder_respond_to_test.rb index e0acd30c22..66413a98e4 100644 --- a/activerecord/test/cases/finder_respond_to_test.rb +++ b/activerecord/test/cases/finder_respond_to_test.rb @@ -12,10 +12,10 @@ class FinderRespondToTest < ActiveRecord::TestCase end def test_should_preserve_normal_respond_to_behaviour_and_respond_to_newly_added_method - class << Topic; self; end.send(:define_method, :method_added_for_finder_respond_to_test) { } + Topic.singleton_class.define_method(:method_added_for_finder_respond_to_test) { } assert_respond_to Topic, :method_added_for_finder_respond_to_test ensure - class << Topic; self; end.send(:remove_method, :method_added_for_finder_respond_to_test) + Topic.singleton_class.remove_method :method_added_for_finder_respond_to_test end def test_should_preserve_normal_respond_to_behaviour_and_respond_to_standard_object_method @@ -56,6 +56,6 @@ class FinderRespondToTest < ActiveRecord::TestCase private def ensure_topic_method_is_not_cached(method_id) - class << Topic; self; end.send(:remove_method, method_id) if Topic.public_methods.include? method_id + Topic.singleton_class.remove_method method_id if Topic.public_methods.include? method_id end end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 21e84d850b..961ae03a4c 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -1116,7 +1116,7 @@ class FinderTest < ActiveRecord::TestCase def test_dynamic_finder_on_one_attribute_with_conditions_returns_same_results_after_caching # ensure this test can run independently of order - class << Account; self; end.send(:remove_method, :find_by_credit_limit) if Account.public_methods.include?(:find_by_credit_limit) + Account.singleton_class.remove_method :find_by_credit_limit if Account.public_methods.include?(:find_by_credit_limit) a = Account.where("firm_id = ?", 6).find_by_credit_limit(50) assert_equal a, Account.where("firm_id = ?", 6).find_by_credit_limit(50) # find_by_credit_limit has been cached end diff --git a/activerecord/test/cases/validations/absence_validation_test.rb b/activerecord/test/cases/validations/absence_validation_test.rb index 8235a54d8a..1982734f02 100644 --- a/activerecord/test/cases/validations/absence_validation_test.rb +++ b/activerecord/test/cases/validations/absence_validation_test.rb @@ -61,7 +61,7 @@ class AbsenceValidationTest < ActiveRecord::TestCase def test_validates_absence_of_virtual_attribute_on_model repair_validations(Interest) do - Interest.send(:attr_accessor, :token) + Interest.attr_accessor(:token) Interest.validates_absence_of(:token) interest = Interest.create!(topic: "Thought Leadering") diff --git a/activerecord/test/cases/validations/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb index 1fbcdc271b..a7cb718043 100644 --- a/activerecord/test/cases/validations/length_validation_test.rb +++ b/activerecord/test/cases/validations/length_validation_test.rb @@ -64,7 +64,7 @@ class LengthValidationTest < ActiveRecord::TestCase def test_validates_length_of_virtual_attribute_on_model repair_validations(Pet) do - Pet.send(:attr_accessor, :nickname) + Pet.attr_accessor(:nickname) Pet.validates_length_of(:name, minimum: 1) Pet.validates_length_of(:nickname, minimum: 1) diff --git a/activerecord/test/cases/validations/presence_validation_test.rb b/activerecord/test/cases/validations/presence_validation_test.rb index 63c3f67da2..4b9cbe9098 100644 --- a/activerecord/test/cases/validations/presence_validation_test.rb +++ b/activerecord/test/cases/validations/presence_validation_test.rb @@ -69,7 +69,7 @@ class PresenceValidationTest < ActiveRecord::TestCase def test_validates_presence_of_virtual_attribute_on_model repair_validations(Interest) do - Interest.send(:attr_accessor, :abbreviation) + Interest.attr_accessor(:abbreviation) Interest.validates_presence_of(:topic) Interest.validates_presence_of(:abbreviation) diff --git a/activerecord/test/cases/validations_test.rb b/activerecord/test/cases/validations_test.rb index 66763c727f..9a70934b7e 100644 --- a/activerecord/test/cases/validations_test.rb +++ b/activerecord/test/cases/validations_test.rb @@ -144,6 +144,13 @@ class ValidationsTest < ActiveRecord::TestCase assert_equal "100,000", d.salary_before_type_cast end + def test_validates_acceptance_of_with_undefined_attribute_methods + Topic.validates_acceptance_of(:approved) + topic = Topic.new(approved: true) + Topic.undefine_attribute_methods + assert topic.approved + end + def test_validates_acceptance_of_as_database_column Topic.validates_acceptance_of(:approved) topic = Topic.create("approved" => true) diff --git a/activestorage/CHANGELOG.md b/activestorage/CHANGELOG.md index ea9b10add9..99f1ef9d86 100644 --- a/activestorage/CHANGELOG.md +++ b/activestorage/CHANGELOG.md @@ -123,9 +123,9 @@ *Janko Marohnić* -* Rails 6 requires Ruby 2.4.1 or newer. +* Rails 6 requires Ruby 2.5.0 or newer. - *Jeremy Daer* + *Jeremy Daer*, *Kasper Timm Hansen* Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/activestorage/CHANGELOG.md) for previous changes. diff --git a/activestorage/activestorage.gemspec b/activestorage/activestorage.gemspec index 2c8816df25..dfada7054a 100644 --- a/activestorage/activestorage.gemspec +++ b/activestorage/activestorage.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.summary = "Local and cloud file storage framework." s.description = "Attach cloud and local files in Rails applications." - s.required_ruby_version = ">= 2.4.1" + s.required_ruby_version = ">= 2.5.0" s.license = "MIT" diff --git a/activestorage/test/models/attached/many_test.rb b/activestorage/test/models/attached/many_test.rb index 3b563b3fc8..8fede0e682 100644 --- a/activestorage/test/models/attached/many_test.rb +++ b/activestorage/test/models/attached/many_test.rb @@ -590,7 +590,7 @@ class ActiveStorage::ManyAttachedTest < ActiveSupport::TestCase assert_equal "town.jpg", @user.highlights.first.filename.to_s assert_equal "funky.jpg", @user.highlights.second.filename.to_s ensure - User.send(:remove_method, :highlights) + User.remove_method :highlights end end end diff --git a/activestorage/test/models/attached/one_test.rb b/activestorage/test/models/attached/one_test.rb index 561c3e9d23..7fb3262781 100644 --- a/activestorage/test/models/attached/one_test.rb +++ b/activestorage/test/models/attached/one_test.rb @@ -507,7 +507,7 @@ class ActiveStorage::OneAttachedTest < ActiveSupport::TestCase assert_equal "gpj.yknuf", @user.avatar ensure - User.send(:remove_method, :avatar) + User.remove_method :avatar end end end diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index ba8aaf47f9..e678f48244 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -282,9 +282,9 @@ *Jeremy Daer* -* Rails 6 requires Ruby 2.4.1 or newer. +* Rails 6 requires Ruby 2.5.0 or newer. - *Jeremy Daer* + *Jeremy Daer*, *Kasper Timm Hansen* * Adds parallel testing to Rails. diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 448a2eeebb..bdd7bc70a0 100644 --- a/activesupport/activesupport.gemspec +++ b/activesupport/activesupport.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.summary = "A toolkit of support libraries and Ruby core extensions extracted from the Rails framework." s.description = "A toolkit of support libraries and Ruby core extensions extracted from the Rails framework. Rich support for multibyte strings, internationalization, time zones, and testing." - s.required_ruby_version = ">= 2.4.1" + s.required_ruby_version = ">= 2.5.0" s.license = "MIT" diff --git a/activesupport/lib/active_support/core_ext/array.rb b/activesupport/lib/active_support/core_ext/array.rb index a2569c798b..88b6567712 100644 --- a/activesupport/lib/active_support/core_ext/array.rb +++ b/activesupport/lib/active_support/core_ext/array.rb @@ -6,5 +6,4 @@ require "active_support/core_ext/array/conversions" require "active_support/core_ext/array/extract" require "active_support/core_ext/array/extract_options" require "active_support/core_ext/array/grouping" -require "active_support/core_ext/array/prepend_and_append" require "active_support/core_ext/array/inquiry" diff --git a/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb index 661971d7cd..ba3739f640 100644 --- a/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb +++ b/activesupport/lib/active_support/core_ext/array/prepend_and_append.rb @@ -1,9 +1,5 @@ # frozen_string_literal: true -class Array - # The human way of thinking about adding stuff to the end of a list is with append. - alias_method :append, :push unless [].respond_to?(:append) +require "active_support/deprecation" - # The human way of thinking about adding stuff to the beginning of a list is with prepend. - alias_method :prepend, :unshift unless [].respond_to?(:prepend) -end +ActiveSupport::Deprecation.warn "Ruby 2.5+ (required by Rails 6) provides Array#append and Array#prepend natively, so requiring active_support/core_ext/array/prepend_and_append is no longer necessary. Requiring it will raise LoadError in Rails 6.1." diff --git a/activesupport/lib/active_support/core_ext/hash/compact.rb b/activesupport/lib/active_support/core_ext/hash/compact.rb index 28c8d86b9b..5cb858af5c 100644 --- a/activesupport/lib/active_support/core_ext/hash/compact.rb +++ b/activesupport/lib/active_support/core_ext/hash/compact.rb @@ -2,4 +2,4 @@ require "active_support/deprecation" -ActiveSupport::Deprecation.warn "Ruby 2.4+ (required by Rails 6) provides Hash#compact and Hash#compact! natively, so requiring active_support/core_ext/hash/compact is no longer necessary. Requiring it will raise LoadError in Rails 6.1." +ActiveSupport::Deprecation.warn "Ruby 2.5+ (required by Rails 6) provides Hash#compact and Hash#compact! natively, so requiring active_support/core_ext/hash/compact is no longer necessary. Requiring it will raise LoadError in Rails 6.1." diff --git a/activesupport/lib/active_support/core_ext/hash/keys.rb b/activesupport/lib/active_support/core_ext/hash/keys.rb index bdf196ec3d..7d3495db2c 100644 --- a/activesupport/lib/active_support/core_ext/hash/keys.rb +++ b/activesupport/lib/active_support/core_ext/hash/keys.rb @@ -1,35 +1,6 @@ # frozen_string_literal: true class Hash - # Returns a new hash with all keys converted using the +block+ operation. - # - # hash = { name: 'Rob', age: '28' } - # - # hash.transform_keys { |key| key.to_s.upcase } # => {"NAME"=>"Rob", "AGE"=>"28"} - # - # If you do not provide a +block+, it will return an Enumerator - # for chaining with other methods: - # - # hash.transform_keys.with_index { |k, i| [k, i].join } # => {"name0"=>"Rob", "age1"=>"28"} - def transform_keys - return enum_for(:transform_keys) { size } unless block_given? - result = {} - each_key do |key| - result[yield(key)] = self[key] - end - result - end unless method_defined? :transform_keys - - # Destructively converts all keys using the +block+ operations. - # Same as +transform_keys+ but modifies +self+. - def transform_keys! - return enum_for(:transform_keys!) { size } unless block_given? - keys.each do |key| - self[yield(key)] = delete(key) - end - self - end unless method_defined? :transform_keys! - # Returns a new hash with all keys converted to strings. # # hash = { name: 'Rob', age: '28' } diff --git a/activesupport/lib/active_support/core_ext/hash/slice.rb b/activesupport/lib/active_support/core_ext/hash/slice.rb index e07a5498ff..3d0f8a1e62 100644 --- a/activesupport/lib/active_support/core_ext/hash/slice.rb +++ b/activesupport/lib/active_support/core_ext/hash/slice.rb @@ -1,29 +1,6 @@ # frozen_string_literal: true class Hash - # Slices a hash to include only the given keys. Returns a hash containing - # the given keys. - # - # { a: 1, b: 2, c: 3, d: 4 }.slice(:a, :b) - # # => {:a=>1, :b=>2} - # - # This is useful for limiting an options hash to valid keys before - # passing to a method: - # - # def search(criteria = {}) - # criteria.assert_valid_keys(:mass, :velocity, :time) - # end - # - # search(options.slice(:mass, :velocity, :time)) - # - # If you have an array of keys you want to limit to, you should splat them: - # - # valid_keys = [:mass, :velocity, :time] - # search(options.slice(*valid_keys)) - def slice(*keys) - keys.each_with_object(Hash.new) { |k, hash| hash[k] = self[k] if has_key?(k) } - end unless method_defined?(:slice) - # Replaces the hash with only the given keys. # Returns a hash containing the removed key/value pairs. # diff --git a/activesupport/lib/active_support/core_ext/hash/transform_values.rb b/activesupport/lib/active_support/core_ext/hash/transform_values.rb index fc15130c9e..e4aeb0e891 100644 --- a/activesupport/lib/active_support/core_ext/hash/transform_values.rb +++ b/activesupport/lib/active_support/core_ext/hash/transform_values.rb @@ -2,4 +2,4 @@ require "active_support/deprecation" -ActiveSupport::Deprecation.warn "Ruby 2.4+ (required by Rails 6) provides Hash#transform_values natively, so requiring active_support/core_ext/hash/transform_values is no longer necessary. Requiring it will raise LoadError in Rails 6.1." +ActiveSupport::Deprecation.warn "Ruby 2.5+ (required by Rails 6) provides Hash#transform_values natively, so requiring active_support/core_ext/hash/transform_values is no longer necessary. Requiring it will raise LoadError in Rails 6.1." diff --git a/activesupport/lib/active_support/core_ext/numeric/inquiry.rb b/activesupport/lib/active_support/core_ext/numeric/inquiry.rb index e05e40825b..6b5240d051 100644 --- a/activesupport/lib/active_support/core_ext/numeric/inquiry.rb +++ b/activesupport/lib/active_support/core_ext/numeric/inquiry.rb @@ -2,4 +2,4 @@ require "active_support/deprecation" -ActiveSupport::Deprecation.warn "Ruby 2.4+ (required by Rails 6) provides Numeric#positive? and Numeric#negative? natively, so requiring active_support/core_ext/numeric/inquiry is no longer necessary. Requiring it will raise LoadError in Rails 6.1." +ActiveSupport::Deprecation.warn "Ruby 2.5+ (required by Rails 6) provides Numeric#positive? and Numeric#negative? natively, so requiring active_support/core_ext/numeric/inquiry is no longer necessary. Requiring it will raise LoadError in Rails 6.1." diff --git a/activesupport/lib/active_support/deprecation/method_wrappers.rb b/activesupport/lib/active_support/deprecation/method_wrappers.rb index f0c3e37e65..d99571790f 100644 --- a/activesupport/lib/active_support/deprecation/method_wrappers.rb +++ b/activesupport/lib/active_support/deprecation/method_wrappers.rb @@ -60,13 +60,13 @@ module ActiveSupport with_method = "#{aliased_method}_with_deprecation#{punctuation}" without_method = "#{aliased_method}_without_deprecation#{punctuation}" - target_module.send(:define_method, with_method) do |*args, &block| + target_module.define_method(with_method) do |*args, &block| deprecator.deprecation_warning(method_name, options[method_name]) send(without_method, *args, &block) end - target_module.send(:alias_method, without_method, method_name) - target_module.send(:alias_method, method_name, with_method) + target_module.alias_method(without_method, method_name) + target_module.alias_method(method_name, with_method) case when target_module.protected_method_defined?(without_method) @@ -75,7 +75,7 @@ module ActiveSupport target_module.send(:private, method_name) end else - mod.send(:define_method, method_name) do |*args, &block| + mod.define_method(method_name) do |*args, &block| deprecator.deprecation_warning(method_name, options[method_name]) super(*args, &block) end diff --git a/activesupport/lib/active_support/inflector/inflections.rb b/activesupport/lib/active_support/inflector/inflections.rb index 2b86d233e5..fa087c4dd6 100644 --- a/activesupport/lib/active_support/inflector/inflections.rb +++ b/activesupport/lib/active_support/inflector/inflections.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "concurrent/map" -require "active_support/core_ext/array/prepend_and_append" require "active_support/i18n" require "active_support/deprecation" diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb index 01cc363e2b..0ff32bd810 100644 --- a/activesupport/lib/active_support/notifications.rb +++ b/activesupport/lib/active_support/notifications.rb @@ -177,7 +177,7 @@ module ActiveSupport # names, or by passing a Regexp to match all events that match a pattern. # # ActiveSupport::Notifications.subscribe(/render/) do |*args| - # ... + # @event = ActiveSupport::Notifications::Event.new(*args) # end # # The +block+ will receive five parameters with information about the event: @@ -189,6 +189,13 @@ module ActiveSupport # id # => String, unique ID for the instrumenter that fired the event # payload # => Hash, the payload # end + # + # If the block passed to the method only takes one parameter, + # it will yield an event object to the block: + # + # ActiveSupport::Notifications.subscribe(/render/) do |event| + # @event = event + # end def subscribe(*args, &block) notifier.subscribe(*args, &block) end diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index ef12c6b9b0..7be4108ed7 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -12,6 +12,7 @@ require "active_support/testing/constant_lookup" require "active_support/testing/time_helpers" require "active_support/testing/file_fixtures" require "active_support/testing/parallelization" +require "concurrent/utility/processor_counter" module ActiveSupport class TestCase < ::Minitest::Test @@ -58,16 +59,20 @@ module ActiveSupport # If the number of workers is set to +1+ or fewer, the tests will not be # parallelized. # + # If +workers+ is set to +:number_of_processors+, the number of workers will be + # set to the actual core count on the machine you are on. + # # The default parallelization method is to fork processes. If you'd like to # use threads instead you can pass <tt>with: :threads</tt> to the +parallelize+ # method. Note the threaded parallelization does not create multiple # database and will not work with system tests at this time. # - # parallelize(workers: 2, with: :threads) + # parallelize(workers: :number_of_processors, with: :threads) # # The threaded parallelization uses minitest's parallel executor directly. # The processes parallelization uses a Ruby DRb server. - def parallelize(workers: 2, with: :processes) + def parallelize(workers: :number_of_processors, with: :processes) + workers = Concurrent.physical_processor_count if workers == :number_of_processors workers = ENV["PARALLEL_WORKERS"].to_i if ENV["PARALLEL_WORKERS"] return if workers <= 1 diff --git a/activesupport/lib/active_support/testing/method_call_assertions.rb b/activesupport/lib/active_support/testing/method_call_assertions.rb index eba41aa907..03c38be481 100644 --- a/activesupport/lib/active_support/testing/method_call_assertions.rb +++ b/activesupport/lib/active_support/testing/method_call_assertions.rb @@ -35,18 +35,16 @@ module ActiveSupport assert_called(object, method_name, message, times: 0, &block) end - # TODO: No need to resort to #send once support for Ruby 2.4 is - # dropped. def assert_called_on_instance_of(klass, method_name, message = nil, times: 1, returns: nil) times_called = 0 - klass.send(:define_method, "stubbed_#{method_name}") do |*| + klass.define_method("stubbed_#{method_name}") do |*| times_called += 1 returns end - klass.send(:alias_method, "original_#{method_name}", method_name) - klass.send(:alias_method, method_name, "stubbed_#{method_name}") + klass.alias_method "original_#{method_name}", method_name + klass.alias_method method_name, "stubbed_#{method_name}" yield @@ -55,9 +53,9 @@ module ActiveSupport assert_equal times, times_called, error ensure - klass.send(:alias_method, method_name, "original_#{method_name}") - klass.send(:undef_method, "original_#{method_name}") - klass.send(:undef_method, "stubbed_#{method_name}") + klass.alias_method method_name, "original_#{method_name}" + klass.undef_method "original_#{method_name}" + klass.undef_method "stubbed_#{method_name}" end def assert_not_called_on_instance_of(klass, method_name, message = nil, &block) diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index f160e66971..5a3fa9346c 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -22,7 +22,7 @@ module ActiveSupport @stubs[object.object_id][method_name] = Stub.new(object, method_name, new_name) - object.singleton_class.send :alias_method, new_name, method_name + object.singleton_class.alias_method new_name, method_name object.define_singleton_method(method_name, &block) end @@ -43,9 +43,9 @@ module ActiveSupport def unstub_object(stub) singleton_class = stub.object.singleton_class - singleton_class.send :silence_redefinition_of_method, stub.method_name - singleton_class.send :alias_method, stub.method_name, stub.original_method - singleton_class.send :undef_method, stub.original_method + singleton_class.silence_redefinition_of_method stub.method_name + singleton_class.alias_method stub.method_name, stub.original_method + singleton_class.undef_method stub.original_method end end diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index 466b364e9d..79098b2a7d 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -31,7 +31,7 @@ module CallbacksTest def callback_object(callback_method) klass = Class.new - klass.send(:define_method, callback_method) do |model| + klass.define_method(callback_method) do |model| model.history << [:"#{callback_method}_save", :object] end klass.new diff --git a/activesupport/test/core_ext/array/prepend_append_test.rb b/activesupport/test/core_ext/array/prepend_append_test.rb index c34acd66ad..8573dbd5a6 100644 --- a/activesupport/test/core_ext/array/prepend_append_test.rb +++ b/activesupport/test/core_ext/array/prepend_append_test.rb @@ -1,14 +1,11 @@ # frozen_string_literal: true require "abstract_unit" -require "active_support/core_ext/array" class PrependAppendTest < ActiveSupport::TestCase - def test_append - assert_equal [1, 2], [1].append(2) - end - - def test_prepend - assert_equal [2, 1], [1].prepend(2) + def test_requiring_prepend_and_append_is_deprecated + assert_deprecated do + require "active_support/core_ext/array/prepend_and_append" + end end end diff --git a/activesupport/test/core_ext/hash/transform_keys_test.rb b/activesupport/test/core_ext/hash/transform_keys_test.rb deleted file mode 100644 index b9e41f7b25..0000000000 --- a/activesupport/test/core_ext/hash/transform_keys_test.rb +++ /dev/null @@ -1,64 +0,0 @@ -# frozen_string_literal: true - -require "abstract_unit" -require "active_support/core_ext/hash/keys" - -class TransformKeysTest < ActiveSupport::TestCase - test "transform_keys returns a new hash with the keys computed from the block" do - original = { a: "a", b: "b" } - mapped = original.transform_keys { |k| "#{k}!".to_sym } - - assert_equal({ a: "a", b: "b" }, original) - assert_equal({ a!: "a", b!: "b" }, mapped) - end - - test "transform_keys! modifies the keys of the original" do - original = { a: "a", b: "b" } - mapped = original.transform_keys! { |k| "#{k}!".to_sym } - - assert_equal({ a!: "a", b!: "b" }, original) - assert_same original, mapped - end - - test "transform_keys returns a sized Enumerator if no block is given" do - original = { a: "a", b: "b" } - enumerator = original.transform_keys - assert_equal original.size, enumerator.size - assert_equal Enumerator, enumerator.class - end - - test "transform_keys! returns a sized Enumerator if no block is given" do - original = { a: "a", b: "b" } - enumerator = original.transform_keys! - assert_equal original.size, enumerator.size - assert_equal Enumerator, enumerator.class - end - - test "transform_keys is chainable with Enumerable methods" do - original = { a: "a", b: "b" } - mapped = original.transform_keys.with_index { |k, i| [k, i].join.to_sym } - assert_equal({ a0: "a", b1: "b" }, mapped) - end - - test "transform_keys! is chainable with Enumerable methods" do - original = { a: "a", b: "b" } - original.transform_keys!.with_index { |k, i| [k, i].join.to_sym } - assert_equal({ a0: "a", b1: "b" }, original) - end - - test "transform_keys returns a Hash instance when self is inherited from Hash" do - class HashDescendant < ::Hash - def initialize(elements = nil) - super(elements) - (elements || {}).each_pair { |key, value| self[key] = value } - end - end - - original = HashDescendant.new(a: "a", b: "b") - mapped = original.transform_keys { |k| "#{k}!".to_sym } - - assert_equal({ a: "a", b: "b" }, original) - assert_equal({ a!: "a", b!: "b" }, mapped) - assert_equal(::Hash, mapped.class) - end -end diff --git a/activesupport/test/core_ext/hash_ext_test.rb b/activesupport/test/core_ext/hash_ext_test.rb index f4f0dd6b31..95fdcb8faf 100644 --- a/activesupport/test/core_ext/hash_ext_test.rb +++ b/activesupport/test/core_ext/hash_ext_test.rb @@ -31,8 +31,6 @@ class HashExtTest < ActiveSupport::TestCase def test_methods h = {} - assert_respond_to h, :transform_keys - assert_respond_to h, :transform_keys! assert_respond_to h, :deep_transform_keys assert_respond_to h, :deep_transform_keys! assert_respond_to h, :symbolize_keys @@ -49,18 +47,6 @@ class HashExtTest < ActiveSupport::TestCase assert_respond_to h, :except! end - def test_transform_keys - assert_equal @upcase_strings, @strings.transform_keys { |key| key.to_s.upcase } - assert_equal @upcase_strings, @symbols.transform_keys { |key| key.to_s.upcase } - assert_equal @upcase_strings, @mixed.transform_keys { |key| key.to_s.upcase } - end - - def test_transform_keys_not_mutates - transformed_hash = @mixed.dup - transformed_hash.transform_keys { |key| key.to_s.upcase } - assert_equal @mixed, transformed_hash - end - def test_deep_transform_keys assert_equal @nested_upcase_strings, @nested_symbols.deep_transform_keys { |key| key.to_s.upcase } assert_equal @nested_upcase_strings, @nested_strings.deep_transform_keys { |key| key.to_s.upcase } @@ -76,19 +62,6 @@ class HashExtTest < ActiveSupport::TestCase assert_equal @nested_mixed, transformed_hash end - def test_transform_keys! - assert_equal @upcase_strings, @symbols.dup.transform_keys! { |key| key.to_s.upcase } - assert_equal @upcase_strings, @strings.dup.transform_keys! { |key| key.to_s.upcase } - assert_equal @upcase_strings, @mixed.dup.transform_keys! { |key| key.to_s.upcase } - end - - def test_transform_keys_with_bang_mutates - transformed_hash = @mixed.dup - transformed_hash.transform_keys! { |key| key.to_s.upcase } - assert_equal @upcase_strings, transformed_hash - assert_equal({ :a => 1, "b" => 2 }, @mixed) - end - def test_deep_transform_keys! assert_equal @nested_upcase_strings, @nested_symbols.deep_dup.deep_transform_keys! { |key| key.to_s.upcase } assert_equal @nested_upcase_strings, @nested_strings.deep_dup.deep_transform_keys! { |key| key.to_s.upcase } @@ -337,30 +310,16 @@ class HashExtTest < ActiveSupport::TestCase assert_equal expected, merged end - def test_slice - original = { a: "x", b: "y", c: 10 } - expected = { a: "x", b: "y" } - - # Should return a new hash with only the given keys. - assert_equal expected, original.slice(:a, :b) - assert_not_equal expected, original - end - def test_slice_inplace original = { a: "x", b: "y", c: 10 } - expected = { c: 10 } + expected_return = { c: 10 } + expected_original = { a: "x", b: "y" } - # Should replace the hash with only the given keys. - assert_equal expected, original.slice!(:a, :b) - end - - def test_slice_with_an_array_key - original = { :a => "x", :b => "y", :c => 10, [:a, :b] => "an array key" } - expected = { [:a, :b] => "an array key", :c => 10 } + # Should return a hash containing the removed key/value pairs. + assert_equal expected_return, original.slice!(:a, :b) - # Should return a new hash with only the given keys when given an array key. - assert_equal expected, original.slice([:a, :b], :c) - assert_not_equal expected, original + # Should replace the hash with only the given keys. + assert_equal expected_original, original end def test_slice_inplace_with_an_array_key @@ -371,14 +330,6 @@ class HashExtTest < ActiveSupport::TestCase assert_equal expected, original.slice!([:a, :b], :c) end - def test_slice_with_splatted_keys - original = { :a => "x", :b => "y", :c => 10, [:a, :b] => "an array key" } - expected = { a: "x", b: "y" } - - # Should grab each of the splatted keys. - assert_equal expected, original.slice(*[:a, :b]) - end - def test_slice_bang_does_not_override_default hash = Hash.new(0) hash.update(a: 1, b: 2) diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb index 54fd4345fb..4e0aef2cc7 100644 --- a/activesupport/test/notifications_test.rb +++ b/activesupport/test/notifications_test.rb @@ -26,7 +26,7 @@ module Notifications end end - class SubscribeEventObjects < TestCase + class SubscribeEventObjectsTest < TestCase def test_subscribe_events events = [] @notifier.subscribe do |event| diff --git a/activesupport/test/time_travel_test.rb b/activesupport/test/time_travel_test.rb index 8c47f2cdc7..fbd33a6036 100644 --- a/activesupport/test/time_travel_test.rb +++ b/activesupport/test/time_travel_test.rb @@ -3,8 +3,11 @@ require "abstract_unit" require "active_support/core_ext/date_time" require "active_support/core_ext/numeric/time" +require "time_zone_test_helpers" class TimeTravelTest < ActiveSupport::TestCase + include TimeZoneTestHelpers + class TimeSubclass < ::Time; end class DateSubclass < ::Date; end class DateTimeSubclass < ::DateTime; end @@ -71,6 +74,20 @@ class TimeTravelTest < ActiveSupport::TestCase end end + def test_time_helper_travel_to_with_time_zone + with_env_tz "US/Eastern" do + with_tz_default ActiveSupport::TimeZone["UTC"] do + Time.stub(:now, Time.now) do + expected_time = 5.minutes.ago + + travel_to 5.minutes.ago do + assert_equal expected_time.to_s(:db), Time.zone.now.to_s(:db) + end + end + end + end + end + def test_time_helper_travel_back Time.stub(:now, Time.now) do begin diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index 516b643cb8..9f95e22245 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -2,9 +2,9 @@ *Xavier Noria* -* Rails 6 requires Ruby 2.4.1 or newer. +* Rails 6 requires Ruby 2.5.0 or newer. - *Jeremy Daer* + *Jeremy Daer*, *Kasper Timm Hansen* Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/guides/CHANGELOG.md) for previous changes. diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 6b0554bb5f..3db46bc42e 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -2132,30 +2132,6 @@ The methods `second`, `third`, `fourth`, and `fifth` return the corresponding el NOTE: Defined in `active_support/core_ext/array/access.rb`. -### Adding Elements - -#### `prepend` - -This method is an alias of `Array#unshift`. - -```ruby -%w(a b c d).prepend('e') # => ["e", "a", "b", "c", "d"] -[].prepend(10) # => [10] -``` - -NOTE: Defined in `active_support/core_ext/array/prepend_and_append.rb`. - -#### `append` - -This method is an alias of `Array#<<`. - -```ruby -%w(a b c d).append('e') # => ["a", "b", "c", "d", "e"] -[].append([1,2]) # => [[1, 2]] -``` - -NOTE: Defined in `active_support/core_ext/array/prepend_and_append.rb`. - ### Extracting The method `extract!` removes and returns the elements for which the block returns a true value. @@ -2646,48 +2622,6 @@ There's also the bang variant `except!` that removes keys in the very receiver. NOTE: Defined in `active_support/core_ext/hash/except.rb`. -#### `transform_keys` and `transform_keys!` - -The method `transform_keys` accepts a block and returns a hash that has applied the block operations to each of the keys in the receiver: - -```ruby -{nil => nil, 1 => 1, a: :a}.transform_keys { |key| key.to_s.upcase } -# => {"" => 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: - -```ruby -{"a" => 1, a: 2}.transform_keys { |key| key.to_s.upcase } -# The result could either be -# => {"A"=>2} -# or -# => {"A"=>1} -``` - -This method may be useful for example to build specialized conversions. For instance `stringify_keys` and `symbolize_keys` use `transform_keys` to perform their key conversions: - -```ruby -def stringify_keys - transform_keys { |key| key.to_s } -end -... -def symbolize_keys - transform_keys { |key| key.to_sym rescue key } -end -``` - -There's also the bang variant `transform_keys!` that applies the block operations to keys in the very receiver. - -Besides that, one can use `deep_transform_keys` and `deep_transform_keys!` to perform the block operation on all the keys in the given hash and all the hashes nested into it. An example of the result is: - -```ruby -{nil => nil, 1 => 1, nested: {a: 3, 5 => 5}}.deep_transform_keys { |key| key.to_s.upcase } -# => {""=>nil, "1"=>1, "NESTED"=>{"A"=>3, "5"=>5}} -``` - -NOTE: Defined in `active_support/core_ext/hash/keys.rb`. - #### `stringify_keys` and `stringify_keys!` The method `stringify_keys` returns a hash that has a stringified version of the keys in the receiver. It does so by sending `to_s` to them: @@ -2795,26 +2729,7 @@ NOTE: Defined in `active_support/core_ext/hash/keys.rb`. ### Slicing -Ruby has built-in support for taking slices out of strings and arrays. Active Support extends slicing to hashes: - -```ruby -{a: 1, b: 2, c: 3}.slice(:a, :c) -# => {:a=>1, :c=>3} - -{a: 1, b: 2, c: 3}.slice(:b, :X) -# => {:b=>2} # non-existing keys are ignored -``` - -If the receiver responds to `convert_key` keys are normalized: - -```ruby -{a: 1, b: 2}.with_indifferent_access.slice("a") -# => {:a=>1} -``` - -NOTE. Slicing may come in handy for sanitizing option hashes with a white list of keys. - -There's also `slice!` which in addition to perform a slice in place returns what's removed: +The method `slice!` replaces the hash with only the given keys and returns a hash containing the removed key/value pairs. ```ruby hash = {a: 1, b: 2} diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md index 64db141381..5e68b3f400 100644 --- a/guides/source/active_support_instrumentation.md +++ b/guides/source/active_support_instrumentation.md @@ -648,6 +648,18 @@ ActiveSupport::Notifications.subscribe "process_action.action_controller" do |*a end ``` +You may also pass block with only one argument, it will yield an event object to the block: + +```ruby +ActiveSupport::Notifications.subscribe "process_action.action_controller" do |event| + event.name # => "process_action.action_controller" + event.duration # => 10 (in milliseconds) + event.payload # => {:extra=>information} + + Rails.logger.info "#{event} Received!" +end +``` + Most times you only care about the data itself. Here is a shortcut to just get the data. ```ruby @@ -672,7 +684,7 @@ Creating custom events Adding your own events is easy as well. `ActiveSupport::Notifications` will take care of all the heavy lifting for you. Simply call `instrument` with a `name`, `payload` and a block. The notification will be sent after the block returns. `ActiveSupport` will generate the start and end times -and add the instrumenter's unique ID. All data passed into the `instrument` call will make +and add the instrumenter's unique ID. All data passed into the `instrument` call will make it into the payload. Here's an example: diff --git a/guides/source/configuring.md b/guides/source/configuring.md index 029ae1a5ff..ae1de3079f 100644 --- a/guides/source/configuring.md +++ b/guides/source/configuring.md @@ -1404,7 +1404,7 @@ Custom configuration You can configure your own code through the Rails configuration object with custom configuration under either the `config.x` namespace, or `config` directly. The key difference between these two is that you should be using `config.x` if you -are defining _nested_ configuration (ex: `config.x.nested.nested.hi`), and just +are defining _nested_ configuration (ex: `config.x.nested.hi`), and just `config` for _single level_ configuration (ex: `config.hello`). ```ruby diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index e2f558d74c..264c94326e 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -90,7 +90,7 @@ $ ruby -v ruby 2.5.0 ``` -Rails requires Ruby version 2.4.1 or later. If the version number returned is +Rails requires Ruby version 2.5.0 or later. If the version number returned is less than that number, you'll need to install a fresh copy of Ruby. TIP: To quickly install Ruby and Ruby on Rails on your system in Windows, you can use diff --git a/guides/source/testing.md b/guides/source/testing.md index 9541598b26..f34f9d95f4 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -473,8 +473,8 @@ takes your entire test suite to run. ### Parallel testing with processes The default parallelization method is to fork processes using Ruby's DRb system. The processes -are forked based on the number of workers provided. The default is 2, but can be changed by the -number passed to the parallelize method. +are forked based on the number of workers provided. The default number is the actual core count +on the machine you are on, but can be changed by the number passed to the parallelize method. To enable parallelization add the following to your `test_helper.rb`: @@ -516,7 +516,7 @@ class ActiveSupport::TestCase # cleanup databases end - parallelize(workers: 2) + parallelize(workers: :number_of_processors) end ``` @@ -531,7 +531,7 @@ To change the parallelization method to use threads over forks put the following ```ruby class ActiveSupport::TestCase - parallelize(workers: 2, with: :threads) + parallelize(workers: :number_of_processors, with: :threads) end ``` diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index e74985c5b0..2682c6ffd7 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -35,7 +35,7 @@ You can find a list of all released Rails versions [here](https://rubygems.org/g Rails generally stays close to the latest released Ruby version when it's released: -* Rails 6 requires Ruby 2.4.1 or newer. +* Rails 6 requires Ruby 2.5.0 or newer. * Rails 5 requires Ruby 2.2.2 or newer. * Rails 4 prefers Ruby 2.0 and requires 1.9.3 or newer. * Rails 3.2.x is the last branch to support Ruby 1.8.7. diff --git a/rails.gemspec b/rails.gemspec index 6f3d99228f..936f74c64b 100644 --- a/rails.gemspec +++ b/rails.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.summary = "Full-stack web application framework." s.description = "Ruby on Rails is a full-stack web framework optimized for programmer happiness and sustainable productivity. It encourages beautiful code by favoring convention over configuration." - s.required_ruby_version = ">= 2.4.1" + s.required_ruby_version = ">= 2.5.0" s.required_rubygems_version = ">= 1.8.11" s.license = "MIT" diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index ff6a00f82b..8e358b4293 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,38 @@ +* Introduce guard against DNS rebinding attacks + + The `ActionDispatch::HostAuthorization` is a new middleware that prevent + against DNS rebinding and other `Host` header attacks. It is included in + the development environment by default with the following configuration: + + Rails.application.config.hosts = [ + IPAddr.new("0.0.0.0/0"), # All IPv4 addresses. + IPAddr.new("::/0"), # All IPv6 addresses. + "localhost" # The localhost reserved domain. + ] + + In other environments `Rails.application.config.hosts` is empty and no + `Host` header checks will be done. If you want to guard against header + attacks on production, you have to manually whitelist the allowed hosts + with: + + Rails.application.config.hosts << "product.com" + + The host of a request is checked against the `hosts` entries with the case + operator (`#===`), which lets `hosts` support entries of type `RegExp`, + `Proc` and `IPAddr` to name a few. Here is an example with a regexp. + + # Allow requests from subdomains like `www.product.com` and + # `beta1.product.com`. + Rails.application.config.hosts << /.*\.product\.com/ + + A special case is supported that allows you to whitelist all sub-domains: + + # Allow requests from subdomains like `www.product.com` and + # `beta1.product.com`. + Rails.application.config.hosts << ".product.com" + + *Genadi Samokovarov* + * Remove redundant suffixes on generated helpers. *Gannon McGibbon* @@ -206,9 +241,9 @@ *Benoit Tigeot* -* Rails 6 requires Ruby 2.4.1 or newer. +* Rails 6 requires Ruby 2.5.0 or newer. - *Jeremy Daer* + *Jeremy Daer*, *Kasper Timm Hansen* Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/railties/CHANGELOG.md) for previous changes. diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index f30e5d2a0f..22a82c051d 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true +require "ipaddr" require "active_support/core_ext/kernel/reporting" require "active_support/file_update_checker" require "rails/engine/configuration" @@ -11,7 +12,7 @@ module Rails attr_accessor :allow_concurrency, :asset_host, :autoflush_log, :cache_classes, :cache_store, :consider_all_requests_local, :console, :eager_load, :exceptions_app, :file_watcher, :filter_parameters, - :force_ssl, :helpers_paths, :logger, :log_formatter, :log_tags, + :force_ssl, :helpers_paths, :hosts, :logger, :log_formatter, :log_tags, :railties_order, :relative_url_root, :secret_key_base, :secret_token, :ssl_options, :public_file_server, :session_options, :time_zone, :reload_classes_only_on_change, @@ -29,6 +30,7 @@ module Rails @filter_parameters = [] @filter_redirect = [] @helpers_paths = [] + @hosts = Array(([IPAddr.new("0.0.0.0/0"), IPAddr.new("::/0"), "localhost"] if Rails.env.development?)) @public_file_server = ActiveSupport::OrderedOptions.new @public_file_server.enabled = true @public_file_server.index_name = "index" diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb index 433a7ab41f..193cc59f3a 100644 --- a/railties/lib/rails/application/default_middleware_stack.rb +++ b/railties/lib/rails/application/default_middleware_stack.rb @@ -13,6 +13,8 @@ module Rails def build_stack ActionDispatch::MiddlewareStack.new do |middleware| + middleware.use ::ActionDispatch::HostAuthorization, config.hosts, config.action_dispatch.hosts_response_app + if config.force_ssl middleware.use ::ActionDispatch::SSL, config.ssl_options end diff --git a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt index c06cd525d7..47b4cf745c 100644 --- a/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt +++ b/railties/lib/rails/generators/rails/app/templates/test/test_helper.rb.tt @@ -5,9 +5,9 @@ require 'rails/test_help' class ActiveSupport::TestCase # Run tests in parallel with specified workers <% if defined?(JRUBY_VERSION) || Gem.win_platform? -%> - parallelize(workers: 2, with: :threads) + parallelize(workers: :number_of_processors, with: :threads) <%- else -%> - parallelize(workers: 2) + parallelize(workers: :number_of_processors) <% end -%> <% unless options[:skip_active_record] -%> diff --git a/railties/lib/rails/info_controller.rb b/railties/lib/rails/info_controller.rb index 50fe176946..14459623ac 100644 --- a/railties/lib/rails/info_controller.rb +++ b/railties/lib/rails/info_controller.rb @@ -4,7 +4,7 @@ require "rails/application_controller" require "action_dispatch/routing/inspector" class Rails::InfoController < Rails::ApplicationController # :nodoc: - prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH + prepend_view_path ActionDispatch::DebugView::RESCUES_TEMPLATE_PATH layout -> { request.xhr? ? false : "application" } before_action :require_local! diff --git a/railties/lib/rails/mailers_controller.rb b/railties/lib/rails/mailers_controller.rb index e2d36d7654..95dae3ec2d 100644 --- a/railties/lib/rails/mailers_controller.rb +++ b/railties/lib/rails/mailers_controller.rb @@ -3,7 +3,7 @@ require "rails/application_controller" class Rails::MailersController < Rails::ApplicationController # :nodoc: - prepend_view_path ActionDispatch::DebugExceptions::RESCUES_TEMPLATE_PATH + prepend_view_path ActionDispatch::DebugView::RESCUES_TEMPLATE_PATH before_action :require_local!, unless: :show_previews? before_action :find_preview, :set_locale, only: :preview diff --git a/railties/lib/rails/ruby_version_check.rb b/railties/lib/rails/ruby_version_check.rb index b2d44d9b8e..ab5339bf24 100644 --- a/railties/lib/rails/ruby_version_check.rb +++ b/railties/lib/rails/ruby_version_check.rb @@ -1,15 +1,15 @@ # frozen_string_literal: true -if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.4.1") && RUBY_ENGINE == "ruby" +if Gem::Version.new(RUBY_VERSION) < Gem::Version.new("2.5.0") && RUBY_ENGINE == "ruby" desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})" abort <<-end_message - Rails 6 requires Ruby 2.4.1 or newer. + Rails 6 requires Ruby 2.5.0 or newer. You're running #{desc} - Please upgrade to Ruby 2.4.1 or newer to continue. + Please upgrade to Ruby 2.5.0 or newer to continue. end_message end diff --git a/railties/railties.gemspec b/railties/railties.gemspec index 91407ee1ae..1bfb0dfe48 100644 --- a/railties/railties.gemspec +++ b/railties/railties.gemspec @@ -9,7 +9,7 @@ Gem::Specification.new do |s| s.summary = "Tools for creating, working with, and running Rails applications." s.description = "Rails internals: application bootup, plugins, generators, and rake tasks." - s.required_ruby_version = ">= 2.4.1" + s.required_ruby_version = ">= 2.5.0" s.license = "MIT" diff --git a/railties/test/application/console_test.rb b/railties/test/application/console_test.rb index 29f8b1e3d9..e74daccbe7 100644 --- a/railties/test/application/console_test.rb +++ b/railties/test/application/console_test.rb @@ -25,7 +25,7 @@ class ConsoleTest < ActiveSupport::TestCase end def test_app_method_should_return_integration_session - TestHelpers::Rack.send :remove_method, :app + TestHelpers::Rack.remove_method :app load_environment console_session = irb_context.app assert_instance_of ActionDispatch::Integration::Session, console_session diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index 631f5bac7f..a6396d3509 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -26,6 +26,7 @@ module ApplicationTests assert_equal [ "Webpacker::DevServerProxy", + "ActionDispatch::HostAuthorization", "Rack::Sendfile", "ActionDispatch::Static", "ActionDispatch::Executor", @@ -58,6 +59,7 @@ module ApplicationTests assert_equal [ "Webpacker::DevServerProxy", + "ActionDispatch::HostAuthorization", "Rack::Sendfile", "ActionDispatch::Static", "ActionDispatch::Executor", @@ -140,7 +142,7 @@ module ApplicationTests add_to_config "config.ssl_options = { redirect: { host: 'example.com' } }" boot! - assert_equal [{ redirect: { host: "example.com" } }], Rails.application.middleware[1].args + assert_equal [{ redirect: { host: "example.com" } }], Rails.application.middleware[2].args end test "removing Active Record omits its middleware" do @@ -224,7 +226,7 @@ module ApplicationTests test "insert middleware after" do add_to_config "config.middleware.insert_after Rack::Sendfile, Rack::Config" boot! - assert_equal "Rack::Config", middleware.third + assert_equal "Rack::Config", middleware.fourth end test "unshift middleware" do @@ -236,19 +238,19 @@ module ApplicationTests test "Rails.cache does not respond to middleware" do add_to_config "config.cache_store = :memory_store" boot! - assert_equal "Rack::Runtime", middleware.fifth + assert_equal "Rack::Runtime", middleware[5] end test "Rails.cache does respond to middleware" do boot! - assert_equal "ActiveSupport::Cache::Strategy::LocalCache", middleware.fifth - assert_equal "Rack::Runtime", middleware[5] + assert_equal "ActiveSupport::Cache::Strategy::LocalCache", middleware[5] + assert_equal "Rack::Runtime", middleware[6] end test "insert middleware before" do add_to_config "config.middleware.insert_before Rack::Sendfile, Rack::Config" boot! - assert_equal "Rack::Config", middleware.second + assert_equal "Rack::Config", middleware.third end test "can't change middleware after it's built" do diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb index 140703e118..55ce72181f 100644 --- a/railties/test/application/test_runner_test.rb +++ b/railties/test/application/test_runner_test.rb @@ -523,6 +523,8 @@ module ApplicationTests end def test_run_in_parallel_with_processes + exercise_parallelization_regardless_of_machine_core_count(with: :processes) + file_name = create_parallel_processes_test_file app_file "db/schema.rb", <<-RUBY @@ -540,11 +542,7 @@ module ApplicationTests end def test_run_in_parallel_with_threads - app_path("/test/test_helper.rb") do |file_name| - file = File.read(file_name) - file.sub!(/parallelize\(([^\)]*)\)/, "parallelize(\\1, with: :threads)") - File.write(file_name, file) - end + exercise_parallelization_regardless_of_machine_core_count(with: :threads) file_name = create_parallel_threads_test_file @@ -563,6 +561,8 @@ module ApplicationTests end def test_run_in_parallel_with_unmarshable_exception + exercise_parallelization_regardless_of_machine_core_count(with: :processes) + file = app_file "test/fail_test.rb", <<-RUBY require "test_helper" class FailTest < ActiveSupport::TestCase @@ -587,7 +587,10 @@ module ApplicationTests end def test_run_in_parallel_with_unknown_object + exercise_parallelization_regardless_of_machine_core_count(with: :processes) + create_scaffold + app_file "config/environments/test.rb", <<-RUBY Rails.application.configure do config.action_controller.allow_forgery_protection = true @@ -966,6 +969,14 @@ module ApplicationTests RUBY end + def exercise_parallelization_regardless_of_machine_core_count(with:) + app_path("test/test_helper.rb") do |file_name| + file = File.read(file_name) + file.sub!(/parallelize\(([^\)]*)\)/, "parallelize(workers: 2, with: :#{with})") + File.write(file_name, file) + end + end + def create_env_test app_file "test/unit/env_test.rb", <<-RUBY require 'test_helper' diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index 4f2894e71e..4b9878187b 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -14,9 +14,9 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase super Kernel.silence_warnings do - Thor::Base.shell.send(:attr_accessor, :always_force) + Thor::Base.shell.attr_accessor :always_force @shell = Thor::Base.shell.new - @shell.send(:always_force=, true) + @shell.always_force = true end end diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb index 2dda856f25..7441ab0603 100644 --- a/railties/test/generators/shared_generator_tests.rb +++ b/railties/test/generators/shared_generator_tests.rb @@ -10,9 +10,9 @@ module SharedGeneratorTests Rails::Generators::AppGenerator.instance_variable_set("@desc", nil) Kernel.silence_warnings do - Thor::Base.shell.send(:attr_accessor, :always_force) + Thor::Base.shell.attr_accessor :always_force @shell = Thor::Base.shell.new - @shell.send(:always_force=, true) + @shell.always_force = true end end diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index d4eed69a87..39c936428f 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -197,6 +197,7 @@ module TestHelpers end add_to_config <<-RUBY + config.hosts << proc { true } config.eager_load = false config.session_store :cookie_store, key: "_myapp_session" config.active_support.deprecation = :log @@ -220,6 +221,7 @@ module TestHelpers @app = Class.new(Rails::Application) do def self.name; "RailtiesTestApp"; end end + @app.config.hosts << proc { true } @app.config.eager_load = false @app.config.session_store :cookie_store, key: "_myapp_session" @app.config.active_support.deprecation = :log |