diff options
205 files changed, 1386 insertions, 1313 deletions
diff --git a/.gitignore b/.gitignore index da3b42cfbb..ab9ca8a6ac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,25 +1,16 @@ -# Don't put *.swp, *.bak, etc here; those belong in a global ~/.gitignore. +# Don't put *.swp, *.bak, etc here; those belong in a global .gitignore. # Check out https://help.github.com/articles/ignoring-files for how to set that up. .Gemfile -.ruby-version .byebug_history -debug.log -pkg +.ruby-version +/*/doc/ +/*/test/tmp/ /.bundle -/dist -/doc/rdoc -/*/doc -/*/test/tmp -/activerecord/sqlnet.log -/activemodel/test/fixtures/fixture_database.sqlite3 -/activesupport/test/fixtures/isolation_test -/activestorage/test/service/configurations.yml -/railties/test/500.html -/railties/test/fixtures/tmp -/railties/test/initializer/root/log -/railties/doc -/railties/tmp -/guides/output +/dist/ +/doc/ +/guides/output/ +debug.log node_modules/ -/actionview/log +package-lock.json +pkg/ diff --git a/.rubocop.yml b/.rubocop.yml index 3b4dd79e81..f2f079b0de 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,5 +1,5 @@ AllCops: - TargetRubyVersion: 2.2 + TargetRubyVersion: 2.4 # 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 3a7f28134b..2513e87114 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,8 +60,6 @@ env: - "GEM=ac:integration" rvm: - - 2.2.9 - - 2.3.6 - 2.4.3 - 2.5.0 - ruby-head @@ -70,18 +68,6 @@ matrix: include: - rvm: 2.5.0 env: "GEM=av:ujs" - - rvm: 2.2.9 - env: "GEM=aj:integration" - services: - - memcached - - redis-server - - rabbitmq - - rvm: 2.3.6 - env: "GEM=aj:integration" - services: - - memcached - - redis-server - - rabbitmq - rvm: 2.4.3 env: "GEM=aj:integration" services: @@ -100,30 +86,30 @@ matrix: - memcached - redis-server - rabbitmq - - rvm: 2.3.6 + - rvm: 2.5.0 env: - "GEM=ar:mysql2 MYSQL=mariadb" addons: mariadb: 10.2 - - rvm: 2.3.6 + - rvm: 2.5.0 env: - "GEM=ar:sqlite3_mem" - - rvm: 2.3.6 + - rvm: 2.5.0 env: - "GEM=ar:postgresql POSTGRES=9.2" addons: postgresql: "9.2" - - rvm: jruby-9.1.15.0 + - rvm: jruby-head jdk: oraclejdk8 env: - "GEM=ap" - - rvm: jruby-9.1.15.0 + - rvm: jruby-head jdk: oraclejdk8 env: - "GEM=am,amo,aj" allow_failures: - rvm: ruby-head - - rvm: jruby-9.1.15.0 + - rvm: jruby-head - env: "GEM=ac:integration" fast_finish: true @@ -38,9 +38,6 @@ gem "rubocop", ">= 0.47", require: false # https://github.com/guard/rb-inotify/pull/79 gem "rb-inotify", github: "matthewd/rb-inotify", branch: "close-handling", require: false -# https://github.com/puma/puma/pull/1345 -gem "stopgap_13632", platforms: :mri if RUBY_VERSION == "2.2.8" - group :doc do gem "sdoc", "~> 1.0" gem "redcarpet", "~> 3.2.3", platforms: :ruby diff --git a/Gemfile.lock b/Gemfile.lock index 5c4e853296..87c991253b 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -321,7 +321,7 @@ GEM multi_json (1.12.2) multipart-post (2.0.0) mustache (1.0.5) - mustermann (1.0.0) + mustermann (1.0.2) mysql2 (0.4.10) mysql2 (0.4.10-x64-mingw32) mysql2 (0.4.10-x86-mingw32) @@ -352,10 +352,10 @@ GEM selenium-webdriver thor racc (1.4.14) - rack (2.0.3) + rack (2.0.4) rack-cache (1.7.0) rack (>= 0.4) - rack-protection (2.0.0) + rack-protection (2.0.1) rack rack-test (0.8.0) rack (>= 1.0, < 3) @@ -432,10 +432,10 @@ GEM faraday (~> 0.9) jwt (>= 1.5, < 3.0) multi_json (~> 1.10) - sinatra (2.0.0) + sinatra (2.0.1) mustermann (~> 1.0) rack (~> 2.0) - rack-protection (= 2.0.0) + rack-protection (= 2.0.1) tilt (~> 2.0) sneakers (2.5.0) bunny (~> 2.6.4) diff --git a/actioncable/.gitignore b/actioncable/.gitignore index 0a04b29786..f514e58c16 100644 --- a/actioncable/.gitignore +++ b/actioncable/.gitignore @@ -1,2 +1,2 @@ -/lib/assets/compiled -/tmp +/lib/assets/compiled/ +/tmp/ diff --git a/actioncable/CHANGELOG.md b/actioncable/CHANGELOG.md index 72f5c5de35..a28e2fe75d 100644 --- a/actioncable/CHANGELOG.md +++ b/actioncable/CHANGELOG.md @@ -1,3 +1,8 @@ +## Rails 6.0.0.alpha (Unreleased) ## + +* Rails 6 requires Ruby 2.4.1 or newer. + + *Jeremy Daer* 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 51db4dda3a..d946d0797f 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.2.2" + s.required_ruby_version = ">= 2.4.1" s.license = "MIT" diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb index 84053db9fd..11a1f1a5e8 100644 --- a/actioncable/lib/action_cable/connection/base.rb +++ b/actioncable/lib/action_cable/connection/base.rb @@ -136,13 +136,10 @@ module ActionCable send_async :handle_close end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected + private attr_reader :websocket attr_reader :message_buffer - private # The request that initiated the WebSocket connection is available here. This gives access to the environment, cookies, etc. def request # :doc: @request ||= begin diff --git a/actioncable/lib/action_cable/connection/message_buffer.rb b/actioncable/lib/action_cable/connection/message_buffer.rb index f151a47072..965841b67e 100644 --- a/actioncable/lib/action_cable/connection/message_buffer.rb +++ b/actioncable/lib/action_cable/connection/message_buffer.rb @@ -30,13 +30,10 @@ module ActionCable receive_buffered_messages end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected + private attr_reader :connection attr_reader :buffered_messages - private def valid?(message) message.is_a?(String) end diff --git a/actioncable/lib/action_cable/connection/subscriptions.rb b/actioncable/lib/action_cable/connection/subscriptions.rb index bb8d64e27a..1ad8d05107 100644 --- a/actioncable/lib/action_cable/connection/subscriptions.rb +++ b/actioncable/lib/action_cable/connection/subscriptions.rb @@ -63,12 +63,8 @@ module ActionCable subscriptions.each { |id, channel| remove_subscription(channel) } end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - attr_reader :connection, :subscriptions - private + attr_reader :connection, :subscriptions delegate :logger, to: :connection def find(data) diff --git a/actioncable/lib/action_cable/connection/web_socket.rb b/actioncable/lib/action_cable/connection/web_socket.rb index 81233ace34..31f29fdd2f 100644 --- a/actioncable/lib/action_cable/connection/web_socket.rb +++ b/actioncable/lib/action_cable/connection/web_socket.rb @@ -34,9 +34,7 @@ module ActionCable websocket.rack_response end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected + private attr_reader :websocket end end diff --git a/actionmailer/CHANGELOG.md b/actionmailer/CHANGELOG.md index b2a1c570e5..c5dbb0f1d4 100644 --- a/actionmailer/CHANGELOG.md +++ b/actionmailer/CHANGELOG.md @@ -1,3 +1,8 @@ +## Rails 6.0.0.alpha (Unreleased) ## + +* Rails 6 requires Ruby 2.4.1 or newer. + + *Jeremy Daer* 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 b8a2e80bd3..f2fb160bdd 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.2.2" + s.required_ruby_version = ">= 2.4.1" s.license = "MIT" diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md index 0d10a9a3f2..cd419b68f7 100644 --- a/actionpack/CHANGELOG.md +++ b/actionpack/CHANGELOG.md @@ -1,3 +1,9 @@ +## Rails 6.0.0.alpha (Unreleased) ## + +* Rails 6 requires Ruby 2.4.1 or newer. + + *Jeremy Daer* + * Add alias method `to_hash` to `to_h` for `cookies`. Add alias method `to_h` to `to_hash` for `session`. diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec index 33d42e69d8..1dc8abf746 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.2.2" + s.required_ruby_version = ">= 2.4.1" s.license = "MIT" diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index 0ab313e398..94092de96c 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -3,7 +3,6 @@ require "rack/session/abstract/id" require "action_controller/metal/exceptions" require "active_support/security_utils" -require "active_support/core_ext/string/strip" module ActionController #:nodoc: class InvalidAuthenticityToken < ActionControllerError #:nodoc: @@ -416,7 +415,7 @@ module ActionController #:nodoc: allow_forgery_protection end - NULL_ORIGIN_MESSAGE = <<-MSG.strip_heredoc + NULL_ORIGIN_MESSAGE = <<~MSG The browser returned a 'null' origin for a request with origin-based forgery protection turned on. This usually means you have the 'no-referrer' Referrer-Policy header enabled, or that you the request came from a site that refused to give its origin. This makes it impossible for Rails to verify the source of the requests. Likely the diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index a56ac749f8..615c90c496 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "active_support/core_ext/hash/indifferent_access" -require "active_support/core_ext/hash/transform_values" require "active_support/core_ext/array/wrap" require "active_support/core_ext/string/filters" require "active_support/core_ext/object/to_query" diff --git a/actionpack/lib/action_dispatch/http/content_security_policy.rb b/actionpack/lib/action_dispatch/http/content_security_policy.rb index 4883e23d24..ffac3b8d99 100644 --- a/actionpack/lib/action_dispatch/http/content_security_policy.rb +++ b/actionpack/lib/action_dispatch/http/content_security_policy.rb @@ -172,7 +172,7 @@ module ActionDispatch #:nodoc: end def build(context = nil) - build_directives(context).compact.join("; ") + ";" + build_directives(context).compact.join("; ") end private diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb index d2b2106845..295539281f 100644 --- a/actionpack/lib/action_dispatch/http/mime_type.rb +++ b/actionpack/lib/action_dispatch/http/mime_type.rb @@ -279,8 +279,6 @@ module Mime def all?; false; end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. protected attr_reader :string, :synonyms diff --git a/actionpack/lib/action_dispatch/journey/nodes/node.rb b/actionpack/lib/action_dispatch/journey/nodes/node.rb index 08b931a3cd..32f632800c 100644 --- a/actionpack/lib/action_dispatch/journey/nodes/node.rb +++ b/actionpack/lib/action_dispatch/journey/nodes/node.rb @@ -32,7 +32,7 @@ module ActionDispatch end def name - left.tr "*:".freeze, "".freeze + -left.tr("*:", "") end def type @@ -82,7 +82,7 @@ module ActionDispatch def initialize(left) super @regexp = DEFAULT_EXP - @name = left.tr "*:".freeze, "".freeze + @name = -left.tr("*:", "") end def default_regexp? diff --git a/actionpack/lib/action_dispatch/journey/path/pattern.rb b/actionpack/lib/action_dispatch/journey/path/pattern.rb index 2d85a89a56..537f479ee5 100644 --- a/actionpack/lib/action_dispatch/journey/path/pattern.rb +++ b/actionpack/lib/action_dispatch/journey/path/pattern.rb @@ -90,7 +90,7 @@ module ActionDispatch return @separator_re unless @matchers.key?(node) re = @matchers[node] - "(#{re})" + "(#{Regexp.union(re)})" end def visit_GROUP(node) @@ -183,7 +183,7 @@ module ActionDispatch node = node.to_sym if @requirements.key?(node) - re = /#{@requirements[node]}|/ + re = /#{Regexp.union(@requirements[node])}|/ @offsets.push((re.match("").length - 1) + @offsets.last) else @offsets << @offsets.last diff --git a/actionpack/lib/action_dispatch/routing/inspector.rb b/actionpack/lib/action_dispatch/routing/inspector.rb index a2205569b4..22336c59b6 100644 --- a/actionpack/lib/action_dispatch/routing/inspector.rb +++ b/actionpack/lib/action_dispatch/routing/inspector.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "delegate" -require "active_support/core_ext/string/strip" module ActionDispatch module Routing @@ -150,10 +149,10 @@ module ActionDispatch def no_routes(routes) @buffer << if routes.none? - <<-MESSAGE.strip_heredoc - You don't have any routes defined! + <<~MESSAGE + You don't have any routes defined! - Please add some routes in config/routes.rb. + Please add some routes in config/routes.rb. MESSAGE else "No routes were found for this controller" @@ -203,7 +202,7 @@ module ActionDispatch end def no_routes(*) - @buffer << <<-MESSAGE.strip_heredoc + @buffer << <<~MESSAGE <p>You don't have any routes defined!</p> <ul> <li>Please add some routes in <tt>config/routes.rb</tt>.</li> diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 31eb6104fe..f3970d5445 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -611,7 +611,7 @@ module ActionDispatch end raise ArgumentError, "A rack application must be specified" unless app.respond_to?(:call) - raise ArgumentError, <<-MSG.strip_heredoc unless path + raise ArgumentError, <<~MSG unless path Must be called with mount point mount SomeRackApp, at: "some_route" diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index ff6998ae31..a29a5a04ef 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -153,13 +153,13 @@ module ActionDispatch url_name = :"#{name}_url" @path_helpers_module.module_eval do - define_method(path_name) do |*args| + redefine_method(path_name) do |*args| helper.call(self, args, true) end end @url_helpers_module.module_eval do - define_method(url_name) do |*args| + redefine_method(url_name) do |*args| helper.call(self, args, false) end end diff --git a/actionpack/test/controller/parameters/accessors_test.rb b/actionpack/test/controller/parameters/accessors_test.rb index 7bcf8c380d..07a897a103 100644 --- a/actionpack/test/controller/parameters/accessors_test.rb +++ b/actionpack/test/controller/parameters/accessors_test.rb @@ -2,7 +2,6 @@ require "abstract_unit" require "action_controller/metal/strong_parameters" -require "active_support/core_ext/hash/transform_values" class ParametersAccessorsTest < ActiveSupport::TestCase setup do @@ -273,23 +272,16 @@ class ParametersAccessorsTest < ActiveSupport::TestCase assert_match(/permitted: true/, @params.inspect) end - if Hash.method_defined?(:dig) - test "#dig delegates the dig method to its values" do - assert_equal "David", @params.dig(:person, :name, :first) - assert_equal "Chicago", @params.dig(:person, :addresses, 0, :city) - end + test "#dig delegates the dig method to its values" do + assert_equal "David", @params.dig(:person, :name, :first) + assert_equal "Chicago", @params.dig(:person, :addresses, 0, :city) + end - test "#dig converts hashes to parameters" do - assert_kind_of ActionController::Parameters, @params.dig(:person) - assert_kind_of ActionController::Parameters, @params.dig(:person, :addresses, 0) - assert @params.dig(:person, :addresses).all? do |value| - value.is_a?(ActionController::Parameters) - end - end - else - test "ActionController::Parameters does not respond to #dig on Ruby 2.2" do - assert_not ActionController::Parameters.method_defined?(:dig) - assert_not_respond_to @params, :dig + test "#dig converts hashes to parameters" do + assert_kind_of ActionController::Parameters, @params.dig(:person) + assert_kind_of ActionController::Parameters, @params.dig(:person, :addresses, 0) + assert @params.dig(:person, :addresses).all? do |value| + value.is_a?(ActionController::Parameters) end end end diff --git a/actionpack/test/controller/parameters/mutators_test.rb b/actionpack/test/controller/parameters/mutators_test.rb index 4d9fea7188..312b1e5b27 100644 --- a/actionpack/test/controller/parameters/mutators_test.rb +++ b/actionpack/test/controller/parameters/mutators_test.rb @@ -2,7 +2,6 @@ require "abstract_unit" require "action_controller/metal/strong_parameters" -require "active_support/core_ext/hash/transform_values" class ParametersMutatorsTest < ActiveSupport::TestCase setup do diff --git a/actionpack/test/controller/parameters/serialization_test.rb b/actionpack/test/controller/parameters/serialization_test.rb index 7809d0f357..7708c8e4fe 100644 --- a/actionpack/test/controller/parameters/serialization_test.rb +++ b/actionpack/test/controller/parameters/serialization_test.rb @@ -2,7 +2,6 @@ require "abstract_unit" require "action_controller/metal/strong_parameters" -require "active_support/core_ext/string/strip" class ParametersSerializationTest < ActiveSupport::TestCase setup do @@ -31,7 +30,7 @@ class ParametersSerializationTest < ActiveSupport::TestCase end test "yaml backwardscompatible with psych 2.0.8 format" do - params = YAML.load <<-end_of_yaml.strip_heredoc + params = YAML.load <<~end_of_yaml --- !ruby/hash:ActionController::Parameters key: :value end_of_yaml @@ -41,7 +40,7 @@ class ParametersSerializationTest < ActiveSupport::TestCase end test "yaml backwardscompatible with psych 2.0.9+ format" do - params = YAML.load(<<-end_of_yaml.strip_heredoc) + params = YAML.load(<<~end_of_yaml) --- !ruby/hash-with-ivars:ActionController::Parameters elements: key: :value diff --git a/actionpack/test/dispatch/content_security_policy_test.rb b/actionpack/test/dispatch/content_security_policy_test.rb index 7c4a65a633..5184e4f960 100644 --- a/actionpack/test/dispatch/content_security_policy_test.rb +++ b/actionpack/test/dispatch/content_security_policy_test.rb @@ -8,10 +8,10 @@ class ContentSecurityPolicyTest < ActiveSupport::TestCase end def test_build - assert_equal ";", @policy.build + assert_equal "", @policy.build @policy.script_src :self - assert_equal "script-src 'self';", @policy.build + assert_equal "script-src 'self'", @policy.build end def test_dup @@ -25,34 +25,34 @@ class ContentSecurityPolicyTest < ActiveSupport::TestCase def test_mappings @policy.script_src :data - assert_equal "script-src data:;", @policy.build + assert_equal "script-src data:", @policy.build @policy.script_src :mediastream - assert_equal "script-src mediastream:;", @policy.build + assert_equal "script-src mediastream:", @policy.build @policy.script_src :blob - assert_equal "script-src blob:;", @policy.build + assert_equal "script-src blob:", @policy.build @policy.script_src :filesystem - assert_equal "script-src filesystem:;", @policy.build + assert_equal "script-src filesystem:", @policy.build @policy.script_src :self - assert_equal "script-src 'self';", @policy.build + assert_equal "script-src 'self'", @policy.build @policy.script_src :unsafe_inline - assert_equal "script-src 'unsafe-inline';", @policy.build + assert_equal "script-src 'unsafe-inline'", @policy.build @policy.script_src :unsafe_eval - assert_equal "script-src 'unsafe-eval';", @policy.build + assert_equal "script-src 'unsafe-eval'", @policy.build @policy.script_src :none - assert_equal "script-src 'none';", @policy.build + assert_equal "script-src 'none'", @policy.build @policy.script_src :strict_dynamic - assert_equal "script-src 'strict-dynamic';", @policy.build + assert_equal "script-src 'strict-dynamic'", @policy.build @policy.script_src :none, :report_sample - assert_equal "script-src 'none' 'report-sample';", @policy.build + assert_equal "script-src 'none' 'report-sample'", @policy.build end def test_fetch_directives @@ -131,16 +131,16 @@ class ContentSecurityPolicyTest < ActiveSupport::TestCase def test_document_directives @policy.base_uri "https://example.com" - assert_match %r{base-uri https://example\.com;}, @policy.build + assert_match %r{base-uri https://example\.com}, @policy.build @policy.plugin_types "application/x-shockwave-flash" - assert_match %r{plugin-types application/x-shockwave-flash;}, @policy.build + assert_match %r{plugin-types application/x-shockwave-flash}, @policy.build @policy.sandbox - assert_match %r{sandbox;}, @policy.build + assert_match %r{sandbox}, @policy.build @policy.sandbox "allow-scripts", "allow-modals" - assert_match %r{sandbox allow-scripts allow-modals;}, @policy.build + assert_match %r{sandbox allow-scripts allow-modals}, @policy.build @policy.sandbox false assert_no_match %r{sandbox}, @policy.build @@ -148,35 +148,35 @@ class ContentSecurityPolicyTest < ActiveSupport::TestCase def test_navigation_directives @policy.form_action :self - assert_match %r{form-action 'self';}, @policy.build + assert_match %r{form-action 'self'}, @policy.build @policy.frame_ancestors :self - assert_match %r{frame-ancestors 'self';}, @policy.build + assert_match %r{frame-ancestors 'self'}, @policy.build end def test_reporting_directives @policy.report_uri "/violations" - assert_match %r{report-uri /violations;}, @policy.build + assert_match %r{report-uri /violations}, @policy.build end def test_other_directives @policy.block_all_mixed_content - assert_match %r{block-all-mixed-content;}, @policy.build + assert_match %r{block-all-mixed-content}, @policy.build @policy.block_all_mixed_content false assert_no_match %r{block-all-mixed-content}, @policy.build @policy.require_sri_for :script, :style - assert_match %r{require-sri-for script style;}, @policy.build + assert_match %r{require-sri-for script style}, @policy.build @policy.require_sri_for "script", "style" - assert_match %r{require-sri-for script style;}, @policy.build + assert_match %r{require-sri-for script style}, @policy.build @policy.require_sri_for assert_no_match %r{require-sri-for}, @policy.build @policy.upgrade_insecure_requests - assert_match %r{upgrade-insecure-requests;}, @policy.build + assert_match %r{upgrade-insecure-requests}, @policy.build @policy.upgrade_insecure_requests false assert_no_match %r{upgrade-insecure-requests}, @policy.build @@ -184,13 +184,13 @@ class ContentSecurityPolicyTest < ActiveSupport::TestCase def test_multiple_sources @policy.script_src :self, :https - assert_equal "script-src 'self' https:;", @policy.build + assert_equal "script-src 'self' https:", @policy.build end def test_multiple_directives @policy.script_src :self, :https @policy.style_src :self, :https - assert_equal "script-src 'self' https:; style-src 'self' https:;", @policy.build + assert_equal "script-src 'self' https:; style-src 'self' https:", @policy.build end def test_dynamic_directives @@ -198,12 +198,12 @@ class ContentSecurityPolicyTest < ActiveSupport::TestCase controller = Struct.new(:request).new(request) @policy.script_src -> { request.host } - assert_equal "script-src www.example.com;", @policy.build(controller) + assert_equal "script-src www.example.com", @policy.build(controller) end def test_mixed_static_and_dynamic_directives @policy.script_src :self, -> { "foo.com" }, "bar.com" - assert_equal "script-src 'self' foo.com bar.com;", @policy.build(Object.new) + assert_equal "script-src 'self' foo.com bar.com", @policy.build(Object.new) end def test_invalid_directive_source @@ -316,25 +316,25 @@ class ContentSecurityPolicyIntegrationTest < ActionDispatch::IntegrationTest def test_generates_content_security_policy_header get "/" - assert_policy "default-src 'self';" + assert_policy "default-src 'self'" end def test_generates_inline_content_security_policy get "/inline" - assert_policy "default-src https://example.com;" + assert_policy "default-src https://example.com" end def test_generates_conditional_content_security_policy get "/conditional", params: { condition: "true" } - assert_policy "default-src https://true.example.com;" + assert_policy "default-src https://true.example.com" get "/conditional", params: { condition: "false" } - assert_policy "default-src https://false.example.com;" + assert_policy "default-src https://false.example.com" end def test_generates_report_only_content_security_policy get "/report-only" - assert_policy "default-src 'self'; report-uri /violations;", report_only: true + assert_policy "default-src 'self'; report-uri /violations", report_only: true end private diff --git a/actionpack/test/dispatch/routing_test.rb b/actionpack/test/dispatch/routing_test.rb index 4222eb4eb7..fe314e26b1 100644 --- a/actionpack/test/dispatch/routing_test.rb +++ b/actionpack/test/dispatch/routing_test.rb @@ -4500,7 +4500,7 @@ class TestPortConstraints < ActionDispatch::IntegrationTest get "/integer", to: ok, constraints: { port: 8080 } get "/string", to: ok, constraints: { port: "8080" } - get "/array", to: ok, constraints: { port: [8080] } + get "/array/:idx", to: ok, constraints: { port: [8080], idx: %w[first last] } get "/regexp", to: ok, constraints: { port: /8080/ } end end @@ -4529,7 +4529,10 @@ class TestPortConstraints < ActionDispatch::IntegrationTest get "http://www.example.com/array" assert_response :not_found - get "http://www.example.com:8080/array" + get "http://www.example.com:8080/array/middle" + assert_response :not_found + + get "http://www.example.com:8080/array/first" assert_response :success end diff --git a/actionpack/test/tmp/.gitignore b/actionpack/test/tmp/.gitignore deleted file mode 100644 index e69de29bb2..0000000000 --- a/actionpack/test/tmp/.gitignore +++ /dev/null diff --git a/actionview/.gitignore b/actionview/.gitignore index 0a04b29786..246aabbb7f 100644 --- a/actionview/.gitignore +++ b/actionview/.gitignore @@ -1,2 +1,5 @@ -/lib/assets/compiled -/tmp +/lib/assets/compiled/ +/log/ +/test/fixtures/public/absolute/ +/test/ujs/log/ +/tmp/ diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md index ea7f8b205e..be0afe4198 100644 --- a/actionview/CHANGELOG.md +++ b/actionview/CHANGELOG.md @@ -1,3 +1,8 @@ +## Rails 6.0.0.alpha (Unreleased) ## + +* Rails 6 requires Ruby 2.4.1 or newer. + + *Jeremy Daer* 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 b99137fcf6..49ee1a292b 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.2.2" + s.required_ruby_version = ">= 2.4.1" s.license = "MIT" diff --git a/actionview/lib/action_view/helpers/tags/translator.rb b/actionview/lib/action_view/helpers/tags/translator.rb index fcf96d2c9c..e81ca3aef0 100644 --- a/actionview/lib/action_view/helpers/tags/translator.rb +++ b/actionview/lib/action_view/helpers/tags/translator.rb @@ -16,13 +16,8 @@ module ActionView translated_attribute || human_attribute_name end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_reader :object_name, :method_and_value, :scope, :model - private + attr_reader :object_name, :method_and_value, :scope, :model def i18n_default if model diff --git a/actionview/test/fixtures/public/.gitignore b/actionview/test/fixtures/public/.gitignore deleted file mode 100644 index 312e635ee6..0000000000 --- a/actionview/test/fixtures/public/.gitignore +++ /dev/null @@ -1 +0,0 @@ -absolute/* diff --git a/actionview/test/template/date_helper_test.rb b/actionview/test/template/date_helper_test.rb index 79e52ccc47..94357d5f90 100644 --- a/actionview/test/template/date_helper_test.rb +++ b/actionview/test/template/date_helper_test.rb @@ -144,15 +144,13 @@ class DateHelperTest < ActionView::TestCase rubinius_skip "Date is written in Ruby and relies on Fixnum#/" jruby_skip "Date is written in Ruby and relies on Fixnum#/" - klass = RUBY_VERSION > "2.4" ? Integer : Fixnum - # Make sure that we avoid {Integer,Fixnum}#/ (redefined by mathn) - klass.send :private, :/ + Integer.send :private, :/ from = Time.utc(2004, 6, 6, 21, 45, 0) assert_distance_of_time_in_words(from) ensure - klass.send :public, :/ + Integer.send :public, :/ end def test_time_ago_in_words_passes_include_seconds diff --git a/actionview/test/tmp/.keep b/actionview/test/tmp/.keep deleted file mode 100644 index e69de29bb2..0000000000 --- a/actionview/test/tmp/.keep +++ /dev/null diff --git a/actionview/test/ujs/.gitignore b/actionview/test/ujs/.gitignore deleted file mode 100644 index 31dbbff57c..0000000000 --- a/actionview/test/ujs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/log diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md index e4768eb3d4..097433c3d0 100644 --- a/activejob/CHANGELOG.md +++ b/activejob/CHANGELOG.md @@ -1,3 +1,9 @@ +## Rails 6.0.0.alpha (Unreleased) ## + +* Rails 6 requires Ruby 2.4.1 or newer. + + *Jeremy Daer* + * Add support to define custom argument serializers. *Evgenii Pecherkin*, *Rafael Mendonça França* diff --git a/activejob/activejob.gemspec b/activejob/activejob.gemspec index 71e32f695b..be6292f737 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 queueing backends." - s.required_ruby_version = ">= 2.2.2" + s.required_ruby_version = ">= 2.4.1" s.license = "MIT" diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb index e6ada163e8..86bb0c5540 100644 --- a/activejob/lib/active_job/arguments.rb +++ b/activejob/lib/active_job/arguments.rb @@ -14,8 +14,8 @@ module ActiveJob end # Raised when an unsupported argument type is set as a job argument. We - # currently support NilClass, Integer, Fixnum, Float, String, TrueClass, FalseClass, - # Bignum, BigDecimal, and objects that can be represented as GlobalIDs (ex: Active Record). + # currently support NilClass, Integer, Float, String, TrueClass, FalseClass, + # BigDecimal, and objects that can be represented as GlobalIDs (ex: Active Record). # Raised if you set the key for a Hash something else than a string or # a symbol. Also raised when trying to serialize an object which can't be # identified with a Global ID - such as an unpersisted Active Record model. @@ -25,7 +25,6 @@ module ActiveJob extend self # :nodoc: TYPE_WHITELIST = [ NilClass, String, Integer, Float, BigDecimal, TrueClass, FalseClass ] - TYPE_WHITELIST.push(Fixnum, Bignum) unless 1.class == Integer # Serializes a set of arguments. Whitelisted types are returned # as-is. Arrays/Hashes are serialized element by element. diff --git a/activejob/lib/active_job/logging.rb b/activejob/lib/active_job/logging.rb index f53b7eaee5..96c48d04a7 100644 --- a/activejob/lib/active_job/logging.rb +++ b/activejob/lib/active_job/logging.rb @@ -1,6 +1,5 @@ # frozen_string_literal: true -require "active_support/core_ext/hash/transform_values" require "active_support/core_ext/string/filters" require "active_support/tagged_logging" require "active_support/logger" diff --git a/activejob/lib/active_job/railtie.rb b/activejob/lib/active_job/railtie.rb index 427ad1e3af..d0294854d3 100644 --- a/activejob/lib/active_job/railtie.rb +++ b/activejob/lib/active_job/railtie.rb @@ -14,8 +14,10 @@ module ActiveJob end initializer "active_job.custom_serializers" do |app| - custom_serializers = app.config.active_job.delete(:custom_serializers) - ActiveJob::Serializers.add_serializers custom_serializers + config.after_initialize do + custom_serializers = app.config.active_job.delete(:custom_serializers) + ActiveJob::Serializers.add_serializers custom_serializers + end end initializer "active_job.set_configs" do |app| @@ -23,7 +25,10 @@ module ActiveJob options.queue_adapter ||= :async ActiveSupport.on_load(:active_job) do - options.each { |k, v| send("#{k}=", v) } + options.each do |k, v| + k = "#{k}=" + send(k, v) if respond_to? k + end end end diff --git a/activejob/lib/active_job/serializers.rb b/activejob/lib/active_job/serializers.rb index d9a130fa73..df66e66659 100644 --- a/activejob/lib/active_job/serializers.rb +++ b/activejob/lib/active_job/serializers.rb @@ -4,17 +4,18 @@ require "set" module ActiveJob # The <tt>ActiveJob::Serializers</tt> module is used to store a list of known serializers - # and to add new ones. It also has helpers to serialize/deserialize objects - module Serializers + # and to add new ones. It also has helpers to serialize/deserialize objects. + module Serializers # :nodoc: extend ActiveSupport::Autoload extend ActiveSupport::Concern autoload :ObjectSerializer autoload :SymbolSerializer autoload :DurationSerializer + autoload :DateTimeSerializer autoload :DateSerializer + autoload :TimeWithZoneSerializer autoload :TimeSerializer - autoload :DateTimeSerializer mattr_accessor :_additional_serializers self._additional_serializers = Set.new @@ -22,7 +23,7 @@ module ActiveJob class << self # Returns serialized representative of the passed object. # Will look up through all known serializers. - # Raises `ActiveJob::SerializationError` if it can't find a proper serializer. + # Raises <tt>ActiveJob::SerializationError</tt> if it can't find a proper serializer. def serialize(argument) serializer = serializers.detect { |s| s.serialize?(argument) } raise SerializationError.new("Unsupported argument type: #{argument.class.name}") unless serializer @@ -31,25 +32,25 @@ module ActiveJob # Returns deserialized object. # Will look up through all known serializers. - # If no serializers found will raise `ArgumentError` + # If no serializer found will raise <tt>ArgumentError</tt>. def deserialize(argument) serializer_name = argument[Arguments::OBJECT_SERIALIZER_KEY] raise ArgumentError, "Serializer name is not present in the argument: #{argument.inspect}" unless serializer_name serializer = serializer_name.safe_constantize - raise ArgumentError, "Serializer #{serializer_name} is not know" unless serializer + raise ArgumentError, "Serializer #{serializer_name} is not known" unless serializer serializer.deserialize(argument) end - # Returns list of known serializers + # Returns list of known serializers. def serializers self._additional_serializers end - # Adds a new serializer to a list of known serializers + # Adds new serializers to a list of known serializers. def add_serializers(*new_serializers) - self._additional_serializers += new_serializers + self._additional_serializers += new_serializers.flatten end end @@ -57,6 +58,7 @@ module ActiveJob DurationSerializer, DateTimeSerializer, DateSerializer, + TimeWithZoneSerializer, TimeSerializer end end diff --git a/activejob/lib/active_job/serializers/object_serializer.rb b/activejob/lib/active_job/serializers/object_serializer.rb index 9f59e8236f..1dfd1e44be 100644 --- a/activejob/lib/active_job/serializers/object_serializer.rb +++ b/activejob/lib/active_job/serializers/object_serializer.rb @@ -2,25 +2,25 @@ module ActiveJob module Serializers - # Base class for serializing and deserializing custom times. + # Base class for serializing and deserializing custom objects. # - # Example + # Example: # - # class MoneySerializer < ActiveJob::Serializers::ObjectSerializer - # def serialize(money) - # super("cents" => money.cents, "currency" => money.currency) - # end + # class MoneySerializer < ActiveJob::Serializers::ObjectSerializer + # def serialize(money) + # super("amount" => money.amount, "currency" => money.currency) + # end # - # def deserialize(hash) - # Money.new(hash["cents"], hash["currency"]) - # end + # def deserialize(hash) + # Money.new(hash["amount"], hash["currency"]) + # end # - # private + # private # - # def klass - # Money - # end - # end + # def klass + # Money + # end + # end class ObjectSerializer include Singleton @@ -43,10 +43,10 @@ module ActiveJob raise NotImplementedError end - protected + private # The class of the object that will be serialized. - def klass + def klass # :doc: raise NotImplementedError end end diff --git a/activejob/lib/active_job/serializers/time_with_zone_serializer.rb b/activejob/lib/active_job/serializers/time_with_zone_serializer.rb new file mode 100644 index 0000000000..43017fc75b --- /dev/null +++ b/activejob/lib/active_job/serializers/time_with_zone_serializer.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module ActiveJob + module Serializers + class TimeWithZoneSerializer < ObjectSerializer # :nodoc: + def serialize(time) + super("value" => time.iso8601) + end + + def deserialize(hash) + Time.iso8601(hash["value"]).in_time_zone + end + + private + + def klass + ActiveSupport::TimeWithZone + end + end + end +end diff --git a/activejob/test/cases/argument_serialization_test.rb b/activejob/test/cases/argument_serialization_test.rb index ff6ac6fc43..e5f1f087fe 100644 --- a/activejob/test/cases/argument_serialization_test.rb +++ b/activejob/test/cases/argument_serialization_test.rb @@ -12,7 +12,7 @@ class ArgumentSerializationTest < ActiveSupport::TestCase end [ nil, 1, 1.0, 1_000_000_000_000_000_000_000, - "a", true, false, BigDecimal.new(5), + "a", true, false, BigDecimal(5), :a, 1.day, Date.new(2001, 2, 3), Time.new(2002, 10, 31, 2, 2, 2, "+02:00"), DateTime.new(2001, 2, 3, 4, 5, 6, "+03:00"), ActiveSupport::TimeWithZone.new(Time.utc(1999, 12, 31, 23, 59, 59), ActiveSupport::TimeZone["UTC"]), @@ -102,6 +102,14 @@ class ArgumentSerializationTest < ActiveSupport::TestCase assert_instance_of ActiveSupport::HashWithIndifferentAccess, perform_round_trip([indifferent_access]).first end + test "should maintain time with zone" do + Time.use_zone "Alaska" do + time_with_zone = Time.new(2002, 10, 31, 2, 2, 2).in_time_zone + assert_instance_of ActiveSupport::TimeWithZone, perform_round_trip([time_with_zone]).first + assert_arguments_unchanged time_with_zone + end + end + test "should disallow non-string/symbol hash keys" do assert_raises ActiveJob::SerializationError do ActiveJob::Arguments.serialize [ { 1 => 2 } ] diff --git a/activejob/test/cases/serializers_test.rb b/activejob/test/cases/serializers_test.rb index a86f168d03..bee0c061bd 100644 --- a/activejob/test/cases/serializers_test.rb +++ b/activejob/test/cases/serializers_test.rb @@ -73,7 +73,7 @@ class SerializersTest < ActiveSupport::TestCase ActiveJob::Serializers.deserialize(hash) end assert_equal( - "Serializer DoNotExist is not know", + "Serializer DoNotExist is not known", error.message ) end diff --git a/activemodel/CHANGELOG.md b/activemodel/CHANGELOG.md index 13ea7d0bf3..b28c83e4ed 100644 --- a/activemodel/CHANGELOG.md +++ b/activemodel/CHANGELOG.md @@ -1,3 +1,8 @@ +## Rails 6.0.0.alpha (Unreleased) ## + +* Rails 6 requires Ruby 2.4.1 or newer. + + *Jeremy Daer* 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 a070a2898c..7be466dc4c 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.2.2" + s.required_ruby_version = ">= 2.4.1" s.license = "MIT" diff --git a/activemodel/lib/active_model/attribute_mutation_tracker.rb b/activemodel/lib/active_model/attribute_mutation_tracker.rb index f55613ecd5..8e92c8807f 100644 --- a/activemodel/lib/active_model/attribute_mutation_tracker.rb +++ b/activemodel/lib/active_model/attribute_mutation_tracker.rb @@ -69,13 +69,8 @@ module ActiveModel forced_changes << attr_name.to_s end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_reader :attributes, :forced_changes - private + attr_reader :attributes, :forced_changes def attr_names attributes.keys diff --git a/activemodel/lib/active_model/type/integer.rb b/activemodel/lib/active_model/type/integer.rb index fe396998a3..da74aaa3c5 100644 --- a/activemodel/lib/active_model/type/integer.rb +++ b/activemodel/lib/active_model/type/integer.rb @@ -31,13 +31,8 @@ module ActiveModel result end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_reader :range - private + attr_reader :range def cast_value(value) case value diff --git a/activemodel/lib/active_model/type/registry.rb b/activemodel/lib/active_model/type/registry.rb index 7272d7b0c5..a19dc0f011 100644 --- a/activemodel/lib/active_model/type/registry.rb +++ b/activemodel/lib/active_model/type/registry.rb @@ -23,13 +23,8 @@ module ActiveModel end end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_reader :registrations - private + attr_reader :registrations def registration_klass Registration @@ -59,10 +54,7 @@ module ActiveModel type_name == name end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - + private attr_reader :name, :block end end diff --git a/activemodel/lib/active_model/validations/acceptance.rb b/activemodel/lib/active_model/validations/acceptance.rb index f35e4dec7f..ea3a6b52ab 100644 --- a/activemodel/lib/active_model/validations/acceptance.rb +++ b/activemodel/lib/active_model/validations/acceptance.rb @@ -58,13 +58,8 @@ module ActiveModel klass.send(:attr_writer, *attr_writers) end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_reader :attributes - private + attr_reader :attributes def convert_to_reader_name(method_name) method_name.to_s.chomp("=") diff --git a/activemodel/lib/active_model/validations/clusivity.rb b/activemodel/lib/active_model/validations/clusivity.rb index 0b9b5ce6a1..bafb8e2106 100644 --- a/activemodel/lib/active_model/validations/clusivity.rb +++ b/activemodel/lib/active_model/validations/clusivity.rb @@ -32,7 +32,7 @@ module ActiveModel @delimiter ||= options[:in] || options[:within] end - # In Ruby 2.2 <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all + # After Ruby 2.2, <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all # possible values in the range for equality, which is slower but more accurate. # <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range # endpoints, which is fast but is only accurate on Numeric, Time, Date, diff --git a/activemodel/test/cases/attribute_assignment_test.rb b/activemodel/test/cases/attribute_assignment_test.rb index 5ecf0a69c4..448f8587fd 100644 --- a/activemodel/test/cases/attribute_assignment_test.rb +++ b/activemodel/test/cases/attribute_assignment_test.rb @@ -18,10 +18,7 @@ class AttributeAssignmentTest < ActiveModel::TestCase raise ErrorFromAttributeWriter end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - + private attr_writer :metadata end diff --git a/activemodel/test/cases/errors_test.rb b/activemodel/test/cases/errors_test.rb index fe351f922d..cb6a8c43d5 100644 --- a/activemodel/test/cases/errors_test.rb +++ b/activemodel/test/cases/errors_test.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "cases/helper" -require "active_support/core_ext/string/strip" require "yaml" class ErrorsTest < ActiveModel::TestCase @@ -406,7 +405,7 @@ class ErrorsTest < ActiveModel::TestCase end test "errors are backward compatible with the Rails 4.2 format" do - yaml = <<-CODE.strip_heredoc + yaml = <<~CODE --- !ruby/object:ActiveModel::Errors base: &1 !ruby/object:ErrorsTest::Person errors: !ruby/object:ActiveModel::Errors diff --git a/activerecord/.gitignore b/activerecord/.gitignore new file mode 100644 index 0000000000..884ee009eb --- /dev/null +++ b/activerecord/.gitignore @@ -0,0 +1,3 @@ +/sqlnet.log +/test/config.yml +/test/fixtures/*.sqlite* diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index bbb0ec575e..325c4abfc8 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,9 +1,21 @@ ## Rails 6.0.0.alpha (Unreleased) ## +* Rails 6 requires Ruby 2.4.1 or newer. + + *Jeremy Daer* + +* Deprecate `update_attributes`/`!` in favor of `update`/`!`. + + *Eddie Lebow* + * Add ActiveRecord::Base.create_or_find_by/! to deal with the SELECT/INSERT race condition in ActiveRecord::Base.find_or_create_by/! by leaning on unique constraints in the database. *DHH* +* Add `Relation#pick` as short-hand for single-value plucks. + + *DHH* + Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/activerecord/CHANGELOG.md) for previous changes. diff --git a/activerecord/activerecord.gemspec b/activerecord/activerecord.gemspec index 8e42a11df4..b43e7c50f5 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.2.2" + s.required_ruby_version = ">= 2.4.1" s.license = "MIT" diff --git a/activerecord/examples/.gitignore b/activerecord/examples/.gitignore deleted file mode 100644 index 0dfc1cb7fb..0000000000 --- a/activerecord/examples/.gitignore +++ /dev/null @@ -1 +0,0 @@ -performance.sql diff --git a/activerecord/lib/active_record.rb b/activerecord/lib/active_record.rb index b4377ad6be..d43378c64f 100644 --- a/activerecord/lib/active_record.rb +++ b/activerecord/lib/active_record.rb @@ -163,6 +163,7 @@ module ActiveRecord "active_record/tasks/postgresql_database_tasks" end + autoload :TestDatabases, "active_record/test_databases" autoload :TestFixtures, "active_record/fixtures" def self.eager_load! diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb index 11967e0571..ad41ec8d2f 100644 --- a/activerecord/lib/active_record/associations/association_scope.rb +++ b/activerecord/lib/active_record/associations/association_scope.rb @@ -46,13 +46,9 @@ module ActiveRecord binds end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - + private attr_reader :value_transformation - private def join(table, constraint) table.create_join(table, table.create_on(constraint)) end @@ -135,7 +131,7 @@ module ActiveRecord item = eval_scope(reflection, scope_chain_item, owner) if scope_chain_item == chain_head.scope - scope.merge! item.except(:where, :includes) + scope.merge! item.except(:where, :includes, :unscope, :order) end reflection.all_includes do @@ -144,7 +140,7 @@ module ActiveRecord scope.unscope!(*item.unscope_values) scope.where_clause += item.where_clause - scope.order_values |= item.order_values + scope.order_values = item.order_values | scope.order_values end end diff --git a/activerecord/lib/active_record/attribute_methods/primary_key.rb b/activerecord/lib/active_record/attribute_methods/primary_key.rb index d8fc046e10..2907547634 100644 --- a/activerecord/lib/active_record/attribute_methods/primary_key.rb +++ b/activerecord/lib/active_record/attribute_methods/primary_key.rb @@ -131,7 +131,7 @@ module ActiveRecord def suppress_composite_primary_key(pk) return pk unless pk.is_a?(Array) - warn <<-WARNING.strip_heredoc + warn <<~WARNING WARNING: Active Record does not support composite primary key. #{table_name} has composite primary key. Composite primary key is ignored. diff --git a/activerecord/lib/active_record/attribute_methods/serialization.rb b/activerecord/lib/active_record/attribute_methods/serialization.rb index ebc2baed34..6e0e90f39c 100644 --- a/activerecord/lib/active_record/attribute_methods/serialization.rb +++ b/activerecord/lib/active_record/attribute_methods/serialization.rb @@ -7,7 +7,7 @@ module ActiveRecord class ColumnNotSerializableError < StandardError def initialize(name, type) - super <<-EOS.strip_heredoc + super <<~EOS Column `#{name}` of type #{type.class} does not support `serialize` feature. Usually it means that you are trying to use `serialize` on a column that already implements serialization natively. diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index b7ad944cec..cc99401390 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -9,7 +9,6 @@ require "active_support/core_ext/module/attribute_accessors" require "active_support/core_ext/array/extract_options" require "active_support/core_ext/hash/deep_merge" require "active_support/core_ext/hash/slice" -require "active_support/core_ext/hash/transform_values" require "active_support/core_ext/string/behavior" require "active_support/core_ext/kernel/singleton_class" require "active_support/core_ext/module/introspection" diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb index 4a191d337c..22abdee4b8 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_creation.rb @@ -1,7 +1,5 @@ # frozen_string_literal: true -require "active_support/core_ext/string/strip" - module ActiveRecord module ConnectionAdapters class AbstractAdapter @@ -66,7 +64,7 @@ module ActiveRecord end def visit_ForeignKeyDefinition(o) - sql = <<-SQL.strip_heredoc + sql = +<<~SQL CONSTRAINT #{quote_column_name(o.name)} FOREIGN KEY (#{quote_column_name(o.column)}) REFERENCES #{quote_table_name(o.to_table)} (#{quote_column_name(o.primary_key)}) @@ -133,7 +131,7 @@ module ActiveRecord when :cascade then "ON #{action} CASCADE" when :restrict then "ON #{action} RESTRICT" else - raise ArgumentError, <<-MSG.strip_heredoc + raise ArgumentError, <<~MSG '#{dependency}' is not supported for :on_update or :on_delete. Supported values are: :nullify, :cascade, :restrict MSG diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb index 0594b4b485..584a86da21 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_definitions.rb @@ -151,13 +151,8 @@ module ActiveRecord end end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_reader :name, :polymorphic, :index, :foreign_key, :type, :options - private + attr_reader :name, :polymorphic, :index, :foreign_key, :type, :options def as_options(value) value.is_a?(Hash) ? value : {} 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 1fb0bd8d56..608258d05c 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @@ -11,8 +11,6 @@ require "active_record/connection_adapters/mysql/schema_dumper" require "active_record/connection_adapters/mysql/schema_statements" require "active_record/connection_adapters/mysql/type_metadata" -require "active_support/core_ext/string/strip" - module ActiveRecord module ConnectionAdapters class AbstractMysqlAdapter < AbstractAdapter @@ -284,7 +282,7 @@ module ActiveRecord def table_comment(table_name) # :nodoc: scope = quoted_scope(table_name) - query_value(<<-SQL.strip_heredoc, "SCHEMA").presence + query_value(<<~SQL, "SCHEMA").presence SELECT table_comment FROM information_schema.tables WHERE table_schema = #{scope[:schema]} @@ -392,7 +390,7 @@ module ActiveRecord scope = quoted_scope(table_name) - fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA") + fk_info = exec_query(<<~SQL, "SCHEMA") SELECT fk.referenced_table_name AS 'to_table', fk.referenced_column_name AS 'primary_key', fk.column_name AS 'column', @@ -480,7 +478,7 @@ module ActiveRecord scope = quoted_scope(table_name) - query_values(<<-SQL.strip_heredoc, "SCHEMA") + query_values(<<~SQL, "SCHEMA") SELECT column_name FROM information_schema.key_column_usage WHERE constraint_name = 'PRIMARY' diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb index 587e95d192..e9a79526f9 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/oid/bit.rb @@ -43,10 +43,7 @@ module ActiveRecord /\A[0-9A-F]*\Z/i.match?(value) end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - + private attr_reader :value end end diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb index 7999434ad8..6f3db772cd 100644 --- a/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb +++ b/activerecord/lib/active_record/connection_adapters/postgresql/schema_statements.rb @@ -115,7 +115,7 @@ module ActiveRecord if indkey.include?(0) columns = expressions else - columns = Hash[query(<<-SQL.strip_heredoc, "SCHEMA")].values_at(*indkey).compact + columns = Hash[query(<<~SQL, "SCHEMA")].values_at(*indkey).compact SELECT a.attnum, a.attname FROM pg_attribute a WHERE a.attrelid = #{oid} @@ -158,7 +158,7 @@ module ActiveRecord def table_comment(table_name) # :nodoc: scope = quoted_scope(table_name, type: "BASE TABLE") if scope[:name] - query_value(<<-SQL.strip_heredoc, "SCHEMA") + query_value(<<~SQL, "SCHEMA") SELECT pg_catalog.obj_description(c.oid, 'pg_class') FROM pg_catalog.pg_class c LEFT JOIN pg_namespace n ON n.oid = c.relnamespace @@ -353,7 +353,7 @@ module ActiveRecord end def primary_keys(table_name) # :nodoc: - query_values(<<-SQL.strip_heredoc, "SCHEMA") + query_values(<<~SQL, "SCHEMA") SELECT a.attname FROM ( SELECT indrelid, indkey, generate_subscripts(indkey, 1) idx @@ -502,7 +502,7 @@ module ActiveRecord def foreign_keys(table_name) scope = quoted_scope(table_name) - fk_info = exec_query(<<-SQL.strip_heredoc, "SCHEMA") + fk_info = exec_query(<<~SQL, "SCHEMA") SELECT t2.oid::regclass::text AS to_table, a1.attname AS column, a2.attname AS primary_key, c.conname AS name, c.confupdtype AS on_update, c.confdeltype AS on_delete, c.convalidated AS valid FROM pg_constraint c JOIN pg_class t1 ON c.conrelid = t1.oid diff --git a/activerecord/lib/active_record/enum.rb b/activerecord/lib/active_record/enum.rb index 1a3e6e4d09..23ecb24542 100644 --- a/activerecord/lib/active_record/enum.rb +++ b/activerecord/lib/active_record/enum.rb @@ -141,10 +141,7 @@ module ActiveRecord end end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - + private attr_reader :name, :mapping, :subtype end diff --git a/activerecord/lib/active_record/errors.rb b/activerecord/lib/active_record/errors.rb index efcbd44776..c2a180c939 100644 --- a/activerecord/lib/active_record/errors.rb +++ b/activerecord/lib/active_record/errors.rb @@ -120,13 +120,13 @@ module ActiveRecord def initialize(adapter = nil, message: nil, table: nil, foreign_key: nil, target_table: nil, primary_key: nil) @adapter = adapter if table - msg = <<-EOM.strip_heredoc + msg = +<<~EOM Column `#{foreign_key}` on table `#{table}` has a type of `#{column_type(table, foreign_key)}`. This does not match column `#{primary_key}` on `#{target_table}`, which has type `#{column_type(target_table, primary_key)}`. To resolve this issue, change the type of the `#{foreign_key}` column on `#{table}` to be :integer. (For example `t.integer #{foreign_key}`). EOM else - msg = <<-EOM + msg = +<<~EOM There is a mismatch between the foreign key and primary key column types. Verify that the foreign key column type and the primary key of the associated table match types. EOM diff --git a/activerecord/lib/active_record/fixtures.rb b/activerecord/lib/active_record/fixtures.rb index d9a75d9ad6..8f022ff7a7 100644 --- a/activerecord/lib/active_record/fixtures.rb +++ b/activerecord/lib/active_record/fixtures.rb @@ -874,6 +874,7 @@ module ActiveRecord class_attribute :use_instantiated_fixtures, default: false # true, false, or :no_instances class_attribute :pre_loaded_fixtures, default: false class_attribute :config, default: ActiveRecord::Base + class_attribute :lock_threads, default: true end module ClassMethods @@ -973,7 +974,7 @@ module ActiveRecord @fixture_connections = enlist_fixture_connections @fixture_connections.each do |connection| connection.begin_transaction joinable: false - connection.pool.lock_thread = true + connection.pool.lock_thread = true if lock_threads end # When connections are established in the future, begin a transaction too @@ -989,7 +990,7 @@ module ActiveRecord if connection && !@fixture_connections.include?(connection) connection.begin_transaction joinable: false - connection.pool.lock_thread = true + connection.pool.lock_thread = true if lock_threads @fixture_connections << connection end end diff --git a/activerecord/lib/active_record/internal_metadata.rb b/activerecord/lib/active_record/internal_metadata.rb index 5a65edf27e..3626a13d7c 100644 --- a/activerecord/lib/active_record/internal_metadata.rb +++ b/activerecord/lib/active_record/internal_metadata.rb @@ -17,7 +17,7 @@ module ActiveRecord end def []=(key, value) - find_or_initialize_by(key: key).update_attributes!(value: value) + find_or_initialize_by(key: key).update!(value: value) end def [](key) diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb index 81ef4828f8..087632b10f 100644 --- a/activerecord/lib/active_record/migration/command_recorder.rb +++ b/activerecord/lib/active_record/migration/command_recorder.rb @@ -85,7 +85,7 @@ module ActiveRecord # invert the +command+. def inverse_of(command, args, &block) method = :"invert_#{command}" - raise IrreversibleMigration, <<-MSG.strip_heredoc unless respond_to?(method, true) + raise IrreversibleMigration, <<~MSG unless respond_to?(method, true) This migration uses #{command}, which is not automatically reversible. To make the migration reversible you can either: 1. Define #up and #down methods in place of the #change method. diff --git a/activerecord/lib/active_record/persistence.rb b/activerecord/lib/active_record/persistence.rb index a45d011d75..88b971327b 100644 --- a/activerecord/lib/active_record/persistence.rb +++ b/activerecord/lib/active_record/persistence.rb @@ -418,6 +418,7 @@ module ActiveRecord end alias update_attributes update + deprecate :update_attributes # Updates its receiver just like #update but calls #save! instead # of +save+, so an exception is raised if the record is invalid and saving will fail. @@ -431,6 +432,7 @@ module ActiveRecord end alias update_attributes! update! + deprecate :update_attributes! # Equivalent to <code>update_columns(name => value)</code>. def update_column(name, value) diff --git a/activerecord/lib/active_record/query_cache.rb b/activerecord/lib/active_record/query_cache.rb index 8e23128333..c8e340712d 100644 --- a/activerecord/lib/active_record/query_cache.rb +++ b/activerecord/lib/active_record/query_cache.rb @@ -7,20 +7,20 @@ module ActiveRecord # Enable the query cache within the block if Active Record is configured. # If it's not, it will execute the given block. def cache(&block) - if configurations.empty? - yield - else + if connected? || !configurations.empty? connection.cache(&block) + else + yield end end # Disable the query cache within the block if Active Record is configured. # If it's not, it will execute the given block. def uncached(&block) - if configurations.empty? - yield - else + if connected? || !configurations.empty? connection.uncached(&block) + else + yield end end end diff --git a/activerecord/lib/active_record/railties/controller_runtime.rb b/activerecord/lib/active_record/railties/controller_runtime.rb index 2ae733f657..309441a057 100644 --- a/activerecord/lib/active_record/railties/controller_runtime.rb +++ b/activerecord/lib/active_record/railties/controller_runtime.rb @@ -8,49 +8,44 @@ module ActiveRecord module ControllerRuntime #:nodoc: extend ActiveSupport::Concern - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_internal :db_runtime - - private - - def process_action(action, *args) - # We also need to reset the runtime before each action - # because of queries in middleware or in cases we are streaming - # and it won't be cleaned up by the method below. - ActiveRecord::LogSubscriber.reset_runtime - super + module ClassMethods # :nodoc: + def log_process_action(payload) + messages, db_runtime = super, payload[:db_runtime] + messages << ("ActiveRecord: %.1fms" % db_runtime.to_f) if db_runtime + messages + end end - def cleanup_view_runtime - if logger && logger.info? && ActiveRecord::Base.connected? - db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime - self.db_runtime = (db_runtime || 0) + db_rt_before_render - runtime = super - db_rt_after_render = ActiveRecord::LogSubscriber.reset_runtime - self.db_runtime += db_rt_after_render - runtime - db_rt_after_render - else + private + attr_internal :db_runtime + + def process_action(action, *args) + # We also need to reset the runtime before each action + # because of queries in middleware or in cases we are streaming + # and it won't be cleaned up by the method below. + ActiveRecord::LogSubscriber.reset_runtime super end - end - def append_info_to_payload(payload) - super - if ActiveRecord::Base.connected? - payload[:db_runtime] = (db_runtime || 0) + ActiveRecord::LogSubscriber.reset_runtime + def cleanup_view_runtime + if logger && logger.info? && ActiveRecord::Base.connected? + db_rt_before_render = ActiveRecord::LogSubscriber.reset_runtime + self.db_runtime = (db_runtime || 0) + db_rt_before_render + runtime = super + db_rt_after_render = ActiveRecord::LogSubscriber.reset_runtime + self.db_runtime += db_rt_after_render + runtime - db_rt_after_render + else + super + end end - end - module ClassMethods # :nodoc: - def log_process_action(payload) - messages, db_runtime = super, payload[:db_runtime] - messages << ("ActiveRecord: %.1fms" % db_runtime.to_f) if db_runtime - messages + def append_info_to_payload(payload) + super + if ActiveRecord::Base.connected? + payload[:db_runtime] = (db_runtime || 0) + ActiveRecord::LogSubscriber.reset_runtime + end end - end end end end diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index e640d75d2f..c66c7778f4 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -962,16 +962,14 @@ module ActiveRecord collect_join_reflections(seed + [self]) end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. protected - attr_reader :delegate_reflection - def actual_source_reflection # FIXME: this is a horrible name source_reflection.actual_source_reflection end private + attr_reader :delegate_reflection + def collect_join_reflections(seed) a = source_reflection.add_as_source seed if options[:source_type] diff --git a/activerecord/lib/active_record/relation/delegation.rb b/activerecord/lib/active_record/relation/delegation.rb index 4863befec8..4b16b49cdf 100644 --- a/activerecord/lib/active_record/relation/delegation.rb +++ b/activerecord/lib/active_record/relation/delegation.rb @@ -38,7 +38,7 @@ module ActiveRecord # may vary depending on the klass of a relation, so we create a subclass of Relation # for each different klass, and the delegations are compiled into that subclass only. - delegate :to_xml, :encode_with, :length, :each, :uniq, :join, + delegate :to_xml, :encode_with, :length, :each, :join, :[], :&, :|, :+, :-, :sample, :reverse, :rotate, :compact, :in_groups, :in_groups_of, :to_sentence, :to_formatted_s, :as_json, :shuffle, :split, :slice, :index, :rindex, to: :records diff --git a/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb b/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb index 28c7483c95..88cd71cf69 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/association_query_value.rb @@ -12,12 +12,9 @@ module ActiveRecord [associated_table.association_join_foreign_key.to_s => ids] end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected + private attr_reader :associated_table, :value - private def ids case value when Relation diff --git a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb index e8e2f2c626..a5e9a0473e 100644 --- a/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +++ b/activerecord/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb @@ -17,12 +17,9 @@ module ActiveRecord end end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected + private attr_reader :associated_table, :values - private def type_to_ids_mapping default_hash = Hash.new { |hsh, key| hsh[key] = [] } values.each_with_object(default_hash) { |value, hash| hash[base_class(value).name] << convert_to_id(value) } diff --git a/activerecord/lib/active_record/table_metadata.rb b/activerecord/lib/active_record/table_metadata.rb index d3b9171159..b67479fb6a 100644 --- a/activerecord/lib/active_record/table_metadata.rb +++ b/activerecord/lib/active_record/table_metadata.rb @@ -73,10 +73,7 @@ module ActiveRecord klass.reflect_on_aggregation(aggregation_name) end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - + private attr_reader :klass, :arel_table, :association end end diff --git a/activerecord/lib/active_record/test_databases.rb b/activerecord/lib/active_record/test_databases.rb new file mode 100644 index 0000000000..606a3b0fb5 --- /dev/null +++ b/activerecord/lib/active_record/test_databases.rb @@ -0,0 +1,38 @@ +# frozen_string_literal: true + +require "active_support/testing/parallelization" + +module ActiveRecord + module TestDatabases # :nodoc: + ActiveSupport::Testing::Parallelization.after_fork_hook do |i| + create_and_migrate(i, spec_name: Rails.env) + end + + ActiveSupport::Testing::Parallelization.run_cleanup_hook do |i| + drop(i, spec_name: Rails.env) + end + + def self.create_and_migrate(i, spec_name:) + old, ENV["VERBOSE"] = ENV["VERBOSE"], "false" + + connection_spec = ActiveRecord::Base.configurations[spec_name] + + connection_spec["database"] += "-#{i}" + ActiveRecord::Tasks::DatabaseTasks.create(connection_spec) + ActiveRecord::Base.establish_connection(connection_spec) + ActiveRecord::Tasks::DatabaseTasks.migrate + ensure + ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[Rails.env]) + ENV["VERBOSE"] = old + end + + def self.drop(i, spec_name:) + old, ENV["VERBOSE"] = ENV["VERBOSE"], "false" + connection_spec = ActiveRecord::Base.configurations[spec_name] + + ActiveRecord::Tasks::DatabaseTasks.drop(connection_spec) + ensure + ENV["VERBOSE"] = old + end + end +end diff --git a/activerecord/lib/active_record/type/adapter_specific_registry.rb b/activerecord/lib/active_record/type/adapter_specific_registry.rb index e7468aa542..b300fdfa05 100644 --- a/activerecord/lib/active_record/type/adapter_specific_registry.rb +++ b/activerecord/lib/active_record/type/adapter_specific_registry.rb @@ -52,8 +52,6 @@ module ActiveRecord priority <=> other.priority end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. protected attr_reader :name, :block, :adapter, :override @@ -114,13 +112,8 @@ module ActiveRecord super | 4 end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_reader :options, :klass - private + attr_reader :options, :klass def matches_options?(**kwargs) options.all? do |key, value| diff --git a/activerecord/lib/active_record/type_caster/connection.rb b/activerecord/lib/active_record/type_caster/connection.rb index af4e4e37e2..7cf8181d8e 100644 --- a/activerecord/lib/active_record/type_caster/connection.rb +++ b/activerecord/lib/active_record/type_caster/connection.rb @@ -14,15 +14,10 @@ module ActiveRecord connection.type_cast_from_column(column, value) end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - + private attr_reader :table_name delegate :connection, to: :@klass - private - def column_for(attribute_name) if connection.schema_cache.data_source_exists?(table_name) connection.schema_cache.columns_hash(table_name)[attribute_name.to_s] diff --git a/activerecord/lib/active_record/type_caster/map.rb b/activerecord/lib/active_record/type_caster/map.rb index c5cfdba8b1..663cdadb03 100644 --- a/activerecord/lib/active_record/type_caster/map.rb +++ b/activerecord/lib/active_record/type_caster/map.rb @@ -13,10 +13,7 @@ module ActiveRecord type.serialize(value) end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - + private attr_reader :types end end diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb index 856fcc5897..a07b00ef79 100644 --- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb +++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb @@ -15,12 +15,8 @@ module ActiveRecord migration_template @migration_template, File.join(db_migrate_path, "#{file_name}.rb") end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - attr_reader :migration_action, :join_tables - private + attr_reader :migration_action, :join_tables # Sets the default migration template that is being used for the generation of the migration. # Depending on command line arguments, the migration template and the table name instance diff --git a/activerecord/test/.gitignore b/activerecord/test/.gitignore deleted file mode 100644 index a0ec5967dd..0000000000 --- a/activerecord/test/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/config.yml diff --git a/activerecord/test/cases/adapter_test.rb b/activerecord/test/cases/adapter_test.rb index 0092146e09..4c41a68407 100644 --- a/activerecord/test/cases/adapter_test.rb +++ b/activerecord/test/cases/adapter_test.rb @@ -20,7 +20,7 @@ module ActiveRecord b = Book.create(name: "my \x00 book") b.reload assert_equal "my \x00 book", b.name - b.update_attributes(name: "my other \x00 book") + b.update(name: "my other \x00 book") b.reload assert_equal "my other \x00 book", b.name end diff --git a/activerecord/test/cases/adapters/mysql2/transaction_test.rb b/activerecord/test/cases/adapters/mysql2/transaction_test.rb index f921515c10..30455fbde5 100644 --- a/activerecord/test/cases/adapters/mysql2/transaction_test.rb +++ b/activerecord/test/cases/adapters/mysql2/transaction_test.rb @@ -46,7 +46,7 @@ module ActiveRecord Sample.transaction do s1.lock! barrier.wait - s2.update_attributes value: 1 + s2.update value: 1 end end @@ -54,7 +54,7 @@ module ActiveRecord Sample.transaction do s2.lock! barrier.wait - s1.update_attributes value: 2 + s1.update value: 2 end ensure thread.join diff --git a/activerecord/test/cases/adapters/postgresql/transaction_test.rb b/activerecord/test/cases/adapters/postgresql/transaction_test.rb index 9821b103df..8e952a9408 100644 --- a/activerecord/test/cases/adapters/postgresql/transaction_test.rb +++ b/activerecord/test/cases/adapters/postgresql/transaction_test.rb @@ -76,7 +76,7 @@ module ActiveRecord Sample.transaction do s1.lock! barrier.wait - s2.update_attributes value: 1 + s2.update value: 1 end end @@ -84,7 +84,7 @@ module ActiveRecord Sample.transaction do s2.lock! barrier.wait - s1.update_attributes value: 2 + s1.update value: 2 end ensure thread.join diff --git a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb index d9e05cf7e2..5f771fe85f 100644 --- a/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_and_belongs_to_many_associations_test.rb @@ -662,7 +662,7 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase assert_includes developer.sym_special_projects, sp end - def test_update_attributes_after_push_without_duplicate_join_table_rows + def test_update_columns_after_push_without_duplicate_join_table_rows developer = Developer.new("name" => "Kano") project = SpecialProject.create("name" => "Special Project") assert developer.save diff --git a/activerecord/test/cases/associations/has_many_associations_test.rb b/activerecord/test/cases/associations/has_many_associations_test.rb index 9ee42cef0f..00821f2319 100644 --- a/activerecord/test/cases/associations/has_many_associations_test.rb +++ b/activerecord/test/cases/associations/has_many_associations_test.rb @@ -1211,20 +1211,20 @@ class HasManyAssociationsTest < ActiveRecord::TestCase end end - def test_calling_update_attributes_on_id_changes_the_counter_cache + def test_calling_update_on_id_changes_the_counter_cache topic = Topic.order("id ASC").first original_count = topic.replies.to_a.size assert_equal original_count, topic.replies_count first_reply = topic.replies.first - first_reply.update_attributes(parent_id: nil) + first_reply.update(parent_id: nil) assert_equal original_count - 1, topic.reload.replies_count - first_reply.update_attributes(parent_id: topic.id) + first_reply.update(parent_id: topic.id) assert_equal original_count, topic.reload.replies_count end - def test_calling_update_attributes_changing_ids_doesnt_change_counter_cache + def test_calling_update_changing_ids_doesnt_change_counter_cache topic1 = Topic.find(1) topic2 = Topic.find(3) original_count1 = topic1.replies.to_a.size @@ -1233,11 +1233,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase reply1 = topic1.replies.first reply2 = topic2.replies.first - reply1.update_attributes(parent_id: topic2.id) + reply1.update(parent_id: topic2.id) assert_equal original_count1 - 1, topic1.reload.replies_count assert_equal original_count2 + 1, topic2.reload.replies_count - reply2.update_attributes(parent_id: topic1.id) + reply2.update(parent_id: topic1.id) assert_equal original_count1, topic1.reload.replies_count assert_equal original_count2, topic2.reload.replies_count end @@ -1842,6 +1842,13 @@ class HasManyAssociationsTest < ActiveRecord::TestCase ].each { |block| assert_raise(ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection, &block) } end + def test_associations_order_should_be_priority_over_throughs_order + david = authors(:david) + expected = [12, 10, 9, 8, 7, 6, 5, 3, 2, 1] + assert_equal expected, david.comments_desc.map(&:id) + assert_equal expected, Author.includes(:comments_desc).find(david.id).comments_desc.map(&:id) + end + def test_dynamic_find_should_respect_association_order_for_through assert_equal Comment.find(10), authors(:david).comments_desc.where("comments.type = 'SpecialComment'").first assert_equal Comment.find(10), authors(:david).comments_desc.find_by_type("SpecialComment") diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb index 09684520d1..b8243d148a 100644 --- a/activerecord/test/cases/autosave_association_test.rb +++ b/activerecord/test/cases/autosave_association_test.rb @@ -50,7 +50,7 @@ class TestAutosaveAssociationsInGeneral < ActiveRecord::TestCase } u = person.create!(first_name: "cool") - u.update_attributes!(first_name: "nah") # still valid because validation only applies on 'create' + u.update!(first_name: "nah") # still valid because validation only applies on 'create' assert_predicate reference.create!(person: u), :persisted? end diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index ad701fb200..c8163901c6 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -508,7 +508,7 @@ class EachTest < ActiveRecord::TestCase def test_in_batches_relations_update_all_should_not_affect_matching_records_in_other_batches Post.update_all(author_id: 0) person = Post.last - person.update_attributes(author_id: 1) + person.update(author_id: 1) Post.in_batches(of: 2) do |batch| batch.where("author_id >= 1").update_all("author_id = author_id + 1") diff --git a/activerecord/test/cases/core_test.rb b/activerecord/test/cases/core_test.rb index 356afdbd2b..6e7ae2efb4 100644 --- a/activerecord/test/cases/core_test.rb +++ b/activerecord/test/cases/core_test.rb @@ -4,7 +4,6 @@ require "cases/helper" require "models/person" require "models/topic" require "pp" -require "active_support/core_ext/string/strip" class NonExistentTable < ActiveRecord::Base; end @@ -39,26 +38,26 @@ class CoreTest < ActiveRecord::TestCase topic = Topic.new actual = "".dup PP.pp(topic, StringIO.new(actual)) - expected = <<-PRETTY.strip_heredoc - #<Topic:0xXXXXXX - id: nil, - title: nil, - author_name: nil, - author_email_address: "test@test.com", - written_on: nil, - bonus_time: nil, - last_read: nil, - content: nil, - important: nil, - approved: true, - replies_count: 0, - unique_replies_count: 0, - parent_id: nil, - parent_title: nil, - type: nil, - group: nil, - created_at: nil, - updated_at: nil> + expected = <<~PRETTY + #<Topic:0xXXXXXX + id: nil, + title: nil, + author_name: nil, + author_email_address: "test@test.com", + written_on: nil, + bonus_time: nil, + last_read: nil, + content: nil, + important: nil, + approved: true, + replies_count: 0, + unique_replies_count: 0, + parent_id: nil, + parent_title: nil, + type: nil, + group: nil, + created_at: nil, + updated_at: nil> PRETTY assert actual.start_with?(expected.split("XXXXXX").first) assert actual.end_with?(expected.split("XXXXXX").last) @@ -68,26 +67,26 @@ class CoreTest < ActiveRecord::TestCase topic = topics(:first) actual = "".dup PP.pp(topic, StringIO.new(actual)) - expected = <<-PRETTY.strip_heredoc - #<Topic:0x\\w+ - id: 1, - title: "The First Topic", - author_name: "David", - author_email_address: "david@loudthinking.com", - written_on: 2003-07-16 14:28:11 UTC, - bonus_time: 2000-01-01 14:28:00 UTC, - last_read: Thu, 15 Apr 2004, - content: "Have a nice day", - important: nil, - approved: false, - replies_count: 1, - unique_replies_count: 0, - parent_id: nil, - parent_title: nil, - type: nil, - group: nil, - created_at: [^,]+, - updated_at: [^,>]+> + expected = <<~PRETTY + #<Topic:0x\\w+ + id: 1, + title: "The First Topic", + author_name: "David", + author_email_address: "david@loudthinking.com", + written_on: 2003-07-16 14:28:11 UTC, + bonus_time: 2000-01-01 14:28:00 UTC, + last_read: Thu, 15 Apr 2004, + content: "Have a nice day", + important: nil, + approved: false, + replies_count: 1, + unique_replies_count: 0, + parent_id: nil, + parent_title: nil, + type: nil, + group: nil, + created_at: [^,]+, + updated_at: [^,>]+> PRETTY assert_match(/\A#{expected}\z/, actual) end diff --git a/activerecord/test/cases/explain_test.rb b/activerecord/test/cases/explain_test.rb index 17654027a9..a0e75f4e89 100644 --- a/activerecord/test/cases/explain_test.rb +++ b/activerecord/test/cases/explain_test.rb @@ -2,7 +2,6 @@ require "cases/helper" require "models/car" -require "active_support/core_ext/string/strip" if ActiveRecord::Base.connection.supports_explain? class ExplainTest < ActiveRecord::TestCase @@ -53,7 +52,7 @@ if ActiveRecord::Base.connection.supports_explain? queries = sqls.zip(binds) stub_explain_for_query_plans(["query plan foo\n", "query plan bar\n"]) do - expected = <<-SQL.strip_heredoc + expected = <<~SQL EXPLAIN for: #{sqls[0]} [["wadus", 1]] query plan foo diff --git a/activerecord/test/cases/fixtures_test.rb b/activerecord/test/cases/fixtures_test.rb index c88b90c8eb..c92edc6f45 100644 --- a/activerecord/test/cases/fixtures_test.rb +++ b/activerecord/test/cases/fixtures_test.rb @@ -86,7 +86,7 @@ class FixturesTest < ActiveRecord::TestCase create_fixtures("bulbs", "authors", "computers") - expected_sql = <<-EOS.strip_heredoc.chop + expected_sql = <<~EOS.chop INSERT INTO #{ActiveRecord::Base.connection.quote_table_name("bulbs")} .* INSERT INTO #{ActiveRecord::Base.connection.quote_table_name("authors")} .* INSERT INTO #{ActiveRecord::Base.connection.quote_table_name("computers")} .* diff --git a/activerecord/test/cases/json_shared_test_cases.rb b/activerecord/test/cases/json_shared_test_cases.rb index c60a276850..9b79803503 100644 --- a/activerecord/test/cases/json_shared_test_cases.rb +++ b/activerecord/test/cases/json_shared_test_cases.rb @@ -101,7 +101,7 @@ module JSONSharedTestCases x = klass.where(payload: nil).first assert_nil(x) - json.update_attributes(payload: nil) + json.update(payload: nil) x = klass.where(payload: nil).first assert_equal(json.reload, x) end diff --git a/activerecord/test/cases/migration/rename_table_test.rb b/activerecord/test/cases/migration/rename_table_test.rb index 8514ccd55b..a9deb92585 100644 --- a/activerecord/test/cases/migration/rename_table_test.rb +++ b/activerecord/test/cases/migration/rename_table_test.rb @@ -88,7 +88,7 @@ module ActiveRecord assert connection.table_exists? :felines assert_not connection.table_exists? :cats - primary_key_name = connection.select_values(<<-SQL.strip_heredoc, "SCHEMA")[0] + primary_key_name = connection.select_values(<<~SQL, "SCHEMA")[0] SELECT c.relname FROM pg_class c JOIN pg_index i diff --git a/activerecord/test/cases/persistence_test.rb b/activerecord/test/cases/persistence_test.rb index d4a4cd564a..e4a65a48ca 100644 --- a/activerecord/test/cases/persistence_test.rb +++ b/activerecord/test/cases/persistence_test.rb @@ -525,7 +525,7 @@ class PersistenceTest < ActiveRecord::TestCase def test_update_does_not_run_sql_if_record_has_not_changed topic = Topic.create(title: "Another New Topic") assert_queries(0) { assert topic.update(title: "Another New Topic") } - assert_queries(0) { assert topic.update_attributes(title: "Another New Topic") } + assert_queries(0) { assert topic.update(title: "Another New Topic") } end def test_delete @@ -934,42 +934,33 @@ class PersistenceTest < ActiveRecord::TestCase topic.reload assert_not_predicate topic, :approved? assert_equal "The First Topic", topic.title - end - - def test_update_attributes - topic = Topic.find(1) - assert_not_predicate topic, :approved? - assert_equal "The First Topic", topic.title - - topic.update_attributes("approved" => true, "title" => "The First Topic Updated") - topic.reload - assert_predicate topic, :approved? - assert_equal "The First Topic Updated", topic.title - - topic.update_attributes(approved: false, title: "The First Topic") - topic.reload - assert_not_predicate topic, :approved? - assert_equal "The First Topic", topic.title error = assert_raise(ActiveRecord::RecordNotUnique, ActiveRecord::StatementInvalid) do - topic.update_attributes(id: 3, title: "Hm is it possible?") + topic.update(id: 3, title: "Hm is it possible?") end assert_not_nil error.cause assert_not_equal "Hm is it possible?", Topic.find(3).title - topic.update_attributes(id: 1234) + topic.update(id: 1234) assert_nothing_raised { topic.reload } assert_equal topic.title, Topic.find(1234).title end - def test_update_attributes_parameters + def test_update_attributes + topic = Topic.find(1) + assert_deprecated do + topic.update_attributes("title" => "The First Topic Updated") + end + end + + def test_update_parameters topic = Topic.find(1) assert_nothing_raised do - topic.update_attributes({}) + topic.update({}) end assert_raises(ArgumentError) do - topic.update_attributes(nil) + topic.update(nil) end end @@ -995,24 +986,10 @@ class PersistenceTest < ActiveRecord::TestCase end def test_update_attributes! - Reply.validates_presence_of(:title) reply = Reply.find(2) - assert_equal "The Second Topic of the day", reply.title - assert_equal "Have a nice day", reply.content - - reply.update_attributes!("title" => "The Second Topic of the day updated", "content" => "Have a nice evening") - reply.reload - assert_equal "The Second Topic of the day updated", reply.title - assert_equal "Have a nice evening", reply.content - - reply.update_attributes!(title: "The Second Topic of the day", content: "Have a nice day") - reply.reload - assert_equal "The Second Topic of the day", reply.title - assert_equal "Have a nice day", reply.content - - assert_raise(ActiveRecord::RecordInvalid) { reply.update_attributes!(title: nil, content: "Have a nice evening") } - ensure - Reply.clear_validators! + assert_deprecated do + reply.update_attributes!("title" => "The Second Topic of the day updated") + end end def test_destroyed_returns_boolean diff --git a/activerecord/test/cases/query_cache_test.rb b/activerecord/test/cases/query_cache_test.rb index 8d37f5ea95..d635a47c0e 100644 --- a/activerecord/test/cases/query_cache_test.rb +++ b/activerecord/test/cases/query_cache_test.rb @@ -320,6 +320,17 @@ class QueryCacheTest < ActiveRecord::TestCase end end + def test_cache_is_available_when_connection_is_connected + conf = ActiveRecord::Base.configurations + + ActiveRecord::Base.configurations = {} + Task.cache do + assert_queries(1) { Task.find(1); Task.find(1) } + end + ensure + ActiveRecord::Base.configurations = conf + end + def test_cache_is_available_when_using_a_not_connected_connection skip "In-Memory DB can't test for using a not connected connection" if in_memory_db? with_temporary_connection_pool do @@ -366,7 +377,7 @@ class QueryCacheTest < ActiveRecord::TestCase post = Post.first Post.transaction do - post.update_attributes(title: "rollback") + post.update(title: "rollback") assert_equal 1, Post.where(title: "rollback").to_a.count raise ActiveRecord::Rollback end @@ -379,7 +390,7 @@ class QueryCacheTest < ActiveRecord::TestCase begin Post.transaction do - post.update_attributes(title: "rollback") + post.update(title: "rollback") assert_equal 1, Post.where(title: "rollback").to_a.count raise "broken" end diff --git a/activerecord/test/cases/validations/length_validation_test.rb b/activerecord/test/cases/validations/length_validation_test.rb index 73422a31cd..62cd89041a 100644 --- a/activerecord/test/cases/validations/length_validation_test.rb +++ b/activerecord/test/cases/validations/length_validation_test.rb @@ -56,7 +56,7 @@ class LengthValidationTest < ActiveRecord::TestCase assert owner.save pet_count = Pet.count - assert_not owner.update_attributes pets_attributes: [ { _destroy: 1, id: pet.id } ] + assert_not owner.update pets_attributes: [ { _destroy: 1, id: pet.id } ] assert_not_predicate owner, :valid? assert_predicate owner.errors[:pets], :any? assert_equal pet_count, Pet.count diff --git a/activerecord/test/cases/validations/uniqueness_validation_test.rb b/activerecord/test/cases/validations/uniqueness_validation_test.rb index 1fa94a5b75..941aed5402 100644 --- a/activerecord/test/cases/validations/uniqueness_validation_test.rb +++ b/activerecord/test/cases/validations/uniqueness_validation_test.rb @@ -62,7 +62,7 @@ class TopicWithAfterCreate < Topic after_create :set_author def set_author - update_attributes!(author_name: "#{title} #{id}") + update!(author_name: "#{title} #{id}") end end diff --git a/activerecord/test/fixtures/.gitignore b/activerecord/test/fixtures/.gitignore deleted file mode 100644 index 885029a512..0000000000 --- a/activerecord/test/fixtures/.gitignore +++ /dev/null @@ -1 +0,0 @@ -*.sqlite*
\ No newline at end of file diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index 27da886e1c..bd12cdf7ef 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -8,6 +8,7 @@ class Author < ActiveRecord::Base has_many :posts_with_comments, -> { includes(:comments) }, class_name: "Post" has_many :popular_grouped_posts, -> { includes(:comments).group("type").having("SUM(comments_count) > 1").select("type") }, class_name: "Post" has_many :posts_with_comments_sorted_by_comment_id, -> { includes(:comments).order("comments.id") }, class_name: "Post" + has_many :posts_sorted_by_id, -> { order(:id) }, class_name: "Post" has_many :posts_sorted_by_id_limited, -> { order("posts.id").limit(1) }, class_name: "Post" has_many :posts_with_categories, -> { includes(:categories) }, class_name: "Post" has_many :posts_with_comments_and_categories, -> { includes(:comments, :categories).order("posts.id") }, class_name: "Post" @@ -40,7 +41,7 @@ class Author < ActiveRecord::Base -> { where(title: "Welcome to the weblog").where(Post.arel_table[:comments_count].gt(0)) }, class_name: "Post" - has_many :comments_desc, -> { order("comments.id DESC") }, through: :posts, source: :comments + has_many :comments_desc, -> { order("comments.id DESC") }, through: :posts_sorted_by_id, source: :comments has_many :unordered_comments, -> { unscope(:order).distinct }, through: :posts_sorted_by_id_limited, source: :comments has_many :funky_comments, through: :posts, source: :comments has_many :ordered_uniq_comments, -> { distinct.order("comments.id") }, through: :posts, source: :comments diff --git a/activerecord/test/models/comment.rb b/activerecord/test/models/comment.rb index 5ab433f2d9..f0f0576709 100644 --- a/activerecord/test/models/comment.rb +++ b/activerecord/test/models/comment.rb @@ -76,7 +76,7 @@ class CommentThatAutomaticallyAltersPostBody < Comment belongs_to :post, class_name: "PostThatLoadsCommentsInAnAfterSaveHook", foreign_key: :post_id after_save do |comment| - comment.post.update_attributes(body: "Automatically altered") + comment.post.update(body: "Automatically altered") end end @@ -87,6 +87,6 @@ end class CommentWithAfterCreateUpdate < Comment after_create do - update_attributes(body: "bar") + update(body: "bar") end end diff --git a/activestorage/.gitignore b/activestorage/.gitignore index 29bdcc4468..3e78878ffc 100644 --- a/activestorage/.gitignore +++ b/activestorage/.gitignore @@ -1,7 +1,6 @@ -.byebug_history -node_modules -src -test/dummy/db/*.sqlite3 -test/dummy/db/*.sqlite3-journal -test/dummy/log/*.log -test/dummy/tmp/ +/src/ +/test/dummy/db/*.sqlite3 +/test/dummy/db/*.sqlite3-journal +/test/dummy/log/*.log +/test/dummy/tmp/ +/test/service/configurations.yml diff --git a/activestorage/CHANGELOG.md b/activestorage/CHANGELOG.md index 682e35e0a8..cc21c1f75a 100644 --- a/activestorage/CHANGELOG.md +++ b/activestorage/CHANGELOG.md @@ -1,3 +1,9 @@ +## Rails 6.0.0.alpha (Unreleased) ## + +* Rails 6 requires Ruby 2.4.1 or newer. + + *Jeremy Daer* + * Add source code to published npm package This allows activestorage users to depend on the javascript source code @@ -5,4 +11,5 @@ *Richard Macklin* + 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 d135324700..df7801a671 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.2.2" + s.required_ruby_version = ">= 2.4.1" s.license = "MIT" diff --git a/activestorage/app/models/active_storage/blob/identifiable.rb b/activestorage/app/models/active_storage/blob/identifiable.rb index 40ca84ac70..dbe03cfa6c 100644 --- a/activestorage/app/models/active_storage/blob/identifiable.rb +++ b/activestorage/app/models/active_storage/blob/identifiable.rb @@ -2,10 +2,15 @@ module ActiveStorage::Blob::Identifiable def identify - ActiveStorage::Identification.new(self).apply + update!(content_type: identification.content_type, identified: true) unless identified? end def identified? identified end + + private + def identification + ActiveStorage::Identification.new self + end end diff --git a/activestorage/app/models/active_storage/identification.rb b/activestorage/app/models/active_storage/identification.rb index bf1d8f5624..8d334ae1ea 100644 --- a/activestorage/app/models/active_storage/identification.rb +++ b/activestorage/app/models/active_storage/identification.rb @@ -2,26 +2,21 @@ require "net/http" -class ActiveStorage::Identification +class ActiveStorage::Identification #:nodoc: attr_reader :blob def initialize(blob) @blob = blob end - def apply - blob.update!(content_type: content_type, identified: true) unless blob.identified? + def content_type + Marcel::MimeType.for(identifiable_chunk, name: filename, declared_type: declared_content_type) end private - def content_type - Marcel::MimeType.for(identifiable_chunk, name: filename, declared_type: declared_content_type) - end - - def identifiable_chunk Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https") do |client| - client.get(uri, "Range" => "0-4096").body + client.get(uri, "Range" => "bytes=0-4095").body end end diff --git a/activestorage/test/controllers/direct_uploads_controller_test.rb b/activestorage/test/controllers/direct_uploads_controller_test.rb index 888767086c..dfffb6bb9c 100644 --- a/activestorage/test/controllers/direct_uploads_controller_test.rb +++ b/activestorage/test/controllers/direct_uploads_controller_test.rb @@ -27,7 +27,7 @@ if SERVICE_CONFIGURATIONS[:s3] && SERVICE_CONFIGURATIONS[:s3][:access_key_id].pr assert_equal checksum, details["checksum"] assert_equal "text/plain", details["content_type"] assert_match SERVICE_CONFIGURATIONS[:s3][:bucket], details["direct_upload"]["url"] - assert_match(/s3\.(\S+)?amazonaws\.com/, details["direct_upload"]["url"]) + assert_match(/s3(-[-a-z0-9]+)?\.(\S+)?amazonaws\.com/, details["direct_upload"]["url"]) assert_equal({ "Content-Type" => "text/plain", "Content-MD5" => checksum }, details["direct_upload"]["headers"]) end end diff --git a/activestorage/test/service/s3_service_test.rb b/activestorage/test/service/s3_service_test.rb index c3818422aa..d6996209d2 100644 --- a/activestorage/test/service/s3_service_test.rb +++ b/activestorage/test/service/s3_service_test.rb @@ -35,7 +35,7 @@ if SERVICE_CONFIGURATIONS[:s3] && SERVICE_CONFIGURATIONS[:s3][:access_key_id].pr url = @service.url(FIXTURE_KEY, expires_in: 5.minutes, disposition: :inline, filename: ActiveStorage::Filename.new("avatar.png"), content_type: "image/png") - assert_match(/s3\.(\S+)?amazonaws.com.*response-content-disposition=inline.*avatar\.png.*response-content-type=image%2Fpng/, url) + assert_match(/s3(-[-a-z0-9]+)?\.(\S+)?amazonaws.com.*response-content-disposition=inline.*avatar\.png.*response-content-type=image%2Fpng/, url) assert_match SERVICE_CONFIGURATIONS[:s3][:bucket], url end diff --git a/activesupport/.gitignore b/activesupport/.gitignore new file mode 100644 index 0000000000..8cde8514fd --- /dev/null +++ b/activesupport/.gitignore @@ -0,0 +1 @@ +/test/fixtures/isolation_test/ diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md index fc47c76e99..73896be453 100644 --- a/activesupport/CHANGELOG.md +++ b/activesupport/CHANGELOG.md @@ -1,2 +1,51 @@ +## Rails 6.0.0.alpha (Unreleased) ## + +* Return all mappings for a timezone identifier in `country_zones` + + Some timezones like `Europe/London` have multiple mappings in + `ActiveSupport::TimeZone::MAPPING` so return all of them instead + of the first one found by using `Hash#value`. e.g: + + # Before + ActiveSupport::TimeZone.country_zones("GB") # => ["Edinburgh"] + + # After + ActiveSupport::TimeZone.country_zones("GB") # => ["Edinburgh", "London"] + + Fixes #31668. + + *Andrew White* + +* `String#truncate_bytes` to truncate a string to a maximum bytesize without + breaking multibyte characters or grapheme clusters like 👩👩👦👦. + + *Jeremy Daer* + +* `String#strip_heredoc` preserves frozenness. + + "foo".freeze.strip_heredoc.frozen? # => true + + Fixes that frozen string literals would inadvertently become unfrozen: + + # frozen_string_literal: true + + foo = <<-MSG.strip_heredoc + la la la + MSG + + foo.frozen? # => false !?? + + *Jeremy Daer* + +* Rails 6 requires Ruby 2.4.1 or newer. + + *Jeremy Daer* + +* Adds parallel testing to Rails + + Parallelize your test suite with forked processes or threads. + + *Eileen M. Uchitelle*, *Aaron Patterson* + Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/activesupport/CHANGELOG.md) for previous changes. diff --git a/activesupport/activesupport.gemspec b/activesupport/activesupport.gemspec index 5d4e4e2d3b..aa695c98b2 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.2.2" + s.required_ruby_version = ">= 2.4.1" s.license = "MIT" diff --git a/activesupport/bin/generate_tables b/activesupport/bin/generate_tables deleted file mode 100755 index 18199b2171..0000000000 --- a/activesupport/bin/generate_tables +++ /dev/null @@ -1,141 +0,0 @@ -#!/usr/bin/env ruby -# frozen_string_literal: true - -begin - $:.unshift(File.expand_path("../lib", __dir__)) - require "active_support" -rescue IOError -end - -require "open-uri" -require "tmpdir" -require "fileutils" - -module ActiveSupport - module Multibyte - module Unicode - class UnicodeDatabase - def load; end - end - - class DatabaseGenerator - BASE_URI = "http://www.unicode.org/Public/#{UNICODE_VERSION}/ucd/" - SOURCES = { - codepoints: BASE_URI + "UnicodeData.txt", - composition_exclusion: BASE_URI + "CompositionExclusions.txt", - grapheme_break_property: BASE_URI + "auxiliary/GraphemeBreakProperty.txt", - cp1252: "http://unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1252.TXT" - } - - def initialize - @ucd = Unicode::UnicodeDatabase.new - end - - def parse_codepoints(line) - codepoint = Codepoint.new - raise "Could not parse input." unless line =~ /^ - ([0-9A-F]+); # code - ([^;]+); # name - ([A-Z]+); # general category - ([0-9]+); # canonical combining class - ([A-Z]+); # bidi class - (<([A-Z]*)>)? # decomposition type - ((\ ?[0-9A-F]+)*); # decomposition mapping - ([0-9]*); # decimal digit - ([0-9]*); # digit - ([^;]*); # numeric - ([YN]*); # bidi mirrored - ([^;]*); # unicode 1.0 name - ([^;]*); # iso comment - ([0-9A-F]*); # simple uppercase mapping - ([0-9A-F]*); # simple lowercase mapping - ([0-9A-F]*)$/ix # simple titlecase mapping - codepoint.code = $1.hex - codepoint.combining_class = Integer($4) - codepoint.decomp_type = $7 - codepoint.decomp_mapping = ($8 == "") ? nil : $8.split.collect(&:hex) - codepoint.uppercase_mapping = ($16 == "") ? 0 : $16.hex - codepoint.lowercase_mapping = ($17 == "") ? 0 : $17.hex - @ucd.codepoints[codepoint.code] = codepoint - end - - def parse_grapheme_break_property(line) - if line =~ /^([0-9A-F.]+)\s*;\s*([\w]+)\s*#/ - type = $2.downcase.intern - @ucd.boundary[type] ||= [] - if $1.include? ".." - parts = $1.split ".." - @ucd.boundary[type] << (parts[0].hex..parts[1].hex) - else - @ucd.boundary[type] << $1.hex - end - end - end - - def parse_composition_exclusion(line) - if line =~ /^([0-9A-F]+)/i - @ucd.composition_exclusion << $1.hex - end - end - - def parse_cp1252(line) - if line =~ /^([0-9A-Fx]+)\s([0-9A-Fx]+)/i - @ucd.cp1252[$1.hex] = $2.hex - end - end - - def create_composition_map - @ucd.codepoints.each do |_, cp| - if !cp.nil? && cp.combining_class == 0 && cp.decomp_type.nil? && !cp.decomp_mapping.nil? && cp.decomp_mapping.length == 2 && @ucd.codepoints[cp.decomp_mapping[0]].combining_class == 0 && !@ucd.composition_exclusion.include?(cp.code) - @ucd.composition_map[cp.decomp_mapping[0]] ||= {} - @ucd.composition_map[cp.decomp_mapping[0]][cp.decomp_mapping[1]] = cp.code - end - end - end - - def normalize_boundary_map - @ucd.boundary.each do |k, v| - if [:lf, :cr].include? k - @ucd.boundary[k] = v[0] - end - end - end - - def parse - SOURCES.each do |type, url| - filename = File.join(Dir.tmpdir, UNICODE_VERSION, "#{url.split('/').last}") - unless File.exist?(filename) - $stderr.puts "Downloading #{url.split('/').last}" - FileUtils.mkdir_p(File.dirname(filename)) - File.open(filename, "wb") do |target| - open(url) do |source| - source.each_line { |line| target.write line } - end - end - end - File.open(filename) do |file| - file.each_line { |line| send "parse_#{type}".intern, line } - end - end - create_composition_map - normalize_boundary_map - end - - def dump_to(filename) - File.open(filename, "wb") do |f| - f.write Marshal.dump([@ucd.codepoints, @ucd.composition_exclusion, @ucd.composition_map, @ucd.boundary, @ucd.cp1252]) - end - end - end - end - end -end - -if __FILE__ == $0 - filename = ActiveSupport::Multibyte::Unicode::UnicodeDatabase.filename - generator = ActiveSupport::Multibyte::Unicode::DatabaseGenerator.new - generator.parse - print "Writing to: #{filename}" - generator.dump_to filename - puts " (#{File.size(filename)} bytes)" -end diff --git a/activesupport/lib/active_support/cache/redis_cache_store.rb b/activesupport/lib/active_support/cache/redis_cache_store.rb index c5f8c876ba..64bc77cf22 100644 --- a/activesupport/lib/active_support/cache/redis_cache_store.rb +++ b/activesupport/lib/active_support/cache/redis_cache_store.rb @@ -17,7 +17,6 @@ end require "digest/sha2" require "active_support/core_ext/marshal" -require "active_support/core_ext/hash/transform_values" module ActiveSupport module Cache diff --git a/activesupport/lib/active_support/core_ext/hash.rb b/activesupport/lib/active_support/core_ext/hash.rb index e19aeaa983..325581a60a 100644 --- a/activesupport/lib/active_support/core_ext/hash.rb +++ b/activesupport/lib/active_support/core_ext/hash.rb @@ -8,4 +8,3 @@ require "active_support/core_ext/hash/indifferent_access" require "active_support/core_ext/hash/keys" require "active_support/core_ext/hash/reverse_merge" require "active_support/core_ext/hash/slice" -require "active_support/core_ext/hash/transform_values" 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 4b19c9fc1f..fc15130c9e 100644 --- a/activesupport/lib/active_support/core_ext/hash/transform_values.rb +++ b/activesupport/lib/active_support/core_ext/hash/transform_values.rb @@ -1,32 +1,5 @@ # frozen_string_literal: true -class Hash - # Returns a new hash with the results of running +block+ once for every value. - # The keys are unchanged. - # - # { a: 1, b: 2, c: 3 }.transform_values { |x| x * 2 } # => { a: 2, b: 4, c: 6 } - # - # If you do not provide a +block+, it will return an Enumerator - # for chaining with other methods: - # - # { a: 1, b: 2 }.transform_values.with_index { |v, i| [v, i].join.to_i } # => { a: 10, b: 21 } - def transform_values - return enum_for(:transform_values) { size } unless block_given? - return {} if empty? - result = self.class.new - each do |key, value| - result[key] = yield(value) - end - result - end unless method_defined? :transform_values +require "active_support/deprecation" - # Destructively converts all values using the +block+ operations. - # Same as +transform_values+ but modifies +self+. - def transform_values! - return enum_for(:transform_values!) { size } unless block_given? - each do |key, value| - self[key] = yield(value) - end - end unless method_defined? :transform_values! - # TODO: Remove this file when supporting only Ruby 2.4+. -end +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." diff --git a/activesupport/lib/active_support/core_ext/module/redefine_method.rb b/activesupport/lib/active_support/core_ext/module/redefine_method.rb index a0a6622ca4..5bd8e6e973 100644 --- a/activesupport/lib/active_support/core_ext/module/redefine_method.rb +++ b/activesupport/lib/active_support/core_ext/module/redefine_method.rb @@ -1,23 +1,14 @@ # frozen_string_literal: true class Module - if RUBY_VERSION >= "2.3" - # Marks the named method as intended to be redefined, if it exists. - # Suppresses the Ruby method redefinition warning. Prefer - # #redefine_method where possible. - def silence_redefinition_of_method(method) - if method_defined?(method) || private_method_defined?(method) - # This suppresses the "method redefined" warning; the self-alias - # looks odd, but means we don't need to generate a unique name - alias_method method, method - end - end - else - def silence_redefinition_of_method(method) - if method_defined?(method) || private_method_defined?(method) - alias_method :__rails_redefine, method - remove_method :__rails_redefine - end + # Marks the named method as intended to be redefined, if it exists. + # Suppresses the Ruby method redefinition warning. Prefer + # #redefine_method where possible. + def silence_redefinition_of_method(method) + if method_defined?(method) || private_method_defined?(method) + # This suppresses the "method redefined" warning; the self-alias + # looks odd, but means we don't need to generate a unique name + alias_method method, method end end diff --git a/activesupport/lib/active_support/core_ext/numeric/conversions.rb b/activesupport/lib/active_support/core_ext/numeric/conversions.rb index f6c2713986..7fcd0d0311 100644 --- a/activesupport/lib/active_support/core_ext/numeric/conversions.rb +++ b/activesupport/lib/active_support/core_ext/numeric/conversions.rb @@ -129,12 +129,6 @@ module ActiveSupport::NumericWithFormat end end -# Ruby 2.4+ unifies Fixnum & Bignum into Integer. -if 0.class == Integer - Integer.prepend ActiveSupport::NumericWithFormat -else - Fixnum.prepend ActiveSupport::NumericWithFormat - Bignum.prepend ActiveSupport::NumericWithFormat -end +Integer.prepend ActiveSupport::NumericWithFormat Float.prepend ActiveSupport::NumericWithFormat BigDecimal.prepend ActiveSupport::NumericWithFormat diff --git a/activesupport/lib/active_support/core_ext/object/duplicable.rb b/activesupport/lib/active_support/core_ext/object/duplicable.rb index 9bb99087bc..c78ee6bbfc 100644 --- a/activesupport/lib/active_support/core_ext/object/duplicable.rb +++ b/activesupport/lib/active_support/core_ext/object/duplicable.rb @@ -75,8 +75,11 @@ end class Symbol begin - :symbol.dup # Ruby 2.4.x. - "symbol_from_string".to_sym.dup # Some symbols can't `dup` in Ruby 2.4.0. + :symbol.dup + + # Some symbols couldn't be duped in Ruby 2.4.0 only, due to a bug. + # This feature check catches any regression. + "symbol_from_string".to_sym.dup rescue TypeError # Symbols are not duplicable: diff --git a/activesupport/lib/active_support/core_ext/regexp.rb b/activesupport/lib/active_support/core_ext/regexp.rb index efbd708aee..d92943c7ae 100644 --- a/activesupport/lib/active_support/core_ext/regexp.rb +++ b/activesupport/lib/active_support/core_ext/regexp.rb @@ -4,8 +4,4 @@ class Regexp #:nodoc: def multiline? options & MULTILINE == MULTILINE end - - def match?(string, pos = 0) - !!match(string, pos) - end unless //.respond_to?(:match?) end diff --git a/activesupport/lib/active_support/core_ext/string/filters.rb b/activesupport/lib/active_support/core_ext/string/filters.rb index 66e721eea3..df0e79afa8 100644 --- a/activesupport/lib/active_support/core_ext/string/filters.rb +++ b/activesupport/lib/active_support/core_ext/string/filters.rb @@ -78,6 +78,47 @@ class String "#{self[0, stop]}#{omission}" end + # Truncates +text+ to at most <tt>bytesize</tt> bytes in length without + # breaking string encoding by splitting multibyte characters or breaking + # grapheme clusters ("perceptual characters") by truncating at combining + # characters. + # + # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".size + # => 20 + # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".bytesize + # => 80 + # >> "🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪🔪".truncate_bytes(20) + # => "🔪🔪🔪🔪…" + # + # The truncated text ends with the <tt>:omission</tt> string, defaulting + # to "…", for a total length not exceeding <tt>bytesize</tt>. + def truncate_bytes(truncate_at, omission: "…") + omission ||= "" + + case + when bytesize <= truncate_at + dup + when omission.bytesize > truncate_at + raise ArgumentError, "Omission #{omission.inspect} is #{omission.bytesize}, larger than the truncation length of #{truncate_at} bytes" + when omission.bytesize == truncate_at + omission.dup + else + self.class.new.tap do |cut| + cut_at = truncate_at - omission.bytesize + + scan(/\X/) do |grapheme| + if cut.bytesize + grapheme.bytesize <= cut_at + cut << grapheme + else + break + end + end + + cut << omission + end + end + end + # Truncates a given +text+ after a given number of words (<tt>words_count</tt>): # # 'Once upon a time in a world far far away'.truncate_words(4) diff --git a/activesupport/lib/active_support/core_ext/string/multibyte.rb b/activesupport/lib/active_support/core_ext/string/multibyte.rb index 07c0d16398..6cceb46507 100644 --- a/activesupport/lib/active_support/core_ext/string/multibyte.rb +++ b/activesupport/lib/active_support/core_ext/string/multibyte.rb @@ -11,12 +11,13 @@ class String # encapsulates the original string. A Unicode safe version of all the String methods are defined on this proxy # class. If the proxy class doesn't respond to a certain method, it's forwarded to the encapsulated string. # - # >> "lj".upcase - # => "lj" # >> "lj".mb_chars.upcase.to_s # => "LJ" # - # NOTE: An above example is useful for pre Ruby 2.4. Ruby 2.4 supports Unicode case mappings. + # NOTE: Ruby 2.4 and later support native Unicode case mappings: + # + # >> "lj".upcase + # => "LJ" # # == Method chaining # diff --git a/activesupport/lib/active_support/core_ext/string/strip.rb b/activesupport/lib/active_support/core_ext/string/strip.rb index cc26274e4a..6f9834bb16 100644 --- a/activesupport/lib/active_support/core_ext/string/strip.rb +++ b/activesupport/lib/active_support/core_ext/string/strip.rb @@ -20,6 +20,8 @@ class String # Technically, it looks for the least indented non-empty line # in the whole string, and removes that amount of leading whitespace. def strip_heredoc - gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, "".freeze) + gsub(/^#{scan(/^[ \t]*(?=\S)/).min}/, "".freeze).tap do |stripped| + stripped.freeze if frozen? + end end end diff --git a/activesupport/lib/active_support/duration/iso8601_serializer.rb b/activesupport/lib/active_support/duration/iso8601_serializer.rb index bb177ae5b7..84ae29c1ec 100644 --- a/activesupport/lib/active_support/duration/iso8601_serializer.rb +++ b/activesupport/lib/active_support/duration/iso8601_serializer.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "active_support/core_ext/object/blank" -require "active_support/core_ext/hash/transform_values" module ActiveSupport class Duration diff --git a/activesupport/lib/active_support/hash_with_indifferent_access.rb b/activesupport/lib/active_support/hash_with_indifferent_access.rb index 2e2ed8a25d..e4afc8af93 100644 --- a/activesupport/lib/active_support/hash_with_indifferent_access.rb +++ b/activesupport/lib/active_support/hash_with_indifferent_access.rb @@ -177,20 +177,18 @@ module ActiveSupport super(convert_key(key), *extras) end - if Hash.new.respond_to?(:dig) - # Same as <tt>Hash#dig</tt> where the key passed as argument can be - # either a string or a symbol: - # - # counters = ActiveSupport::HashWithIndifferentAccess.new - # counters[:foo] = { bar: 1 } - # - # counters.dig('foo', 'bar') # => 1 - # counters.dig(:foo, :bar) # => 1 - # counters.dig(:zoo) # => nil - def dig(*args) - args[0] = convert_key(args[0]) if args.size > 0 - super(*args) - end + # Same as <tt>Hash#dig</tt> where the key passed as argument can be + # either a string or a symbol: + # + # counters = ActiveSupport::HashWithIndifferentAccess.new + # counters[:foo] = { bar: 1 } + # + # counters.dig('foo', 'bar') # => 1 + # counters.dig(:foo, :bar) # => 1 + # counters.dig(:zoo) # => nil + def dig(*args) + args[0] = convert_key(args[0]) if args.size > 0 + super(*args) end # Same as <tt>Hash#default</tt> where the key passed as argument can be @@ -228,7 +226,7 @@ module ActiveSupport # hash.fetch_values('a', 'c') # => KeyError: key not found: "c" def fetch_values(*indices, &block) indices.collect { |key| fetch(key, &block) } - end if Hash.method_defined?(:fetch_values) + end # Returns a shallow copy of the hash. # @@ -311,6 +309,14 @@ module ActiveSupport dup.tap { |hash| hash.transform_keys!(*args, &block) } end + def transform_keys! + return enum_for(:transform_keys!) { size } unless block_given? + keys.each do |key| + self[yield(key)] = delete(key) + end + self + end + def slice(*keys) keys.map! { |key| convert_key(key) } self.class.new(super) diff --git a/activesupport/lib/active_support/multibyte/unicode.rb b/activesupport/lib/active_support/multibyte/unicode.rb index f923061fae..4f0e1165ef 100644 --- a/activesupport/lib/active_support/multibyte/unicode.rb +++ b/activesupport/lib/active_support/multibyte/unicode.rb @@ -11,7 +11,7 @@ module ActiveSupport NORMALIZATION_FORMS = [:c, :kc, :d, :kd] # The Unicode version that is supported by the implementation - UNICODE_VERSION = "9.0.0" + UNICODE_VERSION = RbConfig::CONFIG["UNICODE_VERSION"] # The default normalization used for operations that require # normalization. It can be set to any of the normalizations @@ -21,96 +21,13 @@ module ActiveSupport attr_accessor :default_normalization_form @default_normalization_form = :kc - # Hangul character boundaries and properties - HANGUL_SBASE = 0xAC00 - HANGUL_LBASE = 0x1100 - HANGUL_VBASE = 0x1161 - HANGUL_TBASE = 0x11A7 - HANGUL_LCOUNT = 19 - HANGUL_VCOUNT = 21 - HANGUL_TCOUNT = 28 - HANGUL_NCOUNT = HANGUL_VCOUNT * HANGUL_TCOUNT - HANGUL_SCOUNT = 11172 - HANGUL_SLAST = HANGUL_SBASE + HANGUL_SCOUNT - - # Detect whether the codepoint is in a certain character class. Returns - # +true+ when it's in the specified character class and +false+ otherwise. - # Valid character classes are: <tt>:cr</tt>, <tt>:lf</tt>, <tt>:l</tt>, - # <tt>:v</tt>, <tt>:lv</tt>, <tt>:lvt</tt> and <tt>:t</tt>. - # - # Primarily used by the grapheme cluster support. - def in_char_class?(codepoint, classes) - classes.detect { |c| database.boundary[c] === codepoint } ? true : false - end - # Unpack the string at grapheme boundaries. Returns a list of character # lists. # # Unicode.unpack_graphemes('क्षि') # => [[2325, 2381], [2359], [2367]] # Unicode.unpack_graphemes('Café') # => [[67], [97], [102], [233]] def unpack_graphemes(string) - codepoints = string.codepoints.to_a - unpacked = [] - pos = 0 - marker = 0 - eoc = codepoints.length - while (pos < eoc) - pos += 1 - previous = codepoints[pos - 1] - current = codepoints[pos] - - # See http://unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules - should_break = - if pos == eoc - true - # GB3. CR X LF - elsif previous == database.boundary[:cr] && current == database.boundary[:lf] - false - # GB4. (Control|CR|LF) ÷ - elsif previous && in_char_class?(previous, [:control, :cr, :lf]) - true - # GB5. ÷ (Control|CR|LF) - elsif in_char_class?(current, [:control, :cr, :lf]) - true - # GB6. L X (L|V|LV|LVT) - elsif database.boundary[:l] === previous && in_char_class?(current, [:l, :v, :lv, :lvt]) - false - # GB7. (LV|V) X (V|T) - elsif in_char_class?(previous, [:lv, :v]) && in_char_class?(current, [:v, :t]) - false - # GB8. (LVT|T) X (T) - elsif in_char_class?(previous, [:lvt, :t]) && database.boundary[:t] === current - false - # GB9. X (Extend | ZWJ) - elsif in_char_class?(current, [:extend, :zwj]) - false - # GB9a. X SpacingMark - elsif database.boundary[:spacingmark] === current - false - # GB9b. Prepend X - elsif database.boundary[:prepend] === previous - false - # GB10. (E_Base | EBG) Extend* X E_Modifier - elsif (marker...pos).any? { |i| in_char_class?(codepoints[i], [:e_base, :e_base_gaz]) && codepoints[i + 1...pos].all? { |c| database.boundary[:extend] === c } } && database.boundary[:e_modifier] === current - false - # GB11. ZWJ X (Glue_After_Zwj | EBG) - elsif database.boundary[:zwj] === previous && in_char_class?(current, [:glue_after_zwj, :e_base_gaz]) - false - # GB12. ^ (RI RI)* RI X RI - # GB13. [^RI] (RI RI)* RI X RI - elsif codepoints[marker..pos].all? { |c| database.boundary[:regional_indicator] === c } && codepoints[marker..pos].count { |c| database.boundary[:regional_indicator] === c }.even? - false - # GB999. Any ÷ Any - else - true - end - - if should_break - unpacked << codepoints[marker..pos - 1] - marker = pos - end - end - unpacked + string.scan(/\X/).map(&:codepoints) end # Reverse operation of unpack_graphemes. @@ -120,100 +37,18 @@ module ActiveSupport unpacked.flatten.pack("U*") end - # Re-order codepoints so the string becomes canonical. - def reorder_characters(codepoints) - length = codepoints.length - 1 - pos = 0 - while pos < length do - cp1, cp2 = database.codepoints[codepoints[pos]], database.codepoints[codepoints[pos + 1]] - if (cp1.combining_class > cp2.combining_class) && (cp2.combining_class > 0) - codepoints[pos..pos + 1] = cp2.code, cp1.code - pos += (pos > 0 ? -1 : 1) - else - pos += 1 - end - end - codepoints - end - # Decompose composed characters to the decomposed form. def decompose(type, codepoints) - codepoints.inject([]) do |decomposed, cp| - # if it's a hangul syllable starter character - if HANGUL_SBASE <= cp && cp < HANGUL_SLAST - sindex = cp - HANGUL_SBASE - ncp = [] # new codepoints - ncp << HANGUL_LBASE + sindex / HANGUL_NCOUNT - ncp << HANGUL_VBASE + (sindex % HANGUL_NCOUNT) / HANGUL_TCOUNT - tindex = sindex % HANGUL_TCOUNT - ncp << (HANGUL_TBASE + tindex) unless tindex == 0 - decomposed.concat ncp - # if the codepoint is decomposable in with the current decomposition type - elsif (ncp = database.codepoints[cp].decomp_mapping) && (!database.codepoints[cp].decomp_type || type == :compatibility) - decomposed.concat decompose(type, ncp.dup) - else - decomposed << cp - end + if type == :compatibility + codepoints.pack("U*").unicode_normalize(:nfkd).codepoints + else + codepoints.pack("U*").unicode_normalize(:nfd).codepoints end end # Compose decomposed characters to the composed form. def compose(codepoints) - pos = 0 - eoa = codepoints.length - 1 - starter_pos = 0 - starter_char = codepoints[0] - previous_combining_class = -1 - while pos < eoa - pos += 1 - lindex = starter_char - HANGUL_LBASE - # -- Hangul - if 0 <= lindex && lindex < HANGUL_LCOUNT - vindex = codepoints[starter_pos + 1] - HANGUL_VBASE rescue vindex = -1 - if 0 <= vindex && vindex < HANGUL_VCOUNT - tindex = codepoints[starter_pos + 2] - HANGUL_TBASE rescue tindex = -1 - if 0 <= tindex && tindex < HANGUL_TCOUNT - j = starter_pos + 2 - eoa -= 2 - else - tindex = 0 - j = starter_pos + 1 - eoa -= 1 - end - codepoints[starter_pos..j] = (lindex * HANGUL_VCOUNT + vindex) * HANGUL_TCOUNT + tindex + HANGUL_SBASE - end - starter_pos += 1 - starter_char = codepoints[starter_pos] - # -- Other characters - else - current_char = codepoints[pos] - current = database.codepoints[current_char] - if current.combining_class > previous_combining_class - if ref = database.composition_map[starter_char] - composition = ref[current_char] - else - composition = nil - end - unless composition.nil? - codepoints[starter_pos] = composition - starter_char = composition - codepoints.delete_at pos - eoa -= 1 - pos -= 1 - previous_combining_class = -1 - else - previous_combining_class = current.combining_class - end - else - previous_combining_class = current.combining_class - end - if current.combining_class == 0 - starter_pos = pos - starter_char = codepoints[pos] - end - end - end - codepoints + codepoints.pack("U*").unicode_normalize(:nfc).codepoints end # Rubinius' String#scrub, however, doesn't support ASCII-incompatible chars. @@ -266,129 +101,37 @@ module ActiveSupport def normalize(string, form = nil) form ||= @default_normalization_form # See http://www.unicode.org/reports/tr15, Table 1 - codepoints = string.codepoints.to_a case form when :d - reorder_characters(decompose(:canonical, codepoints)) + string.unicode_normalize(:nfd) when :c - compose(reorder_characters(decompose(:canonical, codepoints))) + string.unicode_normalize(:nfc) when :kd - reorder_characters(decompose(:compatibility, codepoints)) + string.unicode_normalize(:nfkd) when :kc - compose(reorder_characters(decompose(:compatibility, codepoints))) + string.unicode_normalize(:nfkc) else raise ArgumentError, "#{form} is not a valid normalization variant", caller - end.pack("U*".freeze) + end end def downcase(string) - apply_mapping string, :lowercase_mapping + string.downcase end def upcase(string) - apply_mapping string, :uppercase_mapping + string.upcase end def swapcase(string) - apply_mapping string, :swapcase_mapping - end - - # Holds data about a codepoint in the Unicode database. - class Codepoint - attr_accessor :code, :combining_class, :decomp_type, :decomp_mapping, :uppercase_mapping, :lowercase_mapping - - # Initializing Codepoint object with default values - def initialize - @combining_class = 0 - @uppercase_mapping = 0 - @lowercase_mapping = 0 - end - - def swapcase_mapping - uppercase_mapping > 0 ? uppercase_mapping : lowercase_mapping - end - end - - # Holds static data from the Unicode database. - class UnicodeDatabase - ATTRIBUTES = :codepoints, :composition_exclusion, :composition_map, :boundary, :cp1252 - - attr_writer(*ATTRIBUTES) - - def initialize - @codepoints = Hash.new(Codepoint.new) - @composition_exclusion = [] - @composition_map = {} - @boundary = {} - @cp1252 = {} - end - - # Lazy load the Unicode database so it's only loaded when it's actually used - ATTRIBUTES.each do |attr_name| - class_eval(<<-EOS, __FILE__, __LINE__ + 1) - def #{attr_name} # def codepoints - load # load - @#{attr_name} # @codepoints - end # end - EOS - end - - # Loads the Unicode database and returns all the internal objects of - # UnicodeDatabase. - def load - begin - @codepoints, @composition_exclusion, @composition_map, @boundary, @cp1252 = File.open(self.class.filename, "rb") { |f| Marshal.load f.read } - rescue => e - raise IOError.new("Couldn't load the Unicode tables for UTF8Handler (#{e.message}), ActiveSupport::Multibyte is unusable") - end - - # Redefine the === method so we can write shorter rules for grapheme cluster breaks - @boundary.each_key do |k| - @boundary[k].instance_eval do - def ===(other) - detect { |i| i === other } ? true : false - end - end if @boundary[k].kind_of?(Array) - end - - # define attr_reader methods for the instance variables - class << self - attr_reader(*ATTRIBUTES) - end - end - - # Returns the directory in which the data files are stored. - def self.dirname - File.expand_path("../values", __dir__) - end - - # Returns the filename for the data file for this version. - def self.filename - File.expand_path File.join(dirname, "unicode_tables.dat") - end + string.swapcase end private - def apply_mapping(string, mapping) - database.codepoints - string.each_codepoint.map do |codepoint| - cp = database.codepoints[codepoint] - if cp && (ncp = cp.send(mapping)) && ncp > 0 - ncp - else - codepoint - end - end.pack("U*") - end - def recode_windows1252_chars(string) string.encode(Encoding::UTF_8, Encoding::Windows_1252, invalid: :replace, undef: :replace) end - - def database - @database ||= UnicodeDatabase.new - end end end end diff --git a/activesupport/lib/active_support/rails.rb b/activesupport/lib/active_support/rails.rb index 5c34a0abb3..8b727a69ec 100644 --- a/activesupport/lib/active_support/rails.rb +++ b/activesupport/lib/active_support/rails.rb @@ -27,9 +27,3 @@ require "active_support/core_ext/module/delegation" # Defines ActiveSupport::Deprecation. require "active_support/deprecation" - -# Defines Regexp#match?. -# -# This should be removed when Rails needs Ruby 2.4 or later, and the require -# added where other Regexp extensions are being used (easy to grep). -require "active_support/core_ext/regexp" diff --git a/activesupport/lib/active_support/subscriber.rb b/activesupport/lib/active_support/subscriber.rb index d6dd5474d0..8ad39f7a05 100644 --- a/activesupport/lib/active_support/subscriber.rb +++ b/activesupport/lib/active_support/subscriber.rb @@ -54,25 +54,20 @@ module ActiveSupport @@subscribers ||= [] end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_reader :subscriber, :notifier, :namespace - private + attr_reader :subscriber, :notifier, :namespace - def add_event_subscriber(event) # :doc: - return if %w{ start finish }.include?(event.to_s) + def add_event_subscriber(event) # :doc: + return if %w{ start finish }.include?(event.to_s) - pattern = "#{event}.#{namespace}" + pattern = "#{event}.#{namespace}" - # Don't add multiple subscribers (eg. if methods are redefined). - return if subscriber.patterns.include?(pattern) + # Don't add multiple subscribers (eg. if methods are redefined). + return if subscriber.patterns.include?(pattern) - subscriber.patterns << pattern - notifier.subscribe(pattern, subscriber) - end + subscriber.patterns << pattern + notifier.subscribe(pattern, subscriber) + end end attr_reader :patterns # :nodoc: diff --git a/activesupport/lib/active_support/test_case.rb b/activesupport/lib/active_support/test_case.rb index d1f7e6ea09..a698b4e61e 100644 --- a/activesupport/lib/active_support/test_case.rb +++ b/activesupport/lib/active_support/test_case.rb @@ -11,6 +11,7 @@ require "active_support/testing/isolation" require "active_support/testing/constant_lookup" require "active_support/testing/time_helpers" require "active_support/testing/file_fixtures" +require "active_support/testing/parallelization" module ActiveSupport class TestCase < ::Minitest::Test @@ -39,6 +40,91 @@ module ActiveSupport def test_order ActiveSupport.test_order ||= :random end + + # Parallelizes the test suite. + # + # Takes a `workers` argument that controls how many times the process + # is forked. For each process a new database will be created suffixed + # with the worker number. + # + # test-database-0 + # test-database-1 + # + # If `ENV["PARALLEL_WORKERS"]` is set the workers argument will be ignored + # and the environment variable will be used instead. This is useful for CI + # environments, or other environments where you may need more workers than + # you do for local testing. + # + # If the number of workers is set to `1` or fewer, the tests will not be + # parallelized. + # + # The default parallelization method is to fork processes. If you'd like to + # use threads instead you can pass `with: :threads` 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) + # + # The threaded parallelization uses Minitest's parallel executor directly. + # The processes parallelization uses a Ruby Drb server. + def parallelize(workers: 2, with: :processes) + workers = ENV["PARALLEL_WORKERS"].to_i if ENV["PARALLEL_WORKERS"] + + return if workers <= 1 + + executor = case with + when :processes + Testing::Parallelization.new(workers) + when :threads + Minitest::Parallel::Executor.new(workers) + else + raise ArgumentError, "#{with} is not a supported parallelization executor." + end + + self.lock_threads = false if defined?(self.lock_threads) && with == :threads + + Minitest.parallel_executor = executor + + parallelize_me! + end + + # Set up hook for parallel testing. This can be used if you have multiple + # databases or any behavior that needs to be run after the process is forked + # but before the tests run. + # + # Note: this feature is not available with the threaded parallelization. + # + # In your +test_helper.rb+ add the following: + # + # class ActiveSupport::TestCase + # parallelize_setup do + # # create databases + # end + # end + def parallelize_setup(&block) + ActiveSupport::Testing::Parallelization.after_fork_hook do |worker| + yield worker + end + end + + # Clean up hook for parallel testing. This can be used to drop databases + # if your app uses multiple write/read databases or other clean up before + # the tests finish. This runs before the forked process is closed. + # + # Note: this feature is not available with the threaded parallelization. + # + # In your +test_helper.rb+ add the following: + # + # class ActiveSupport::TestCase + # parallelize_teardown do + # # drop databases + # end + # end + def parallelize_teardown(&block) + ActiveSupport::Testing::Parallelization.run_cleanup_hook do |worker| + yield worker + end + end end alias_method :method_name, :name diff --git a/activesupport/lib/active_support/testing/parallelization.rb b/activesupport/lib/active_support/testing/parallelization.rb new file mode 100644 index 0000000000..59c8486f41 --- /dev/null +++ b/activesupport/lib/active_support/testing/parallelization.rb @@ -0,0 +1,102 @@ +# frozen_string_literal: true + +require "drb" +require "drb/unix" + +module ActiveSupport + module Testing + class Parallelization # :nodoc: + class Server + include DRb::DRbUndumped + + def initialize + @queue = Queue.new + end + + def record(reporter, result) + reporter.synchronize do + reporter.record(result) + end + end + + def <<(o) + @queue << o + end + + def pop; @queue.pop; end + end + + @after_fork_hooks = [] + + def self.after_fork_hook(&blk) + @after_fork_hooks << blk + end + + def self.after_fork_hooks + @after_fork_hooks + end + + @run_cleanup_hooks = [] + + def self.run_cleanup_hook(&blk) + @run_cleanup_hooks << blk + end + + def self.run_cleanup_hooks + @run_cleanup_hooks + end + + def initialize(queue_size) + @queue_size = queue_size + @queue = Server.new + @pool = [] + + @url = DRb.start_service("drbunix:", @queue).uri + end + + def after_fork(worker) + self.class.after_fork_hooks.each do |cb| + cb.call(worker) + end + end + + def run_cleanup(worker) + self.class.run_cleanup_hooks.each do |cb| + cb.call(worker) + end + end + + def start + @pool = @queue_size.times.map do |worker| + fork do + DRb.stop_service + + after_fork(worker) + + queue = DRbObject.new_with_uri(@url) + + while job = queue.pop + klass = job[0] + method = job[1] + reporter = job[2] + result = Minitest.run_one_method(klass, method) + + queue.record(reporter, result) + end + + run_cleanup(worker) + end + end + end + + def <<(work) + @queue << work + end + + def shutdown + @queue_size.times { @queue << nil } + @pool.each { |pid| Process.waitpid pid } + end + end + end +end diff --git a/activesupport/lib/active_support/testing/time_helpers.rb b/activesupport/lib/active_support/testing/time_helpers.rb index 998a51a34c..801ea2909b 100644 --- a/activesupport/lib/active_support/testing/time_helpers.rb +++ b/activesupport/lib/active_support/testing/time_helpers.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "active_support/core_ext/module/redefine_method" -require "active_support/core_ext/string/strip" # for strip_heredoc require "active_support/core_ext/time/calculations" require "concurrent/map" @@ -112,7 +111,7 @@ module ActiveSupport # Time.current # => Sat, 09 Nov 2013 15:34:49 EST -05:00 def travel_to(date_or_time) if block_given? && simple_stubs.stubbing(Time, :now) - travel_to_nested_block_call = <<-MSG.strip_heredoc + travel_to_nested_block_call = <<~MSG Calling `travel_to` with a block, when we have previously already made a call to `travel_to`, can lead to confusing time stubbing. diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb index b75f5733b5..9dfaddb825 100644 --- a/activesupport/lib/active_support/values/time_zone.rb +++ b/activesupport/lib/active_support/values/time_zone.rb @@ -2,7 +2,6 @@ require "tzinfo" require "concurrent/map" -require "active_support/core_ext/object/blank" module ActiveSupport # The TimeZone class serves as a wrapper around TZInfo::Timezone instances. @@ -268,11 +267,14 @@ module ActiveSupport country = TZInfo::Country.get(code) country.zone_identifiers.map do |tz_id| if MAPPING.value?(tz_id) - self[MAPPING.key(tz_id)] + MAPPING.inject([]) do |memo, (key, value)| + memo << self[key] if value == tz_id + memo + end else create(tz_id, nil, TZInfo::Timezone.new(tz_id)) end - end.sort! + end.flatten(1).sort! end def zones_map diff --git a/activesupport/lib/active_support/values/unicode_tables.dat b/activesupport/lib/active_support/values/unicode_tables.dat Binary files differdeleted file mode 100644 index f7d9c48bbe..0000000000 --- a/activesupport/lib/active_support/values/unicode_tables.dat +++ /dev/null diff --git a/activesupport/lib/active_support/xml_mini.rb b/activesupport/lib/active_support/xml_mini.rb index ee2048cb54..d0659aeaae 100644 --- a/activesupport/lib/active_support/xml_mini.rb +++ b/activesupport/lib/active_support/xml_mini.rb @@ -48,10 +48,6 @@ module ActiveSupport "Array" => "array", "Hash" => "hash" } - - # No need to map these on Ruby 2.4+ - TYPE_NAMES["Fixnum"] = "integer" unless 0.class == Integer - TYPE_NAMES["Bignum"] = "integer" unless 0.class == Integer end FORMATTING = { diff --git a/activesupport/test/callbacks_test.rb b/activesupport/test/callbacks_test.rb index 30f1632460..6c7643ed72 100644 --- a/activesupport/test/callbacks_test.rb +++ b/activesupport/test/callbacks_test.rb @@ -482,10 +482,9 @@ module CallbacksTest "block in run_callbacks", "tweedle_dum", "block in run_callbacks", - ("call" if RUBY_VERSION < "2.3"), "run_callbacks", "save" - ].compact, call_stack.map(&:label) + ], call_stack.map(&:label) end def test_short_call_stack diff --git a/activesupport/test/core_ext/array/grouping_test.rb b/activesupport/test/core_ext/array/grouping_test.rb index c182b91826..37111a5d7d 100644 --- a/activesupport/test/core_ext/array/grouping_test.rb +++ b/activesupport/test/core_ext/array/grouping_test.rb @@ -4,15 +4,6 @@ require "abstract_unit" require "active_support/core_ext/array" class GroupingTest < ActiveSupport::TestCase - def setup - # In Ruby < 2.4, test we avoid Integer#/ (redefined by mathn) - Fixnum.send :private, :/ unless 0.class == Integer - end - - def teardown - Fixnum.send :public, :/ unless 0.class == Integer - end - def test_in_groups_of_with_perfect_fit groups = [] ("a".."i").to_a.in_groups_of(3) do |group| diff --git a/activesupport/test/core_ext/duration_test.rb b/activesupport/test/core_ext/duration_test.rb index c5022cf5b1..8f6befe809 100644 --- a/activesupport/test/core_ext/duration_test.rb +++ b/activesupport/test/core_ext/duration_test.rb @@ -24,7 +24,7 @@ class DurationTest < ActiveSupport::TestCase end def test_instance_of - assert 1.minute.instance_of?(1.class) + assert 1.minute.instance_of?(Integer) assert 2.days.instance_of?(ActiveSupport::Duration) assert !3.second.instance_of?(Numeric) end diff --git a/activesupport/test/core_ext/hash/transform_values_test.rb b/activesupport/test/core_ext/hash/transform_values_test.rb index d34b7fa7b9..e481b5e4a9 100644 --- a/activesupport/test/core_ext/hash/transform_values_test.rb +++ b/activesupport/test/core_ext/hash/transform_values_test.rb @@ -2,25 +2,16 @@ require "abstract_unit" require "active_support/core_ext/hash/indifferent_access" -require "active_support/core_ext/hash/transform_values" -class TransformValuesTest < ActiveSupport::TestCase - test "transform_values returns a new hash with the values computed from the block" do - original = { a: "a", b: "b" } - mapped = original.transform_values { |v| v + "!" } - - assert_equal({ a: "a", b: "b" }, original) - assert_equal({ a: "a!", b: "b!" }, mapped) - end - - test "transform_values! modifies the values of the original" do - original = { a: "a", b: "b" } - mapped = original.transform_values! { |v| v + "!" } - - assert_equal({ a: "a!", b: "b!" }, original) - assert_same original, mapped +class TransformValuesDeprecatedRequireTest < ActiveSupport::TestCase + test "requiring transform_values is deprecated" do + assert_deprecated do + require "active_support/core_ext/hash/transform_values" + end end +end +class IndifferentTransformValuesTest < ActiveSupport::TestCase test "indifferent access is still indifferent after mapping values" do original = { a: "a", b: "b" }.with_indifferent_access mapped = original.transform_values { |v| v + "!" } @@ -28,50 +19,4 @@ class TransformValuesTest < ActiveSupport::TestCase assert_equal "a!", mapped[:a] assert_equal "a!", mapped["a"] end - - # This is to be consistent with the behavior of Ruby's built in methods - # (e.g. #select, #reject) as of 2.2 - test "default values do not persist during mapping" do - original = Hash.new("foo") - original[:a] = "a" - mapped = original.transform_values { |v| v + "!" } - - assert_equal "a!", mapped[:a] - assert_nil mapped[:b] - end - - test "default procs do not persist after mapping" do - original = Hash.new { "foo" } - original[:a] = "a" - mapped = original.transform_values { |v| v + "!" } - - assert_equal "a!", mapped[:a] - assert_nil mapped[:b] - end - - test "transform_values returns a sized Enumerator if no block is given" do - original = { a: "a", b: "b" } - enumerator = original.transform_values - assert_equal original.size, enumerator.size - assert_equal Enumerator, enumerator.class - end - - test "transform_values! returns a sized Enumerator if no block is given" do - original = { a: "a", b: "b" } - enumerator = original.transform_values! - assert_equal original.size, enumerator.size - assert_equal Enumerator, enumerator.class - end - - test "transform_values is chainable with Enumerable methods" do - original = { a: "a", b: "b" } - mapped = original.transform_values.with_index { |v, i| [v, i].join } - assert_equal({ a: "a0", b: "b1" }, mapped) - end - - test "transform_values! is chainable with Enumerable methods" do - original = { a: "a", b: "b" } - original.transform_values!.with_index { |v, i| [v, i].join } - assert_equal({ a: "a0", b: "b1" }, original) - end end diff --git a/activesupport/test/core_ext/object/duplicable_test.rb b/activesupport/test/core_ext/object/duplicable_test.rb index b984becce3..635dd7f281 100644 --- a/activesupport/test/core_ext/object/duplicable_test.rb +++ b/activesupport/test/core_ext/object/duplicable_test.rb @@ -9,15 +9,9 @@ class DuplicableTest < ActiveSupport::TestCase if RUBY_VERSION >= "2.5.0" RAISE_DUP = [method(:puts)] ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("4.56"), nil, false, true, 1, 2.3, Complex(1), Rational(1)] - elsif RUBY_VERSION >= "2.4.1" + else RAISE_DUP = [method(:puts), Complex(1), Rational(1)] ALLOW_DUP = ["1", "symbol_from_string".to_sym, Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("4.56"), nil, false, true, 1, 2.3] - elsif RUBY_VERSION >= "2.4.0" # Due to 2.4.0 bug. This elsif cannot be removed unless we drop 2.4.0 support... - RAISE_DUP = [method(:puts), Complex(1), Rational(1), "symbol_from_string".to_sym] - ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("4.56"), nil, false, true, 1, 2.3] - else - RAISE_DUP = [nil, false, true, :symbol, 1, 2.3, method(:puts), Complex(1), Rational(1)] - ALLOW_DUP = ["1", Object.new, /foo/, [], {}, Time.now, Class.new, Module.new, BigDecimal("4.56")] end def test_duplicable diff --git a/activesupport/test/core_ext/string_ext_test.rb b/activesupport/test/core_ext/string_ext_test.rb index 472190277e..ccaa5dc786 100644 --- a/activesupport/test/core_ext/string_ext_test.rb +++ b/activesupport/test/core_ext/string_ext_test.rb @@ -9,9 +9,9 @@ require "constantize_test_cases" require "active_support/inflector" require "active_support/core_ext/string" require "active_support/time" -require "active_support/core_ext/string/strip" require "active_support/core_ext/string/output_safety" require "active_support/core_ext/string/indent" +require "active_support/core_ext/string/strip" require "time_zone_test_helpers" require "yaml" @@ -24,6 +24,10 @@ class StringInflectionsTest < ActiveSupport::TestCase assert_equal "", "".strip_heredoc end + def test_strip_heredoc_on_a_frozen_string + assert "".freeze.strip_heredoc.frozen? + end + def test_strip_heredoc_on_a_string_with_no_lines assert_equal "x", "x".strip_heredoc assert_equal "x", " x".strip_heredoc @@ -281,6 +285,68 @@ class StringInflectionsTest < ActiveSupport::TestCase assert_equal "Hello Big[...]", "Hello Big World!".truncate(15, omission: "[...]", separator: /\s/) end + def test_truncate_bytes + assert_equal "👍👍👍👍", "👍👍👍👍".truncate_bytes(16) + assert_equal "👍👍👍👍", "👍👍👍👍".truncate_bytes(16, omission: nil) + assert_equal "👍👍👍👍", "👍👍👍👍".truncate_bytes(16, omission: " ") + assert_equal "👍👍👍👍", "👍👍👍👍".truncate_bytes(16, omission: "🖖") + + assert_equal "👍👍👍…", "👍👍👍👍".truncate_bytes(15) + assert_equal "👍👍👍", "👍👍👍👍".truncate_bytes(15, omission: nil) + assert_equal "👍👍👍 ", "👍👍👍👍".truncate_bytes(15, omission: " ") + assert_equal "👍👍🖖", "👍👍👍👍".truncate_bytes(15, omission: "🖖") + + assert_equal "…", "👍👍👍👍".truncate_bytes(5) + assert_equal "👍", "👍👍👍👍".truncate_bytes(5, omission: nil) + assert_equal "👍 ", "👍👍👍👍".truncate_bytes(5, omission: " ") + assert_equal "🖖", "👍👍👍👍".truncate_bytes(5, omission: "🖖") + + assert_equal "…", "👍👍👍👍".truncate_bytes(4) + assert_equal "👍", "👍👍👍👍".truncate_bytes(4, omission: nil) + assert_equal " ", "👍👍👍👍".truncate_bytes(4, omission: " ") + assert_equal "🖖", "👍👍👍👍".truncate_bytes(4, omission: "🖖") + + assert_raise ArgumentError do + "👍👍👍👍".truncate_bytes(3, omission: "🖖") + end + end + + def test_truncate_bytes_preserves_codepoints + assert_equal "👍👍👍👍", "👍👍👍👍".truncate_bytes(16) + assert_equal "👍👍👍👍", "👍👍👍👍".truncate_bytes(16, omission: nil) + assert_equal "👍👍👍👍", "👍👍👍👍".truncate_bytes(16, omission: " ") + assert_equal "👍👍👍👍", "👍👍👍👍".truncate_bytes(16, omission: "🖖") + + assert_equal "👍👍👍…", "👍👍👍👍".truncate_bytes(15) + assert_equal "👍👍👍", "👍👍👍👍".truncate_bytes(15, omission: nil) + assert_equal "👍👍👍 ", "👍👍👍👍".truncate_bytes(15, omission: " ") + assert_equal "👍👍🖖", "👍👍👍👍".truncate_bytes(15, omission: "🖖") + + assert_equal "…", "👍👍👍👍".truncate_bytes(5) + assert_equal "👍", "👍👍👍👍".truncate_bytes(5, omission: nil) + assert_equal "👍 ", "👍👍👍👍".truncate_bytes(5, omission: " ") + assert_equal "🖖", "👍👍👍👍".truncate_bytes(5, omission: "🖖") + + assert_equal "…", "👍👍👍👍".truncate_bytes(4) + assert_equal "👍", "👍👍👍👍".truncate_bytes(4, omission: nil) + assert_equal " ", "👍👍👍👍".truncate_bytes(4, omission: " ") + assert_equal "🖖", "👍👍👍👍".truncate_bytes(4, omission: "🖖") + + assert_raise ArgumentError do + "👍👍👍👍".truncate_bytes(3, omission: "🖖") + end + end + + def test_truncates_bytes_preserves_grapheme_clusters + assert_equal "a ", "a ❤️ b".truncate_bytes(2, omission: nil) + assert_equal "a ", "a ❤️ b".truncate_bytes(3, omission: nil) + assert_equal "a ", "a ❤️ b".truncate_bytes(7, omission: nil) + assert_equal "a ❤️", "a ❤️ b".truncate_bytes(8, omission: nil) + + assert_equal "a ", "a 👩❤️👩".truncate_bytes(13, omission: nil) + assert_equal "", "👩❤️👩".truncate_bytes(13, omission: nil) + end + def test_truncate_words assert_equal "Hello Big World!", "Hello Big World!".truncate_words(3) assert_equal "Hello Big...", "Hello Big World!".truncate_words(2) diff --git a/activesupport/test/core_ext/time_with_zone_test.rb b/activesupport/test/core_ext/time_with_zone_test.rb index be3b8a49a9..2ea5f0921c 100644 --- a/activesupport/test/core_ext/time_with_zone_test.rb +++ b/activesupport/test/core_ext/time_with_zone_test.rb @@ -3,7 +3,6 @@ require "abstract_unit" require "active_support/time" require "time_zone_test_helpers" -require "active_support/core_ext/string/strip" require "yaml" class TimeWithZoneTest < ActiveSupport::TestCase @@ -163,7 +162,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_to_yaml - yaml = <<-EOF.strip_heredoc + yaml = <<~EOF --- !ruby/object:ActiveSupport::TimeWithZone utc: 2000-01-01 00:00:00.000000000 Z zone: !ruby/object:ActiveSupport::TimeZone @@ -175,7 +174,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_ruby_to_yaml - yaml = <<-EOF.strip_heredoc + yaml = <<~EOF --- twz: !ruby/object:ActiveSupport::TimeWithZone utc: 2000-01-01 00:00:00.000000000 Z @@ -188,7 +187,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_yaml_load - yaml = <<-EOF.strip_heredoc + yaml = <<~EOF --- !ruby/object:ActiveSupport::TimeWithZone utc: 2000-01-01 00:00:00.000000000 Z zone: !ruby/object:ActiveSupport::TimeZone @@ -200,7 +199,7 @@ class TimeWithZoneTest < ActiveSupport::TestCase end def test_ruby_yaml_load - yaml = <<-EOF.strip_heredoc + yaml = <<~EOF --- twz: !ruby/object:ActiveSupport::TimeWithZone utc: 2000-01-01 00:00:00.000000000 Z diff --git a/activesupport/test/hash_with_indifferent_access_test.rb b/activesupport/test/hash_with_indifferent_access_test.rb index 41d653fa59..b06250baf8 100644 --- a/activesupport/test/hash_with_indifferent_access_test.rb +++ b/activesupport/test/hash_with_indifferent_access_test.rb @@ -169,8 +169,6 @@ class HashWithIndifferentAccessTest < ActiveSupport::TestCase end def test_indifferent_fetch_values - skip unless Hash.method_defined?(:fetch_values) - @mixed = @mixed.with_indifferent_access assert_equal [1, 2], @mixed.fetch_values("a", "b") @@ -404,6 +402,12 @@ class HashWithIndifferentAccessTest < ActiveSupport::TestCase assert_equal({ "aa" => 1, "bb" => 2 }, hash) assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash + + hash = ActiveSupport::HashWithIndifferentAccess.new(@strings).transform_keys { |k| k.to_sym } + + assert_equal(1, hash[:a]) + assert_equal(1, hash["a"]) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, hash end def test_indifferent_transform_keys_bang @@ -412,6 +416,13 @@ class HashWithIndifferentAccessTest < ActiveSupport::TestCase assert_equal({ "aa" => 1, "bb" => 2 }, indifferent_strings) assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings + + indifferent_strings = ActiveSupport::HashWithIndifferentAccess.new(@strings) + indifferent_strings.transform_keys! { |k| k.to_sym } + + assert_equal(1, indifferent_strings[:a]) + assert_equal(1, indifferent_strings["a"]) + assert_instance_of ActiveSupport::HashWithIndifferentAccess, indifferent_strings end def test_indifferent_transform_values @@ -562,7 +573,6 @@ class HashWithIndifferentAccessTest < ActiveSupport::TestCase end def test_nested_dig_indifferent_access - skip if RUBY_VERSION < "2.3.0" data = { "this" => { "views" => 1234 } }.with_indifferent_access assert_equal 1234, data.dig(:this, :views) end diff --git a/activesupport/test/multibyte_unicode_database_test.rb b/activesupport/test/multibyte_unicode_database_test.rb deleted file mode 100644 index 540a34493d..0000000000 --- a/activesupport/test/multibyte_unicode_database_test.rb +++ /dev/null @@ -1,26 +0,0 @@ -# frozen_string_literal: true - -require "abstract_unit" - -class MultibyteUnicodeDatabaseTest < ActiveSupport::TestCase - include ActiveSupport::Multibyte::Unicode - - def setup - @ucd = UnicodeDatabase.new - end - - UnicodeDatabase::ATTRIBUTES.each do |attribute| - define_method "test_lazy_loading_on_attribute_access_of_#{attribute}" do - assert_called(@ucd, :load) do - @ucd.send(attribute) - end - end - end - - def test_load - @ucd.load - UnicodeDatabase::ATTRIBUTES.each do |attribute| - assert @ucd.send(attribute).length > 1 - end - end -end diff --git a/activesupport/test/time_zone_test.rb b/activesupport/test/time_zone_test.rb index 405c8f315b..63ca22efb5 100644 --- a/activesupport/test/time_zone_test.rb +++ b/activesupport/test/time_zone_test.rb @@ -756,6 +756,16 @@ class TimeZoneTest < ActiveSupport::TestCase assert_not_includes ActiveSupport::TimeZone.country_zones(:ru), ActiveSupport::TimeZone["Kuala Lumpur"] end + def test_country_zones_with_and_without_mappings + assert_includes ActiveSupport::TimeZone.country_zones("au"), ActiveSupport::TimeZone["Adelaide"] + assert_includes ActiveSupport::TimeZone.country_zones("au"), ActiveSupport::TimeZone["Australia/Lord_Howe"] + end + + def test_country_zones_with_multiple_mappings + assert_includes ActiveSupport::TimeZone.country_zones("gb"), ActiveSupport::TimeZone["Edinburgh"] + assert_includes ActiveSupport::TimeZone.country_zones("gb"), ActiveSupport::TimeZone["London"] + end + def test_country_zones_without_mappings assert_includes ActiveSupport::TimeZone.country_zones(:sv), ActiveSupport::TimeZone["America/El_Salvador"] end diff --git a/guides/CHANGELOG.md b/guides/CHANGELOG.md index 64e8b51817..21b4ed4b9f 100644 --- a/guides/CHANGELOG.md +++ b/guides/CHANGELOG.md @@ -1,3 +1,8 @@ +## Rails 6.0.0.alpha (Unreleased) ## + +* Rails 6 requires Ruby 2.4.1 or newer. + + *Jeremy Daer* Please check [5-2-stable](https://github.com/rails/rails/blob/5-2-stable/guides/CHANGELOG.md) for previous changes. diff --git a/guides/bug_report_templates/action_controller_gem.rb b/guides/bug_report_templates/action_controller_gem.rb index 557b1d7bef..7fc85e636a 100644 --- a/guides/bug_report_templates/action_controller_gem.rb +++ b/guides/bug_report_templates/action_controller_gem.rb @@ -13,7 +13,7 @@ gemfile(true) do git_source(:github) { |repo| "https://github.com/#{repo}.git" } # Activate the gem you are reporting the issue against. - gem "rails", "5.1.0" + gem "rails", "5.2.0.rc1" end require "rack/test" diff --git a/guides/bug_report_templates/active_job_gem.rb b/guides/bug_report_templates/active_job_gem.rb index 013d1f8602..6b30a7d446 100644 --- a/guides/bug_report_templates/active_job_gem.rb +++ b/guides/bug_report_templates/active_job_gem.rb @@ -13,7 +13,7 @@ gemfile(true) do git_source(:github) { |repo| "https://github.com/#{repo}.git" } # Activate the gem you are reporting the issue against. - gem "activejob", "5.1.0" + gem "activejob", "5.2.0.rc1" end require "minitest/autorun" diff --git a/guides/bug_report_templates/active_record_gem.rb b/guides/bug_report_templates/active_record_gem.rb index 921917fbe9..fabc2a2382 100644 --- a/guides/bug_report_templates/active_record_gem.rb +++ b/guides/bug_report_templates/active_record_gem.rb @@ -13,7 +13,7 @@ gemfile(true) do git_source(:github) { |repo| "https://github.com/#{repo}.git" } # Activate the gem you are reporting the issue against. - gem "activerecord", "5.1.0" + gem "activerecord", "5.2.0.rc1" gem "sqlite3" end diff --git a/guides/bug_report_templates/active_record_migrations_gem.rb b/guides/bug_report_templates/active_record_migrations_gem.rb index 9002b8f428..ca9987f956 100644 --- a/guides/bug_report_templates/active_record_migrations_gem.rb +++ b/guides/bug_report_templates/active_record_migrations_gem.rb @@ -13,7 +13,7 @@ gemfile(true) do git_source(:github) { |repo| "https://github.com/#{repo}.git" } # Activate the gem you are reporting the issue against. - gem "activerecord", "5.1.0" + gem "activerecord", "5.2.0.rc1" gem "sqlite3" end @@ -37,7 +37,7 @@ end class Payment < ActiveRecord::Base end -class ChangeAmountToAddScale < ActiveRecord::Migration[5.1] +class ChangeAmountToAddScale < ActiveRecord::Migration[5.2] def change reversible do |dir| dir.up do diff --git a/guides/bug_report_templates/active_record_migrations_master.rb b/guides/bug_report_templates/active_record_migrations_master.rb index 194b093ac3..715dca98ba 100644 --- a/guides/bug_report_templates/active_record_migrations_master.rb +++ b/guides/bug_report_templates/active_record_migrations_master.rb @@ -36,7 +36,7 @@ end class Payment < ActiveRecord::Base end -class ChangeAmountToAddScale < ActiveRecord::Migration[5.2] +class ChangeAmountToAddScale < ActiveRecord::Migration[6.0] def change reversible do |dir| dir.up do diff --git a/guides/bug_report_templates/generic_gem.rb b/guides/bug_report_templates/generic_gem.rb index 60e8322c2a..7a55d7c660 100644 --- a/guides/bug_report_templates/generic_gem.rb +++ b/guides/bug_report_templates/generic_gem.rb @@ -13,9 +13,10 @@ gemfile(true) do git_source(:github) { |repo| "https://github.com/#{repo}.git" } # Activate the gem you are reporting the issue against. - gem "activesupport", "5.1.0" + gem "activesupport", "5.2.0.rc1" end +require "active_support" require "active_support/core_ext/object/blank" require "minitest/autorun" diff --git a/guides/source/active_job_basics.md b/guides/source/active_job_basics.md index 9b738e8cdd..97d98efba0 100644 --- a/guides/source/active_job_basics.md +++ b/guides/source/active_job_basics.md @@ -276,7 +276,7 @@ class GuestsCleanupJob < ApplicationJob end private - def around_cleanup(job) + def around_cleanup # Do something before perform yield # Do something after perform @@ -344,14 +344,14 @@ Supported types for arguments ActiveJob supports the following types of arguments by default: - - Basic types (`NilClass`, `String`, `Integer`, `Fixnum`, `Bignum`, `Float`, `BigDecimal`, `TrueClass`, `FalseClass`) + - Basic types (`NilClass`, `String`, `Integer`, `Float`, `BigDecimal`, `TrueClass`, `FalseClass`) - `Symbol` - - `ActiveSupport::Duration` - `Date` - `Time` - `DateTime` - `ActiveSupport::TimeWithZone` - - `Hash`. Keys should be of `String` or `Symbol` type + - `ActiveSupport::Duration` + - `Hash` (Keys should be of `String` or `Symbol` type) - `ActiveSupport::HashWithIndifferentAccess` - `Array` @@ -385,39 +385,38 @@ by default has been mixed into Active Record classes. ### Serializers -You can extend list of supported types for arguments. You just need to define your own serializer. +You can extend the list of supported argument types. You just need to define your own serializer: ```ruby class MoneySerializer < ActiveJob::Serializers::ObjectSerializer - # Check if this object should be serialized using this serializer. + # Checks if an argument should be serialized by this serializer. def serialize?(argument) argument.is_a? Money end - # Convert an object to a simpler representative using supported object types. + # Converts an object to a simpler representative using supported object types. # The recommended representative is a Hash with a specific key. Keys can be of basic types only. - # You should call `super` to add the custom serializer type to the hash - def serialize(object) + # You should call `super` to add the custom serializer type to the hash. + def serialize(money) super( - "cents" => object.cents, - "currency" => object.currency + "amount" => money.amount, + "currency" => money.currency ) end - # Convert serialized value into a proper object + # Converts serialized value into a proper object. def deserialize(hash) - Money.new hash["cents"], hash["currency"] + Money.new(hash["amount"], hash["currency"]) end end ``` -And now you just need to add this serializer to a list: +and add this serializer to the list: ```ruby -Rails.application.config.active_job.custom_serializers << MySpecialSerializer +Rails.application.config.active_job.custom_serializers << MoneySerializer ``` - Exceptions ---------- diff --git a/guides/source/active_record_callbacks.md b/guides/source/active_record_callbacks.md index 630dafe632..4f54b4c206 100644 --- a/guides/source/active_record_callbacks.md +++ b/guides/source/active_record_callbacks.md @@ -264,7 +264,7 @@ The whole callback chain is wrapped in a transaction. If any callback raises an throw :abort ``` -WARNING. Any exception that is not `ActiveRecord::Rollback` or `ActiveRecord::RecordInvalid` will be re-raised by Rails after the callback chain is halted. Raising an exception other than `ActiveRecord::Rollback` or `ActiveRecord::RecordInvalid` may break code that does not expect methods like `save` and `update_attributes` (which normally try to return `true` or `false`) to raise an exception. +WARNING. Any exception that is not `ActiveRecord::Rollback` or `ActiveRecord::RecordInvalid` will be re-raised by Rails after the callback chain is halted. Raising an exception other than `ActiveRecord::Rollback` or `ActiveRecord::RecordInvalid` may break code that does not expect methods like `save` and `update` (which normally try to return `true` or `false`) to raise an exception. Relational Callbacks -------------------- diff --git a/guides/source/active_storage_overview.md b/guides/source/active_storage_overview.md index 97c56dfd93..a7cb14c52a 100644 --- a/guides/source/active_storage_overview.md +++ b/guides/source/active_storage_overview.md @@ -121,6 +121,8 @@ Add the [`aws-sdk-s3`](https://github.com/aws/aws-sdk-ruby) gem to your `Gemfile gem "aws-sdk-s3", require: false ``` +NOTE: The core features of Active Storage require the following permissions: `s3:ListBucket`, `s3:PutObject`, `s3:GetObject`, and `s3:DeleteObject`. If you have additional upload options configured such as setting ACLs then additional permissions may be required. + ### Microsoft Azure Storage Service Declare an Azure Storage service in `config/storage.yml`: @@ -556,6 +558,30 @@ config.active_job.queue_adapter = :inline config.active_storage.service = :local_test ``` +Discarding Files Stored During Integration Tests +------------------------------------------- + +Similarly to System Tests, files uploaded during Integration Tests will not be +automatically cleaned up. If you want to clear the files, you can do it in an +`after_teardown` callback. Doing it here ensures that all connections created +during the test are complete and you won't receive an error from Active Storage +saying it can't find a file. + +```ruby +module ActionDispatch + class IntegrationTest + def remove_uploaded_files + FileUtils.rm_rf(Rails.root.join('tmp', 'storage')) + end + + def after_teardown + super + remove_uploaded_files + end + end +end +``` + Implementing Support for Other Cloud Services --------------------------------------------- diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md index 6c980fb0e9..73b24b900a 100644 --- a/guides/source/active_support_core_extensions.md +++ b/guides/source/active_support_core_extensions.md @@ -135,16 +135,14 @@ NOTE: Defined in `active_support/core_ext/object/blank.rb`. ### `duplicable?` -In Ruby 2.4 most objects can be duplicated via `dup` or `clone` except -methods and certain numbers. Though Ruby 2.2 and 2.3 can't duplicate `nil`, -`false`, `true`, and symbols as well as instances `Float`, `Fixnum`, -and `Bignum` instances. +As of Ruby 2.5, most objects can be duplicated via `dup` or `clone`: ```ruby "foo".dup # => "foo" "".dup # => "" -1.method(:+).dup # => TypeError: allocator undefined for Method -Complex(0).dup # => TypeError: can't copy Complex +Rational(1).dup # => (1/1) +Complex(0).dup # => (0+0i) +1.method(:+).dup # => TypeError (allocator undefined for Method) ``` Active Support provides `duplicable?` to query an object about this: @@ -152,35 +150,18 @@ Active Support provides `duplicable?` to query an object about this: ```ruby "foo".duplicable? # => true "".duplicable? # => true -Rational(1).duplicable? # => false -Complex(1).duplicable? # => false +Rational(1).duplicable? # => true +Complex(1).duplicable? # => true 1.method(:+).duplicable? # => false ``` -`duplicable?` matches Ruby's `dup` according to the Ruby version. - -So in 2.4: +`duplicable?` matches the current Ruby version's `dup` behavior, +so results will vary according the version of Ruby you're using. +In Ruby 2.4, for example, Complex and Rational are not duplicable: ```ruby -nil.dup # => nil -:my_symbol.dup # => :my_symbol -1.dup # => 1 - -nil.duplicable? # => true -:my_symbol.duplicable? # => true -1.duplicable? # => true -``` - -Whereas in 2.2 and 2.3: - -```ruby -nil.dup # => TypeError: can't dup NilClass -:my_symbol.dup # => TypeError: can't dup Symbol -1.dup # => TypeError: can't dup Fixnum - -nil.duplicable? # => false -:my_symbol.duplicable? # => false -1.duplicable? # => false +Rational(1).duplicable? # => false +Complex(1).duplicable? # => false ``` WARNING: Any class can disallow duplication by removing `dup` and `clone` or raising exceptions from them. Thus only `rescue` can tell whether a given arbitrary object is duplicable. `duplicable?` depends on the hard-coded list above, but it is much faster than `rescue`. Use it only if you know the hard-coded list is enough in your use case. @@ -2776,20 +2757,6 @@ Active Record does not accept unknown options when building associations, for ex NOTE: Defined in `active_support/core_ext/hash/keys.rb`. -### Working with Values - -#### `transform_values` && `transform_values!` - -The method `transform_values` accepts a block and returns a hash that has applied the block operations to each of the values in the receiver. - -```ruby -{ nil => nil, 1 => 1, :x => :a }.transform_values { |value| value.to_s.upcase } -# => {nil=>"", 1=>"1", :x=>"A"} -``` -There's also the bang variant `transform_values!` that applies the block operations to values in the very receiver. - -NOTE: Defined in `active_support/core_ext/hash/transform_values.rb`. - ### Slicing Ruby has built-in support for taking slices out of strings and arrays. Active Support extends slicing to hashes: @@ -2890,24 +2857,6 @@ end NOTE: Defined in `active_support/core_ext/regexp.rb`. -### `match?` - -Rails implements `Regexp#match?` for Ruby versions prior to 2.4: - -```ruby -/oo/.match?('foo') # => true -/oo/.match?('bar') # => false -/oo/.match?('foo', 1) # => true -``` - -The backport has the same interface and lack of side-effects in the caller like -not setting `$1` and friends, but it does not have the speed benefits. Its -purpose is to be able to write 2.4 compatible code. Rails itself uses this -predicate internally for example. - -Active Support defines `Regexp#match?` only if not present, so code running -under 2.4 or later does run the original one and gets the performance boost. - Extensions to `Range` --------------------- diff --git a/guides/source/caching_with_rails.md b/guides/source/caching_with_rails.md index cd9f4b4a68..5dde6f34fa 100644 --- a/guides/source/caching_with_rails.md +++ b/guides/source/caching_with_rails.md @@ -465,6 +465,10 @@ is often faster than waiting more than a second to retrieve it. Both read and write timeouts default to 1 second, but may be set lower if your network is consistently low latency. +By default, the cache store will not attempt to reconnect to Redis if the +connection fails during a request. If you experience frequent disconnects you +may wish to enable reconnect attempts. + Cache reads and writes never raise exceptions. They just return `nil` instead, behaving as if there was nothing in the cache. To gauge whether your cache is hitting exceptions, you may provide an `error_handler` to report to an @@ -480,9 +484,10 @@ like this: cache_servers = %w[ "redis://cache-01:6379/0", "redis://cache-02:6379/0", … ], config.cache_store = :redis_cache_store, url: cache_servers, - connect_timeout: 30, # Defaults to 20 seconds - read_timeout: 0.2, # Defaults to 1 second - write_timeout: 0.2, # Defaults to 1 second + connect_timeout: 30, # Defaults to 20 seconds + read_timeout: 0.2, # Defaults to 1 second + write_timeout: 0.2, # Defaults to 1 second + reconnect_attempts: 1, # Defaults to 0 error_handler: -> (method:, returning:, exception:) { # Report errors to Sentry as warnings diff --git a/guides/source/command_line.md b/guides/source/command_line.md index 648645af7c..b41e8bbec6 100644 --- a/guides/source/command_line.md +++ b/guides/source/command_line.md @@ -431,16 +431,16 @@ INFO: You can also use `bin/rails -T` to get the list of tasks. ```bash $ bin/rails about About your application's environment -Rails version 5.1.0 -Ruby version 2.2.2 (x86_64-linux) -RubyGems version 2.4.6 -Rack version 2.0.1 +Rails version 6.0.0 +Ruby version 2.5.0 (x86_64-linux) +RubyGems version 2.7.3 +Rack version 2.0.4 JavaScript Runtime Node.js (V8) Middleware: Rack::Sendfile, ActionDispatch::Static, ActionDispatch::Executor, ActiveSupport::Cache::Strategy::LocalCache::Middleware, Rack::Runtime, Rack::MethodOverride, ActionDispatch::RequestId, ActionDispatch::RemoteIp, Sprockets::Rails::QuietAssets, Rails::Rack::Logger, ActionDispatch::ShowExceptions, WebConsole::Middleware, ActionDispatch::DebugExceptions, ActionDispatch::Reloader, ActionDispatch::Callbacks, ActiveRecord::Migration::CheckPending, ActionDispatch::Cookies, ActionDispatch::Session::CookieStore, ActionDispatch::Flash, Rack::Head, Rack::ConditionalGet, Rack::ETag Application root /home/foobar/commandsapp Environment development Database adapter sqlite3 -Database schema version 20110805173523 +Database schema version 20180205173523 ``` ### `assets` diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md index ad587d6117..f545b90103 100644 --- a/guides/source/getting_started.md +++ b/guides/source/getting_started.md @@ -87,10 +87,10 @@ current version of Ruby installed: ```bash $ ruby -v -ruby 2.3.1p112 +ruby 2.5.0 ``` -Rails requires Ruby version 2.2.2 or later. If the version number returned is +Rails requires Ruby version 2.4.1 or later. If the version number returned is less than that number, you'll need to install a fresh copy of Ruby. TIP: A number of tools exist to help you quickly install Ruby and Ruby diff --git a/guides/source/testing.md b/guides/source/testing.md index af6127f4a5..b9b310cbba 100644 --- a/guides/source/testing.md +++ b/guides/source/testing.md @@ -462,6 +462,89 @@ Rails options: -c, --[no-]color Enable color in the output ``` +Parallel Testing +---------------- + +Parallel testing allows you to parallelize your test suite. While forking processes is the +default method, threading is supported as well. Running tests in parallel reduces the time it +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. Active Record automatically handles creating and +migrating a new database for each worker to use. + +To enable parallelization add the following to your `test_helper.rb`: + +``` +class ActiveSupport::TestCase + parallelize(workers: 2) +end +``` + +The number of workers passed is the number of times the process will be forked. You may want to +parallelize your local test suite differently from your CI, so an environment variable is provided +to be able to easily change the number of workers a test run should use: + +``` +PARALLEL_WORKERS=15 bin/rails test +``` + +When parallelizing tests, Active Record automatically handles creating and migrating a database for each +process. The databases will be suffixed with the number corresponding to the worker. For example, if you +have 2 workers the tests will create `test-database-0` and `test-database-1` respectively. + +If the number of workers passed is 1 or fewer the processes will not be forked and the tests will not +be parallelized and the tests will use the original `test-database` database. + +Two hooks are provided, one runs when the process is forked, and one runs before the processes are closed. +These can be useful if your app uses multiple databases or perform other tasks that depend on the number of +workers. + +The `parallelize_setup` method is called right after the processes are forked. The `parallelize_teardown` metod +is called right before the processes are closed. + +``` +class ActiveSupport::TestCase + parallelize_setup do |worker| + # setup databases + end + + parallelize_teardown do |worker| + # cleanup database + end + + parallelize(workers: 2) +end +``` + +These methods are not needed or available when using parallel testing with threads. + +### Parallel testing with threads + +If you prefer using threads or are using JRuby, a threaded parallelization option is provided. The threaded +parallelizer is backed by Minitest's `Parallel::Executor`. + +To change the parallelization method to use threads over forks put the following in your `test_helper.rb` + +``` +class ActiveSupport::TestCase + parallelize(workers: 2, with: :threads) +end +``` + +Rails applications generated from JRuby will automatically include the `with: :threads` option. + +The number of workers passed to `parallelize` determines the number of threads the tests will use. You may +want to parallelize your local test suite differently from your CI, so an environment variable is provided +to be able to easily change the number of workers a test run should use: + +``` +PARALLEL_WORKERS=15 bin/rails test +``` + The Test Database ----------------- diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md index 51b284ff12..ff7ccfd47d 100644 --- a/guides/source/upgrading_ruby_on_rails.md +++ b/guides/source/upgrading_ruby_on_rails.md @@ -35,6 +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 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 4b57377871..709ce642f3 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.2.2" + s.required_ruby_version = ">= 2.4.1" s.required_rubygems_version = ">= 1.8.11" s.license = "MIT" diff --git a/railties/.gitignore b/railties/.gitignore index 80dd262d2f..c08562e016 100644 --- a/railties/.gitignore +++ b/railties/.gitignore @@ -1 +1,5 @@ -log/ +/log/ +/test/500.html +/test/fixtures/tmp/ +/test/initializer/root/log/ +/tmp/ diff --git a/railties/CHANGELOG.md b/railties/CHANGELOG.md index b3b35307e3..f1e16ec50f 100644 --- a/railties/CHANGELOG.md +++ b/railties/CHANGELOG.md @@ -1,3 +1,16 @@ +## Rails 6.0.0.alpha (Unreleased) ## + +* Rails 6 requires Ruby 2.4.1 or newer. + + *Jeremy Daer* + +* Fix minitest rails plugin. + + The custom reporters are added only if needed. + + This will fix conflicts with others plugins. + + *Kevin Robatel* 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/minitest/rails_plugin.rb b/railties/lib/minitest/rails_plugin.rb index 6901b0bbc8..7193abbc33 100644 --- a/railties/lib/minitest/rails_plugin.rb +++ b/railties/lib/minitest/rails_plugin.rb @@ -43,10 +43,15 @@ module Minitest Minitest.backtrace_filter = ::Rails.backtrace_cleaner if ::Rails.respond_to?(:backtrace_cleaner) end + # Suppress summary reports when outputting inline rerun snippets. + if reporter.reporters.reject! { |reporter| reporter.kind_of?(SummaryReporter) } + reporter << SuppressedSummaryReporter.new(options[:io], options) + end + # Replace progress reporter for colors. - reporter.reporters.delete_if { |reporter| reporter.kind_of?(SummaryReporter) || reporter.kind_of?(ProgressReporter) } - reporter << SuppressedSummaryReporter.new(options[:io], options) - reporter << ::Rails::TestUnitReporter.new(options[:io], options) + if reporter.reporters.reject! { |reporter| reporter.kind_of?(ProgressReporter) } + reporter << ::Rails::TestUnitReporter.new(options[:io], options) + end end # Backwardscompatibility with Rails 5.0 generated plugin test scripts diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb index 2c00d92f8f..84595342b2 100644 --- a/railties/lib/rails/application/configuration.rb +++ b/railties/lib/rails/application/configuration.rb @@ -242,7 +242,11 @@ module Rails end def content_security_policy(&block) - @content_security_policy ||= ActionDispatch::ContentSecurityPolicy.new(&block) + if block_given? + @content_security_policy = ActionDispatch::ContentSecurityPolicy.new(&block) + else + @content_security_policy + end end class Custom #:nodoc: diff --git a/railties/lib/rails/application/default_middleware_stack.rb b/railties/lib/rails/application/default_middleware_stack.rb index 73c7defe7f..433a7ab41f 100644 --- a/railties/lib/rails/application/default_middleware_stack.rb +++ b/railties/lib/rails/application/default_middleware_stack.rb @@ -70,7 +70,8 @@ module Rails middleware.use ::Rack::Head middleware.use ::Rack::ConditionalGet middleware.use ::Rack::ETag, "no-cache" - middleware.use ::Rack::TempfileReaper + + middleware.use ::Rack::TempfileReaper unless config.api_only end end diff --git a/railties/lib/rails/command.rb b/railties/lib/rails/command.rb index 812e846837..078e9f937f 100644 --- a/railties/lib/rails/command.rb +++ b/railties/lib/rails/command.rb @@ -4,7 +4,6 @@ require "active_support" require "active_support/dependencies/autoload" require "active_support/core_ext/enumerable" require "active_support/core_ext/object/blank" -require "active_support/core_ext/hash/transform_values" require "thor" diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index 3362bf629a..d85bbfb03e 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "active_support/core_ext/string/strip" + module Rails module Generators module Actions diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb index 863a914912..e1889979d7 100644 --- a/railties/lib/rails/generators/app_base.rb +++ b/railties/lib/rails/generators/app_base.rb @@ -2,7 +2,6 @@ require "fileutils" require "digest/md5" -require "active_support/core_ext/string/strip" require "rails/version" unless defined?(Rails::VERSION) require "open-uri" require "uri" diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb index 98fcc95964..d6732f8ff1 100644 --- a/railties/lib/rails/generators/named_base.rb +++ b/railties/lib/rails/generators/named_base.rb @@ -31,12 +31,8 @@ module Rails end end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - attr_reader :file_name - private + attr_reader :file_name # FIXME: We are avoiding to use alias because a bug on thor that make # this method public and add it to the task list. diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb index 72b9044858..5ee9ae05e3 100644 --- a/railties/lib/rails/generators/rails/app/app_generator.rb +++ b/railties/lib/rails/generators/rails/app/app_generator.rb @@ -248,7 +248,7 @@ module Rails RESERVED_NAMES = %w[application destroy plugin runner test] class AppGenerator < AppBase # :nodoc: - WEBPACKS = %w( react vue angular elm ) + WEBPACKS = %w( react vue angular elm stimulus ) add_shared_options_for "application" @@ -389,9 +389,13 @@ module Rails end end - def delete_application_layout_file_if_api_option + def delete_app_views_if_api_option if options[:api] - remove_file "app/views/layouts/application.html.erb" + if options[:skip_action_mailer] + remove_dir "app/views" + else + remove_file "app/views/layouts/application.html.erb" + end end 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 52d68cc77c..c918b57eca 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 @@ -3,6 +3,13 @@ require_relative '../config/environment' require 'rails/test_help' class ActiveSupport::TestCase + # Run tests in parallel with specified workers +<% if defined?(JRUBY_VERSION) -%> + parallelize(workers: 2, with: :threads) +<%- else -%> + parallelize(workers: 2) +<% end -%> + <% unless options[:skip_active_record] -%> # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. fixtures :all diff --git a/railties/lib/rails/generators/rails/credentials/credentials_generator.rb b/railties/lib/rails/generators/rails/credentials/credentials_generator.rb index 0fb4d5fbd1..719e0c1e4c 100644 --- a/railties/lib/rails/generators/rails/credentials/credentials_generator.rb +++ b/railties/lib/rails/generators/rails/credentials/credentials_generator.rb @@ -2,7 +2,6 @@ require "rails/generators/base" require "rails/generators/rails/master_key/master_key_generator" -require "active_support/core_ext/string/strip" require "active_support/encrypted_configuration" module Rails @@ -43,13 +42,13 @@ module Rails end def credentials_template - <<-YAML.strip_heredoc - # aws: - # access_key_id: 123 - # secret_access_key: 345 + <<~YAML + # aws: + # access_key_id: 123 + # secret_access_key: 345 - # Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies. - secret_key_base: #{SecureRandom.hex(64)} + # Used as the base secret for all MessageVerifiers in Rails, including the one protecting cookies. + secret_key_base: #{SecureRandom.hex(64)} YAML end end diff --git a/railties/lib/rails/generators/rails/encrypted_file/encrypted_file_generator.rb b/railties/lib/rails/generators/rails/encrypted_file/encrypted_file_generator.rb index ef398f52a1..867e28c6db 100644 --- a/railties/lib/rails/generators/rails/encrypted_file/encrypted_file_generator.rb +++ b/railties/lib/rails/generators/rails/encrypted_file/encrypted_file_generator.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "rails/generators/base" -require "active_support/core_ext/string/strip" require "active_support/encrypted_file" module Rails @@ -16,10 +15,10 @@ module Rails private def encrypted_file_template - <<-YAML.strip_heredoc - # aws: - # access_key_id: 123 - # secret_access_key: 345 + <<~YAML + # aws: + # access_key_id: 123 + # secret_access_key: 345 YAML end diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt index abbacd9bec..b86ef0f2f8 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/app/controllers/%namespaced_name%/application_controller.rb.tt @@ -1,4 +1,4 @@ -<%= wrap_in_modules <<-rb.strip_heredoc +<%= wrap_in_modules <<~rb class ApplicationController < ActionController::#{api? ? "API" : "Base"} #{ api? ? '# ' : '' }protect_from_forgery with: :exception end diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%namespaced_name%/application_helper.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%namespaced_name%/application_helper.rb.tt index 25d692732d..be078f36de 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%namespaced_name%/application_helper.rb.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/app/helpers/%namespaced_name%/application_helper.rb.tt @@ -1,4 +1,4 @@ -<%= wrap_in_modules <<-rb.strip_heredoc +<%= wrap_in_modules <<~rb module ApplicationHelper end rb diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/jobs/%namespaced_name%/application_job.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/jobs/%namespaced_name%/application_job.rb.tt index bad1ff2d16..846863bc13 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/app/jobs/%namespaced_name%/application_job.rb.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/app/jobs/%namespaced_name%/application_job.rb.tt @@ -1,4 +1,4 @@ -<%= wrap_in_modules <<-rb.strip_heredoc +<%= wrap_in_modules <<~rb class ApplicationJob < ActiveJob::Base end rb diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/mailers/%namespaced_name%/application_mailer.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/mailers/%namespaced_name%/application_mailer.rb.tt index 09aac13f42..246e274348 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/app/mailers/%namespaced_name%/application_mailer.rb.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/app/mailers/%namespaced_name%/application_mailer.rb.tt @@ -1,4 +1,4 @@ -<%= wrap_in_modules <<-rb.strip_heredoc +<%= wrap_in_modules <<~rb class ApplicationMailer < ActionMailer::Base default from: 'from@example.com' layout 'mailer' diff --git a/railties/lib/rails/generators/rails/plugin/templates/app/models/%namespaced_name%/application_record.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/app/models/%namespaced_name%/application_record.rb.tt index 8aa3de78f1..21465278be 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/app/models/%namespaced_name%/application_record.rb.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/app/models/%namespaced_name%/application_record.rb.tt @@ -1,4 +1,4 @@ -<%= wrap_in_modules <<-rb.strip_heredoc +<%= wrap_in_modules <<~rb class ApplicationRecord < ActiveRecord::Base self.abstract_class = true end diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb.tt index 8938770fc4..4ec1804940 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/engine.rb.tt @@ -1,4 +1,4 @@ -<%= wrap_in_modules <<-rb.strip_heredoc +<%= wrap_in_modules <<~rb class Engine < ::Rails::Engine #{mountable? ? ' isolate_namespace ' + camelized_modules : ' '} #{api? ? " config.generators.api_only = true" : ' '} diff --git a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb.tt b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb.tt index 7bdf4ee5fb..b853fabcc3 100644 --- a/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb.tt +++ b/railties/lib/rails/generators/rails/plugin/templates/lib/%namespaced_name%/railtie.rb.tt @@ -1,4 +1,4 @@ -<%= wrap_in_modules <<-rb.strip_heredoc +<%= wrap_in_modules <<~rb class Railtie < ::Rails::Railtie end rb diff --git a/railties/lib/rails/generators/resource_helpers.rb b/railties/lib/rails/generators/resource_helpers.rb index a146a8fda6..5675faff70 100644 --- a/railties/lib/rails/generators/resource_helpers.rb +++ b/railties/lib/rails/generators/resource_helpers.rb @@ -25,13 +25,8 @@ module Rails assign_controller_names!(controller_name.pluralize) end - # TODO Change this to private once we've dropped Ruby 2.2 support. - # Workaround for Ruby 2.2 "private attribute?" warning. - protected - - attr_reader :controller_name, :controller_file_name - private + attr_reader :controller_name, :controller_file_name def controller_class_path if options[:model_name] diff --git a/railties/lib/rails/ruby_version_check.rb b/railties/lib/rails/ruby_version_check.rb index 76b6b80d28..f8d3311156 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 RUBY_VERSION < "2.2.2" && RUBY_ENGINE == "ruby" +if RUBY_VERSION < "2.4.1" && RUBY_ENGINE == "ruby" desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})" abort <<-end_message - Rails 5 requires Ruby 2.2.2 or newer. + Rails 6 requires Ruby 2.4.1 or newer. You're running #{desc} - Please upgrade to Ruby 2.2.2 or newer to continue. + Please upgrade to Ruby 2.4.1 or newer to continue. end_message end diff --git a/railties/lib/rails/secrets.rb b/railties/lib/rails/secrets.rb index 30e3478c9b..747cf31d7a 100644 --- a/railties/lib/rails/secrets.rb +++ b/railties/lib/rails/secrets.rb @@ -2,7 +2,6 @@ require "yaml" require "active_support/message_encryptor" -require "active_support/core_ext/string/strip" module Rails # Greatly inspired by Ara T. Howard's magnificent sekrets gem. 😘 diff --git a/railties/lib/rails/test_help.rb b/railties/lib/rails/test_help.rb index 76c28ac85e..4bd7d74b04 100644 --- a/railties/lib/rails/test_help.rb +++ b/railties/lib/rails/test_help.rb @@ -22,6 +22,7 @@ if defined?(ActiveRecord::Base) module ActiveSupport class TestCase + include ActiveRecord::TestDatabases include ActiveRecord::TestFixtures self.fixture_path = "#{Rails.root}/test/fixtures/" self.file_fixture_path = fixture_path + "files" diff --git a/railties/railties.gemspec b/railties/railties.gemspec index 4c665cd546..1df8b1fe39 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.2.2" + s.required_ruby_version = ">= 2.4.1" s.license = "MIT" diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb index 236d73d5fd..caadae3136 100644 --- a/railties/test/application/configuration_test.rb +++ b/railties/test/application/configuration_test.rb @@ -1940,6 +1940,18 @@ module ApplicationTests assert_equal Digest::SHA1, ActiveSupport::Digest.hash_digest_class end + test "custom serializers should be able to set via config.active_job.custom_serializers in an initializer" do + class ::DummySerializer < ActiveJob::Serializers::ObjectSerializer; end + + app_file "config/initializers/custom_serializers.rb", <<-RUBY + Rails.application.config.active_job.custom_serializers << DummySerializer + RUBY + + app "development" + + assert_includes ActiveJob::Serializers.serializers, DummySerializer + end + private def force_lazy_load_hooks yield # Tasty clarifying sugar, homie! We only need to reference a constant to load it. diff --git a/railties/test/application/content_security_policy_test.rb b/railties/test/application/content_security_policy_test.rb index 97f2957c33..0d28df16f8 100644 --- a/railties/test/application/content_security_policy_test.rb +++ b/railties/test/application/content_security_policy_test.rb @@ -16,7 +16,7 @@ module ApplicationTests teardown_app end - test "default content security policy is empty" do + test "default content security policy is nil" do controller :pages, <<-RUBY class PagesController < ApplicationController def index @@ -34,7 +34,33 @@ module ApplicationTests app("development") get "/" - assert_equal ";", last_response.headers["Content-Security-Policy"] + assert_nil last_response.headers["Content-Security-Policy"] + end + + test "empty content security policy is generated" do + controller :pages, <<-RUBY + class PagesController < ApplicationController + def index + render html: "<h1>Welcome to Rails!</h1>" + end + end + RUBY + + app_file "config/initializers/content_security_policy.rb", <<-RUBY + Rails.application.config.content_security_policy do |p| + end + RUBY + + app_file "config/routes.rb", <<-RUBY + Rails.application.routes.draw do + root to: "pages#index" + end + RUBY + + app("development") + + get "/" + assert_policy "" end test "global content security policy in an initializer" do @@ -61,7 +87,7 @@ module ApplicationTests app("development") get "/" - assert_policy "default-src 'self' https:;" + assert_policy "default-src 'self' https:" end test "global report only content security policy in an initializer" do @@ -90,7 +116,7 @@ module ApplicationTests app("development") get "/" - assert_policy "default-src 'self' https:;", report_only: true + assert_policy "default-src 'self' https:", report_only: true end test "override content security policy in a controller" do @@ -121,7 +147,7 @@ module ApplicationTests app("development") get "/" - assert_policy "default-src https://example.com;" + assert_policy "default-src https://example.com" end test "override content security policy to report only in a controller" do @@ -150,7 +176,7 @@ module ApplicationTests app("development") get "/" - assert_policy "default-src 'self' https:;", report_only: true + assert_policy "default-src 'self' https:", report_only: true end test "global content security policy added to rack app" do @@ -174,7 +200,7 @@ module ApplicationTests app("development") get "/" - assert_policy "default-src 'self' https:;" + assert_policy "default-src 'self' https:" end private diff --git a/railties/test/application/middleware_test.rb b/railties/test/application/middleware_test.rb index b9e4a9ccc0..5efaf841d4 100644 --- a/railties/test/application/middleware_test.rb +++ b/railties/test/application/middleware_test.rb @@ -70,8 +70,7 @@ module ApplicationTests "ActionDispatch::Callbacks", "Rack::Head", "Rack::ConditionalGet", - "Rack::ETag", - "Rack::TempfileReaper" + "Rack::ETag" ], middleware end diff --git a/railties/test/application/rake_test.rb b/railties/test/application/rake_test.rb index 5a6404bd0a..d134febaf4 100644 --- a/railties/test/application/rake_test.rb +++ b/railties/test/application/rake_test.rb @@ -2,7 +2,6 @@ require "isolation/abstract_unit" require "env_helpers" -require "active_support/core_ext/string/strip" module ApplicationTests class RakeTest < ActiveSupport::TestCase @@ -131,7 +130,7 @@ module ApplicationTests RUBY output = rails("routes") - assert_equal <<-MESSAGE.strip_heredoc, output + assert_equal <<~MESSAGE, output Prefix Verb URI Pattern Controller#Action cart GET /cart(.:format) cart#show rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show @@ -173,7 +172,7 @@ module ApplicationTests RUBY output = rails("routes", "-g", "show") - assert_equal <<-MESSAGE.strip_heredoc, output + assert_equal <<~MESSAGE, output Prefix Verb URI Pattern Controller#Action cart GET /cart(.:format) cart#show rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show @@ -183,7 +182,7 @@ module ApplicationTests MESSAGE output = rails("routes", "-g", "POST") - assert_equal <<-MESSAGE.strip_heredoc, output + assert_equal <<~MESSAGE, output Prefix Verb URI Pattern Controller#Action POST /cart(.:format) cart#create rails_direct_uploads POST /rails/active_storage/direct_uploads(.:format) active_storage/direct_uploads#create @@ -242,7 +241,7 @@ module ApplicationTests end RUBY - assert_equal <<-MESSAGE.strip_heredoc, rails("routes") + assert_equal <<~MESSAGE, rails("routes") Prefix Verb URI Pattern Controller#Action rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show rails_blob_variation GET /rails/active_storage/variants/:signed_blob_id/:variation_key/*filename(.:format) active_storage/variants#show @@ -262,7 +261,7 @@ module ApplicationTests output = Dir.chdir(app_path) { `bin/rake --rakefile Rakefile routes` } - assert_equal <<-MESSAGE.strip_heredoc, output + assert_equal <<~MESSAGE, output Prefix Verb URI Pattern Controller#Action cart GET /cart(.:format) cart#show rails_service_blob GET /rails/active_storage/blobs/:signed_id/*filename(.:format) active_storage/blobs#show diff --git a/railties/test/application/test_runner_test.rb b/railties/test/application/test_runner_test.rb index a01325fdb8..8e5ccf94cc 100644 --- a/railties/test/application/test_runner_test.rb +++ b/railties/test/application/test_runner_test.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true require "isolation/abstract_unit" -require "active_support/core_ext/string/strip" require "env_helpers" module ApplicationTests @@ -502,10 +501,10 @@ module ApplicationTests end def test_output_inline_by_default - create_test_file :models, "post", pass: false + create_test_file :models, "post", pass: false, print: false output = run_test_command("test/models/post_test.rb") - expect = %r{Running:\n\nPostTest\nF\n\nFailure:\nPostTest#test_truth \[[^\]]+test/models/post_test.rb:6\]:\nwups!\n\nbin/rails test test/models/post_test.rb:4\n\n\n\n} + expect = %r{Running:\n\nF\n\nFailure:\nPostTest#test_truth \[[^\]]+test/models/post_test.rb:6\]:\nwups!\n\nbin/rails test test/models/post_test.rb:4\n\n\n\n} assert_match expect, output end @@ -523,6 +522,28 @@ module ApplicationTests capture(:stderr) { run_test_command("test/models/post_test.rb --fail-fast", stderr: true) }) end + def test_run_in_parallel_with_processes + file_name = create_parallel_processes_test_file + + output = run_test_command(file_name) + + assert_match %r{Finished in.*\n2 runs, 2 assertions}, output + 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 + + file_name = create_parallel_threads_test_file + + output = run_test_command(file_name) + + assert_match %r{Finished in.*\n2 runs, 2 assertions}, output + end + def test_raise_error_when_specified_file_does_not_exist error = capture(:stderr) { run_test_command("test/not_exists.rb", stderr: true) } assert_match(%r{cannot load such file.+test/not_exists\.rb}, error) @@ -718,7 +739,7 @@ module ApplicationTests def create_model_with_fixture rails "generate", "model", "user", "name:string" - app_file "test/fixtures/users.yml", <<-YAML.strip_heredoc + app_file "test/fixtures/users.yml", <<~YAML vampire: id: 1 name: Koyomi Araragi @@ -800,19 +821,70 @@ module ApplicationTests RUBY end - def create_test_file(path = :unit, name = "test", pass: true) + def create_test_file(path = :unit, name = "test", pass: true, print: true) app_file "test/#{path}/#{name}_test.rb", <<-RUBY require 'test_helper' class #{name.camelize}Test < ActiveSupport::TestCase def test_truth - puts "#{name.camelize}Test" + puts "#{name.camelize}Test" if #{print} assert #{pass}, 'wups!' end end RUBY end + def create_parallel_processes_test_file + app_file "test/models/parallel_test.rb", <<-RUBY + require 'test_helper' + + class ParallelTest < ActiveSupport::TestCase + RD1, WR1 = IO.pipe + RD2, WR2 = IO.pipe + + test "one" do + WR1.close + assert_equal "x", RD1.read(1) # blocks until two runs + + RD2.close + WR2.write "y" # Allow two to run + WR2.close + end + + test "two" do + RD1.close + WR1.write "x" # Allow one to run + WR1.close + + WR2.close + assert_equal "y", RD2.read(1) # blocks until one runs + end + end + RUBY + end + + def create_parallel_threads_test_file + app_file "test/models/parallel_test.rb", <<-RUBY + require 'test_helper' + + class ParallelTest < ActiveSupport::TestCase + Q1 = Queue.new + Q2 = Queue.new + test "one" do + assert_equal "x", Q1.pop # blocks until two runs + + Q2 << "y" + end + + test "two" do + Q1 << "x" + + assert_equal "y", Q2.pop # blocks until one runs + end + end + RUBY + end + def create_env_test app_file "test/unit/env_test.rb", <<-RUBY require 'test_helper' diff --git a/railties/test/application/test_test.rb b/railties/test/application/test_test.rb index 0a51e98656..fb43bebfbe 100644 --- a/railties/test/application/test_test.rb +++ b/railties/test/application/test_test.rb @@ -7,10 +7,15 @@ module ApplicationTests include ActiveSupport::Testing::Isolation def setup + @old = ENV["PARALLEL_WORKERS"] + ENV["PARALLEL_WORKERS"] = "0" + build_app end def teardown + ENV["PARALLEL_WORKERS"] = @old + teardown_app end diff --git a/railties/test/generators/api_app_generator_test.rb b/railties/test/generators/api_app_generator_test.rb index 4815cf6362..857124b23a 100644 --- a/railties/test/generators/api_app_generator_test.rb +++ b/railties/test/generators/api_app_generator_test.rb @@ -63,6 +63,23 @@ class ApiAppGeneratorTest < Rails::Generators::TestCase end end + def test_generator_if_skip_action_mailer_is_given + run_generator [destination_root, "--api", "--skip-action-mailer"] + assert_file "config/application.rb", /#\s+require\s+["']action_mailer\/railtie["']/ + assert_file "config/environments/development.rb" do |content| + assert_no_match(/config\.action_mailer/, content) + end + assert_file "config/environments/test.rb" do |content| + assert_no_match(/config\.action_mailer/, content) + end + assert_file "config/environments/production.rb" do |content| + assert_no_match(/config\.action_mailer/, content) + end + assert_no_directory "app/mailers" + assert_no_directory "test/mailers" + assert_no_directory "app/views" + end + def test_app_update_does_not_generate_unnecessary_config_files run_generator diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb index cc4a376d31..99790e602d 100644 --- a/railties/test/generators/app_generator_test.rb +++ b/railties/test/generators/app_generator_test.rb @@ -315,6 +315,15 @@ class AppGeneratorTest < Rails::Generators::TestCase assert_file "Gemfile", /^# gem 'mini_magick'/ end + def test_mini_magick_gem_when_skip_active_storage_is_given + app_root = File.join(destination_root, "myapp") + run_generator [app_root, "--skip-active-storage"] + + assert_file "#{app_root}/Gemfile" do |content| + assert_no_match(/gem 'mini_magick'/, content) + end + end + def test_app_update_does_not_generate_active_storage_contents_when_skip_active_storage_is_given app_root = File.join(destination_root, "myapp") run_generator [app_root, "--skip-active-storage"] @@ -336,10 +345,6 @@ class AppGeneratorTest < Rails::Generators::TestCase end assert_no_file "#{app_root}/config/storage.yml" - - assert_file "#{app_root}/Gemfile" do |content| - assert_no_match(/gem 'mini_magick'/, content) - end end def test_app_update_does_not_generate_active_storage_contents_when_skip_active_record_is_given @@ -363,10 +368,6 @@ class AppGeneratorTest < Rails::Generators::TestCase end assert_no_file "#{app_root}/config/storage.yml" - - assert_file "#{app_root}/Gemfile" do |content| - assert_no_match(/gem 'mini_magick'/, content) - end end def test_app_update_does_not_change_config_target_version diff --git a/railties/test/generators/model_generator_test.rb b/railties/test/generators/model_generator_test.rb index 516aa0704f..8d933e82c3 100644 --- a/railties/test/generators/model_generator_test.rb +++ b/railties/test/generators/model_generator_test.rb @@ -2,7 +2,6 @@ require "generators/generators_test_helper" require "rails/generators/rails/model/model_generator" -require "active_support/core_ext/string/strip" class ModelGeneratorTest < Rails::Generators::TestCase include GeneratorsTestHelper @@ -379,10 +378,10 @@ class ModelGeneratorTest < Rails::Generators::TestCase def test_required_belongs_to_adds_required_association run_generator ["account", "supplier:references{required}"] - expected_file = <<-FILE.strip_heredoc - class Account < ApplicationRecord - belongs_to :supplier, required: true - end + expected_file = <<~FILE + class Account < ApplicationRecord + belongs_to :supplier, required: true + end FILE assert_file "app/models/account.rb", expected_file end @@ -390,10 +389,10 @@ class ModelGeneratorTest < Rails::Generators::TestCase def test_required_polymorphic_belongs_to_generages_correct_model run_generator ["account", "supplier:references{required,polymorphic}"] - expected_file = <<-FILE.strip_heredoc - class Account < ApplicationRecord - belongs_to :supplier, polymorphic: true, required: true - end + expected_file = <<~FILE + class Account < ApplicationRecord + belongs_to :supplier, polymorphic: true, required: true + end FILE assert_file "app/models/account.rb", expected_file end @@ -401,10 +400,10 @@ class ModelGeneratorTest < Rails::Generators::TestCase def test_required_and_polymorphic_are_order_independent run_generator ["account", "supplier:references{polymorphic.required}"] - expected_file = <<-FILE.strip_heredoc - class Account < ApplicationRecord - belongs_to :supplier, polymorphic: true, required: true - end + expected_file = <<~FILE + class Account < ApplicationRecord + belongs_to :supplier, polymorphic: true, required: true + end FILE assert_file "app/models/account.rb", expected_file end @@ -452,11 +451,11 @@ class ModelGeneratorTest < Rails::Generators::TestCase def test_token_option_adds_has_secure_token run_generator ["user", "token:token", "auth_token:token"] - expected_file = <<-FILE.strip_heredoc - class User < ApplicationRecord - has_secure_token - has_secure_token :auth_token - end + expected_file = <<~FILE + class User < ApplicationRecord + has_secure_token + has_secure_token :auth_token + end FILE assert_file "app/models/user.rb", expected_file end diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb index 6568a356d6..0a4d2a9167 100644 --- a/railties/test/isolation/abstract_unit.rb +++ b/railties/test/isolation/abstract_unit.rb @@ -38,7 +38,12 @@ module TestHelpers end def app_path(*args) - tmp_path(*%w[app] + args) + path = tmp_path(*%w[app] + args) + if block_given? + yield path + else + path + end end def framework_path diff --git a/railties/test/minitest/rails_plugin_test.rb b/railties/test/minitest/rails_plugin_test.rb new file mode 100644 index 0000000000..7c3a2022a9 --- /dev/null +++ b/railties/test/minitest/rails_plugin_test.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require "abstract_unit" + +class Minitest::RailsPluginTest < ActiveSupport::TestCase + setup do + @options = Minitest.process_args [] + @output = StringIO.new("".encode("UTF-8")) + end + + test "default reporters are replaced" do + with_reporter Minitest::CompositeReporter.new do |reporter| + reporter << Minitest::SummaryReporter.new(@output, @options) + reporter << Minitest::ProgressReporter.new(@output, @options) + reporter << Minitest::Reporter.new(@output, @options) + + Minitest.plugin_rails_init({}) + + assert_equal 3, reporter.reporters.count + assert reporter.reporters.any? { |candidate| candidate.kind_of?(Minitest::SuppressedSummaryReporter) } + assert reporter.reporters.any? { |candidate| candidate.kind_of?(::Rails::TestUnitReporter) } + assert reporter.reporters.any? { |candidate| candidate.kind_of?(Minitest::Reporter) } + end + end + + test "no custom reporters are added if nothing to replace" do + with_reporter Minitest::CompositeReporter.new do |reporter| + Minitest.plugin_rails_init({}) + + assert_empty reporter.reporters + end + end + + private + def with_reporter(reporter) + old_reporter, Minitest.reporter = Minitest.reporter, reporter + + yield reporter + ensure + Minitest.reporter = old_reporter + end +end |