aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock8
-rw-r--r--actionpack/CHANGELOG.md4
-rw-r--r--actionpack/lib/action_dispatch/system_test_case.rb44
-rw-r--r--actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb11
-rw-r--r--actionpack/test/dispatch/system_testing/system_test_case_test.rb8
-rw-r--r--actionview/lib/action_view/helpers/asset_url_helper.rb2
-rw-r--r--actionview/lib/action_view/helpers/url_helper.rb12
-rw-r--r--activemodel/lib/active_model/errors.rb2
-rw-r--r--activerecord/lib/active_record/autosave_association.rb2
-rw-r--r--activerecord/lib/active_record/connection_adapters/schema_cache.rb20
-rw-r--r--activerecord/lib/active_record/database_configurations.rb30
-rw-r--r--activerecord/lib/active_record/railties/databases.rake7
-rw-r--r--activerecord/lib/arel/nodes/node.rb8
-rw-r--r--activerecord/lib/arel/visitors.rb1
-rw-r--r--activerecord/lib/arel/visitors/depth_first.rb203
-rw-r--r--activerecord/test/cases/arel/nodes/node_test.rb19
-rw-r--r--activerecord/test/cases/arel/select_manager_test.rb10
-rw-r--r--activerecord/test/cases/arel/visitors/depth_first_test.rb276
-rw-r--r--activerecord/test/cases/arel/visitors/dispatch_contamination_test.rb8
-rw-r--r--activerecord/test/cases/autosave_association_test.rb16
-rw-r--r--activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb24
-rw-r--r--activerecord/test/models/mouse.rb6
-rw-r--r--activerecord/test/models/squeak.rb6
-rw-r--r--activerecord/test/schema/schema.rb8
-rw-r--r--activestorage/lib/active_storage/service/azure_storage_service.rb4
-rw-r--r--activestorage/test/service/azure_storage_service_test.rb14
-rw-r--r--activesupport/CHANGELOG.md32
-rw-r--r--activesupport/lib/active_support/logger_thread_safe_level.rb3
-rw-r--r--activesupport/lib/active_support/secure_compare_rotator.rb52
-rw-r--r--activesupport/test/logger_test.rb44
-rw-r--r--activesupport/test/secure_compare_rotator_test.rb44
-rw-r--r--guides/source/5_2_release_notes.md2
-rw-r--r--guides/source/active_storage_overview.md6
-rw-r--r--guides/source/command_line.md2
-rw-r--r--guides/source/configuring.md2
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt4
-rw-r--r--railties/test/application/rake/dbs_test.rb16
-rw-r--r--railties/test/application/system_test_case_test.rb45
39 files changed, 421 insertions, 586 deletions
diff --git a/Gemfile b/Gemfile
index 52914b6698..3a7a564a35 100644
--- a/Gemfile
+++ b/Gemfile
@@ -9,7 +9,7 @@ gemspec
# We need a newish Rake since Active Job sets its test tasks' descriptions.
gem "rake", ">= 11.1"
-gem "capybara", ">= 2.15"
+gem "capybara", ">= 3.26"
gem "selenium-webdriver", ">= 3.141.592"
gem "rack-cache", "~> 1.2"
diff --git a/Gemfile.lock b/Gemfile.lock
index eb7dbf6877..0618bcf7ec 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -175,13 +175,13 @@ GEM
bunny (2.13.0)
amq-protocol (~> 2.3, >= 2.3.0)
byebug (10.0.2)
- capybara (3.10.1)
+ capybara (3.26.0)
addressable
mini_mime (>= 0.1.3)
nokogiri (~> 1.8)
rack (>= 1.6.0)
rack-test (>= 0.6.3)
- regexp_parser (~> 1.2)
+ regexp_parser (~> 1.5)
xpath (~> 3.2)
childprocess (1.0.1)
rake (< 13.0)
@@ -388,7 +388,7 @@ GEM
redis (4.1.1)
redis-namespace (1.6.0)
redis (>= 3.0.4)
- regexp_parser (1.3.0)
+ regexp_parser (1.6.0)
representable (3.0.4)
declarative (< 0.1.0)
declarative-option (< 0.2.0)
@@ -545,7 +545,7 @@ DEPENDENCIES
blade-sauce_labs_plugin
bootsnap (>= 1.4.4)
byebug
- capybara (>= 2.15)
+ capybara (>= 3.26)
connection_pool
dalli
delayed_job
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 9a6bd4bb45..99a4ac6845 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,3 +1,7 @@
+* System tests require Capybara 3.26 or newer.
+
+ *George Claghorn*
+
* Reduced log noise handling ActionController::RoutingErrors.
*Alberto Fernández-Capel*
diff --git a/actionpack/lib/action_dispatch/system_test_case.rb b/actionpack/lib/action_dispatch/system_test_case.rb
index 9772beb8aa..aae96975c7 100644
--- a/actionpack/lib/action_dispatch/system_test_case.rb
+++ b/actionpack/lib/action_dispatch/system_test_case.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-gem "capybara", ">= 2.15"
+gem "capybara", ">= 3.26"
require "capybara/dsl"
require "capybara/minitest"
@@ -119,11 +119,6 @@ module ActionDispatch
def initialize(*) # :nodoc:
super
self.class.driver.use
- @proxy_route = if ActionDispatch.test_app
- Class.new { include ActionDispatch.test_app.routes.url_helpers }.new
- else
- nil
- end
end
def self.start_application # :nodoc:
@@ -164,20 +159,33 @@ module ActionDispatch
driven_by :selenium
- def url_options # :nodoc:
- default_url_options.merge(host: Capybara.app_host)
- end
+ private
+ def url_helpers
+ @url_helpers ||=
+ if ActionDispatch.test_app
+ Class.new do
+ include ActionDispatch.test_app.routes.url_helpers
- def method_missing(method, *args, &block)
- if @proxy_route.respond_to?(method)
- @proxy_route.send(method, *args, &block)
- else
- super
+ def url_options
+ default_url_options.reverse_merge(host: Capybara.app_host || Capybara.current_session.server_url)
+ end
+ end.new
+ end
end
- end
- ActiveSupport.run_load_hooks(:action_dispatch_system_test_case, self)
- end
+ def method_missing(name, *args, &block)
+ if url_helpers.respond_to?(name)
+ url_helpers.public_send(name, *args, &block)
+ else
+ super
+ end
+ end
- SystemTestCase.start_application
+ def respond_to_missing?(name, include_private = false)
+ url_helpers.respond_to?(name)
+ end
+ end
end
+
+ActiveSupport.run_load_hooks :action_dispatch_system_test_case, ActionDispatch::SystemTestCase
+ActionDispatch::SystemTestCase.start_application
diff --git a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb
index 20f6a7634f..30dc21ebb9 100644
--- a/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb
+++ b/actionpack/lib/action_dispatch/system_testing/test_helpers/setup_and_teardown.rb
@@ -4,15 +4,12 @@ module ActionDispatch
module SystemTesting
module TestHelpers
module SetupAndTeardown # :nodoc:
- DEFAULT_HOST = "http://127.0.0.1"
-
def host!(host)
- Capybara.app_host = host
- end
+ ActiveSupport::Deprecation.warn \
+ "ActionDispatch::SystemTestCase#host! is deprecated with no replacement. " \
+ "Set Capybara.app_host directly or rely on Capybara's default host."
- def before_setup
- host! DEFAULT_HOST
- super
+ Capybara.app_host = host
end
def before_teardown
diff --git a/actionpack/test/dispatch/system_testing/system_test_case_test.rb b/actionpack/test/dispatch/system_testing/system_test_case_test.rb
index 3319db1665..d235f7ad89 100644
--- a/actionpack/test/dispatch/system_testing/system_test_case_test.rb
+++ b/actionpack/test/dispatch/system_testing/system_test_case_test.rb
@@ -36,12 +36,10 @@ class SetDriverToSeleniumHeadlessFirefoxTest < DrivenBySeleniumWithHeadlessFiref
end
class SetHostTest < DrivenByRackTest
- test "sets default host" do
- assert_equal "http://127.0.0.1", Capybara.app_host
- end
-
test "overrides host" do
- host! "http://example.com"
+ assert_deprecated do
+ host! "http://example.com"
+ end
assert_equal "http://example.com", Capybara.app_host
end
diff --git a/actionview/lib/action_view/helpers/asset_url_helper.rb b/actionview/lib/action_view/helpers/asset_url_helper.rb
index cc62783d60..295f945325 100644
--- a/actionview/lib/action_view/helpers/asset_url_helper.rb
+++ b/actionview/lib/action_view/helpers/asset_url_helper.rb
@@ -133,6 +133,8 @@ module ActionView
# which is implemented by sprockets-rails.
#
# asset_path("application.js") # => "/assets/application-60aa4fdc5cea14baf5400fba1abf4f2a46a5166bad4772b1effe341570f07de9.js"
+ # asset_path('application.js', host: 'example.com') # => "//example.com/assets/application.js"
+ # asset_path("application.js", host: 'example.com', protocol: 'https') # => "https://example.com/assets/application.js"
#
# === Without the asset pipeline (<tt>skip_pipeline: true</tt>)
#
diff --git a/actionview/lib/action_view/helpers/url_helper.rb b/actionview/lib/action_view/helpers/url_helper.rb
index 3df2eaf079..85fd549177 100644
--- a/actionview/lib/action_view/helpers/url_helper.rb
+++ b/actionview/lib/action_view/helpers/url_helper.rb
@@ -60,8 +60,8 @@ module ActionView
# Creates an anchor element of the given +name+ using a URL created by the set of +options+.
# See the valid options in the documentation for +url_for+. It's also possible to
- # pass a String instead of an options hash, which generates an anchor element that uses the
- # value of the String as the href for the link. Using a <tt>:back</tt> Symbol instead
+ # pass a \String instead of an options hash, which generates an anchor element that uses the
+ # value of the \String as the href for the link. Using a <tt>:back</tt> \Symbol instead
# of an options hash will generate a link to the referrer (a JavaScript back link
# will be used in place of a referrer if none exists). If +nil+ is passed as the name
# the value of the link itself will become the name.
@@ -226,7 +226,7 @@ module ActionView
# The +options+ hash accepts the same options as +url_for+.
#
# There are a few special +html_options+:
- # * <tt>:method</tt> - Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>,
+ # * <tt>:method</tt> - \Symbol of HTTP verb. Supported verbs are <tt>:post</tt>, <tt>:get</tt>,
# <tt>:delete</tt>, <tt>:patch</tt>, and <tt>:put</tt>. By default it will be <tt>:post</tt>.
# * <tt>:disabled</tt> - If set to true, it will generate a disabled button.
# * <tt>:data</tt> - This option can be used to add custom data attributes.
@@ -235,7 +235,7 @@ module ActionView
# * <tt>:form</tt> - This hash will be form attributes
# * <tt>:form_class</tt> - This controls the class of the form within which the submit button will
# be placed
- # * <tt>:params</tt> - Hash of parameters to be rendered as hidden fields within the form.
+ # * <tt>:params</tt> - \Hash of parameters to be rendered as hidden fields within the form.
#
# ==== Data attributes
#
@@ -576,7 +576,7 @@ module ActionView
# HTML attributes for the link can be passed in +html_options+.
#
# When clicked, an SMS message is prepopulated with the passed phone number
- # and optional +body+ value.
+ # and optional +body+ value.
#
# +sms_to+ has a +body+ option for customizing the SMS message itself by
# passing special keys to +html_options+.
@@ -595,7 +595,7 @@ module ActionView
# body: "Hello Jim I have a question about your product."
# # => <a href="sms:5155555785;?body=Hello%20Jim%20I%20have%20a%20question%20about%20your%20product">Text me</a>
#
- # You can use a block as well if your link target is hard to fit into the name parameter. ERB example:
+ # You can use a block as well if your link target is hard to fit into the name parameter. \ERB example:
#
# <%= sms_to "5155555785" do %>
# <strong>Text me:</strong>
diff --git a/activemodel/lib/active_model/errors.rb b/activemodel/lib/active_model/errors.rb
index 480f24183a..42c004ce31 100644
--- a/activemodel/lib/active_model/errors.rb
+++ b/activemodel/lib/active_model/errors.rb
@@ -307,7 +307,7 @@ module ActiveModel
ActiveSupport::Deprecation.warn(<<~EOM)
ActiveModel::Errors#to_h is deprecated and will be removed in Rails 6.2
Please use `ActiveModel::Errors.to_hash` instead. The values in the hash
- returned by `ActiveModel::Erors.to_hash` is an array of error messages.
+ returned by `ActiveModel::Errors.to_hash` is an array of error messages.
EOM
to_hash.transform_values { |values| values.last }
diff --git a/activerecord/lib/active_record/autosave_association.rb b/activerecord/lib/active_record/autosave_association.rb
index 94d8134b55..734ebb45ae 100644
--- a/activerecord/lib/active_record/autosave_association.rb
+++ b/activerecord/lib/active_record/autosave_association.rb
@@ -302,7 +302,7 @@ module ActiveRecord
def validate_single_association(reflection)
association = association_instance_get(reflection.name)
record = association && association.reader
- association_valid?(reflection, record) if record
+ association_valid?(reflection, record) if record && record.changed_for_autosave?
end
# Validate the associated records if <tt>:validate</tt> or
diff --git a/activerecord/lib/active_record/connection_adapters/schema_cache.rb b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
index 7d54fcf9a0..5e30304864 100644
--- a/activerecord/lib/active_record/connection_adapters/schema_cache.rb
+++ b/activerecord/lib/active_record/connection_adapters/schema_cache.rb
@@ -46,7 +46,11 @@ module ActiveRecord
end
def primary_keys(table_name)
- @primary_keys[table_name] ||= data_source_exists?(table_name) ? connection.primary_key(table_name) : nil
+ @primary_keys.fetch(table_name) do
+ if data_source_exists?(table_name)
+ @primary_keys[deep_deduplicate(table_name)] = deep_deduplicate(connection.primary_key(table_name))
+ end
+ end
end
# A cached lookup for table existence.
@@ -54,7 +58,7 @@ module ActiveRecord
prepare_data_sources if @data_sources.empty?
return @data_sources[name] if @data_sources.key? name
- @data_sources[name] = connection.data_source_exists?(name)
+ @data_sources[deep_deduplicate(name)] = connection.data_source_exists?(name)
end
# Add internal cache for table with +table_name+.
@@ -73,13 +77,17 @@ module ActiveRecord
# Get the columns for a table
def columns(table_name)
- @columns[table_name] ||= connection.columns(table_name)
+ @columns.fetch(table_name) do
+ @columns[deep_deduplicate(table_name)] = deep_deduplicate(connection.columns(table_name))
+ end
end
# Get the columns for a table as a hash, key is the column name
# value is the column object.
def columns_hash(table_name)
- @columns_hash[table_name] ||= columns(table_name).index_by(&:name)
+ @columns_hash.fetch(table_name) do
+ @columns_hash[deep_deduplicate(table_name)] = columns(table_name).index_by(&:name)
+ end
end
# Checks whether the columns hash is already cached for a table.
@@ -88,7 +96,9 @@ module ActiveRecord
end
def indexes(table_name)
- @indexes[table_name] ||= connection.indexes(table_name)
+ @indexes.fetch(table_name) do
+ @indexes[deep_deduplicate(table_name)] = deep_deduplicate(connection.indexes(table_name))
+ end
end
def database_version # :nodoc:
diff --git a/activerecord/lib/active_record/database_configurations.rb b/activerecord/lib/active_record/database_configurations.rb
index bf31bb7c22..e122628b05 100644
--- a/activerecord/lib/active_record/database_configurations.rb
+++ b/activerecord/lib/active_record/database_configurations.rb
@@ -104,18 +104,28 @@ module ActiveRecord
return configs.configurations if configs.is_a?(DatabaseConfigurations)
return configs if configs.is_a?(Array)
- build_db_config = configs.each_pair.flat_map do |env_name, config|
- walk_configs(env_name.to_s, "primary", config)
- end.flatten.compact
+ db_configs = configs.flat_map do |env_name, config|
+ if config.is_a?(Hash) && config.all? { |k, v| v.is_a?(Hash) }
+ walk_configs(env_name.to_s, config)
+ else
+ build_db_config_from_raw_config(env_name.to_s, "primary", config)
+ end
+ end.compact
if url = ENV["DATABASE_URL"]
- build_url_config(url, build_db_config)
+ merge_url_with_configs(url, db_configs)
else
- build_db_config
+ db_configs
+ end
+ end
+
+ def walk_configs(env_name, config)
+ config.map do |spec_name, sub_config|
+ build_db_config_from_raw_config(env_name, spec_name.to_s, sub_config)
end
end
- def walk_configs(env_name, spec_name, config)
+ def build_db_config_from_raw_config(env_name, spec_name, config)
case config
when String
build_db_config_from_string(env_name, spec_name, config)
@@ -141,16 +151,12 @@ module ActiveRecord
config_without_url.delete "url"
ActiveRecord::DatabaseConfigurations::UrlConfig.new(env_name, spec_name, url, config_without_url)
- elsif config["database"] || config["adapter"] || ENV["DATABASE_URL"]
- ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
else
- config.each_pair.map do |sub_spec_name, sub_config|
- walk_configs(env_name, sub_spec_name, sub_config)
- end
+ ActiveRecord::DatabaseConfigurations::HashConfig.new(env_name, spec_name, config)
end
end
- def build_url_config(url, configs)
+ def merge_url_with_configs(url, configs)
env = ActiveRecord::ConnectionHandling::DEFAULT_ENV.call.to_s
if configs.find(&:for_current_env?)
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index 98f57549a5..4d9acc911b 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -297,10 +297,11 @@ db_namespace = namespace :db do
ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
ActiveRecord::Base.establish_connection(db_config.config)
- ActiveRecord::Tasks::DatabaseTasks.migrate
-
# Skipped when no database
- ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config.config, ActiveRecord::Base.schema_format, db_config.spec_name)
+ ActiveRecord::Tasks::DatabaseTasks.migrate
+ if ActiveRecord::Base.dump_schema_after_migration
+ ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config.config, ActiveRecord::Base.schema_format, db_config.spec_name)
+ end
rescue ActiveRecord::NoDatabaseError
ActiveRecord::Tasks::DatabaseTasks.create_current(db_config.env_name, db_config.spec_name)
diff --git a/activerecord/lib/arel/nodes/node.rb b/activerecord/lib/arel/nodes/node.rb
index 8086102bde..0416ff58de 100644
--- a/activerecord/lib/arel/nodes/node.rb
+++ b/activerecord/lib/arel/nodes/node.rb
@@ -6,7 +6,6 @@ module Arel # :nodoc: all
# Abstract base class for all AST nodes
class Node
include Arel::FactoryMethods
- include Enumerable
###
# Factory method to create a Nodes::Not node that has the recipient of
@@ -38,13 +37,6 @@ module Arel # :nodoc: all
collector = engine.connection.visitor.accept self, collector
collector.value
end
-
- # Iterate through AST, nodes will be yielded depth-first
- def each(&block)
- return enum_for(:each) unless block_given?
-
- ::Arel::Visitors::DepthFirst.new(block).accept self
- end
end
end
end
diff --git a/activerecord/lib/arel/visitors.rb b/activerecord/lib/arel/visitors.rb
index e350f52e65..a1097f6750 100644
--- a/activerecord/lib/arel/visitors.rb
+++ b/activerecord/lib/arel/visitors.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require "arel/visitors/visitor"
-require "arel/visitors/depth_first"
require "arel/visitors/to_sql"
require "arel/visitors/sqlite"
require "arel/visitors/postgresql"
diff --git a/activerecord/lib/arel/visitors/depth_first.rb b/activerecord/lib/arel/visitors/depth_first.rb
deleted file mode 100644
index 98c3f92cf1..0000000000
--- a/activerecord/lib/arel/visitors/depth_first.rb
+++ /dev/null
@@ -1,203 +0,0 @@
-# frozen_string_literal: true
-
-module Arel # :nodoc: all
- module Visitors
- class DepthFirst < Arel::Visitors::Visitor
- def initialize(block = nil)
- @block = block || Proc.new
- super()
- end
-
- private
- def visit(o, _ = nil)
- super
- @block.call o
- end
-
- def unary(o)
- visit o.expr
- end
- alias :visit_Arel_Nodes_Else :unary
- alias :visit_Arel_Nodes_Group :unary
- alias :visit_Arel_Nodes_Cube :unary
- alias :visit_Arel_Nodes_RollUp :unary
- alias :visit_Arel_Nodes_GroupingSet :unary
- alias :visit_Arel_Nodes_GroupingElement :unary
- alias :visit_Arel_Nodes_Grouping :unary
- alias :visit_Arel_Nodes_Having :unary
- alias :visit_Arel_Nodes_Lateral :unary
- alias :visit_Arel_Nodes_Limit :unary
- alias :visit_Arel_Nodes_Not :unary
- alias :visit_Arel_Nodes_Offset :unary
- alias :visit_Arel_Nodes_On :unary
- alias :visit_Arel_Nodes_Ordering :unary
- alias :visit_Arel_Nodes_Ascending :unary
- alias :visit_Arel_Nodes_Descending :unary
- alias :visit_Arel_Nodes_UnqualifiedColumn :unary
- alias :visit_Arel_Nodes_OptimizerHints :unary
- alias :visit_Arel_Nodes_ValuesList :unary
-
- def function(o)
- visit o.expressions
- visit o.alias
- visit o.distinct
- end
- alias :visit_Arel_Nodes_Avg :function
- alias :visit_Arel_Nodes_Exists :function
- alias :visit_Arel_Nodes_Max :function
- alias :visit_Arel_Nodes_Min :function
- alias :visit_Arel_Nodes_Sum :function
-
- def visit_Arel_Nodes_NamedFunction(o)
- visit o.name
- visit o.expressions
- visit o.distinct
- visit o.alias
- end
-
- def visit_Arel_Nodes_Count(o)
- visit o.expressions
- visit o.alias
- visit o.distinct
- end
-
- def visit_Arel_Nodes_Case(o)
- visit o.case
- visit o.conditions
- visit o.default
- end
-
- def nary(o)
- o.children.each { |child| visit child }
- end
- alias :visit_Arel_Nodes_And :nary
-
- def binary(o)
- visit o.left
- visit o.right
- end
- alias :visit_Arel_Nodes_As :binary
- alias :visit_Arel_Nodes_Assignment :binary
- alias :visit_Arel_Nodes_Between :binary
- alias :visit_Arel_Nodes_Concat :binary
- alias :visit_Arel_Nodes_DeleteStatement :binary
- alias :visit_Arel_Nodes_DoesNotMatch :binary
- alias :visit_Arel_Nodes_Equality :binary
- alias :visit_Arel_Nodes_FullOuterJoin :binary
- alias :visit_Arel_Nodes_GreaterThan :binary
- alias :visit_Arel_Nodes_GreaterThanOrEqual :binary
- alias :visit_Arel_Nodes_In :binary
- alias :visit_Arel_Nodes_InfixOperation :binary
- alias :visit_Arel_Nodes_JoinSource :binary
- alias :visit_Arel_Nodes_InnerJoin :binary
- alias :visit_Arel_Nodes_LessThan :binary
- alias :visit_Arel_Nodes_LessThanOrEqual :binary
- alias :visit_Arel_Nodes_Matches :binary
- alias :visit_Arel_Nodes_NotEqual :binary
- alias :visit_Arel_Nodes_NotIn :binary
- alias :visit_Arel_Nodes_NotRegexp :binary
- alias :visit_Arel_Nodes_IsNotDistinctFrom :binary
- alias :visit_Arel_Nodes_IsDistinctFrom :binary
- alias :visit_Arel_Nodes_Or :binary
- alias :visit_Arel_Nodes_OuterJoin :binary
- alias :visit_Arel_Nodes_Regexp :binary
- alias :visit_Arel_Nodes_RightOuterJoin :binary
- alias :visit_Arel_Nodes_TableAlias :binary
- alias :visit_Arel_Nodes_When :binary
-
- def visit_Arel_Nodes_StringJoin(o)
- visit o.left
- end
-
- def visit_Arel_Attribute(o)
- visit o.relation
- visit o.name
- end
- alias :visit_Arel_Attributes_Integer :visit_Arel_Attribute
- alias :visit_Arel_Attributes_Float :visit_Arel_Attribute
- alias :visit_Arel_Attributes_String :visit_Arel_Attribute
- alias :visit_Arel_Attributes_Time :visit_Arel_Attribute
- alias :visit_Arel_Attributes_Boolean :visit_Arel_Attribute
- alias :visit_Arel_Attributes_Attribute :visit_Arel_Attribute
- alias :visit_Arel_Attributes_Decimal :visit_Arel_Attribute
-
- def visit_Arel_Table(o)
- visit o.name
- end
-
- def terminal(o)
- end
- alias :visit_ActiveSupport_Multibyte_Chars :terminal
- alias :visit_ActiveSupport_StringInquirer :terminal
- alias :visit_Arel_Nodes_Lock :terminal
- alias :visit_Arel_Nodes_Node :terminal
- alias :visit_Arel_Nodes_SqlLiteral :terminal
- alias :visit_Arel_Nodes_BindParam :terminal
- alias :visit_Arel_Nodes_Window :terminal
- alias :visit_Arel_Nodes_True :terminal
- alias :visit_Arel_Nodes_False :terminal
- alias :visit_BigDecimal :terminal
- alias :visit_Class :terminal
- alias :visit_Date :terminal
- alias :visit_DateTime :terminal
- alias :visit_FalseClass :terminal
- alias :visit_Float :terminal
- alias :visit_Integer :terminal
- alias :visit_NilClass :terminal
- alias :visit_String :terminal
- alias :visit_Symbol :terminal
- alias :visit_Time :terminal
- alias :visit_TrueClass :terminal
-
- def visit_Arel_Nodes_InsertStatement(o)
- visit o.relation
- visit o.columns
- visit o.values
- end
-
- def visit_Arel_Nodes_SelectCore(o)
- visit o.projections
- visit o.source
- visit o.wheres
- visit o.groups
- visit o.windows
- visit o.havings
- end
-
- def visit_Arel_Nodes_SelectStatement(o)
- visit o.cores
- visit o.orders
- visit o.limit
- visit o.lock
- visit o.offset
- end
-
- def visit_Arel_Nodes_UpdateStatement(o)
- visit o.relation
- visit o.values
- visit o.wheres
- visit o.orders
- visit o.limit
- end
-
- def visit_Arel_Nodes_Comment(o)
- visit o.values
- end
-
- def visit_Array(o)
- o.each { |i| visit i }
- end
- alias :visit_Set :visit_Array
-
- def visit_Hash(o)
- o.each { |k, v| visit(k); visit(v) }
- end
-
- DISPATCH = dispatch_cache
-
- def get_dispatch_cache
- DISPATCH
- end
- end
- end
-end
diff --git a/activerecord/test/cases/arel/nodes/node_test.rb b/activerecord/test/cases/arel/nodes/node_test.rb
index f4f07ef2c5..f1e0ce1ea9 100644
--- a/activerecord/test/cases/arel/nodes/node_test.rb
+++ b/activerecord/test/cases/arel/nodes/node_test.rb
@@ -18,24 +18,5 @@ module Arel
assert klass.ancestors.include?(Nodes::Node), klass.name
end
end
-
- def test_each
- list = []
- node = Nodes::Node.new
- node.each { |n| list << n }
- assert_equal [node], list
- end
-
- def test_generator
- list = []
- node = Nodes::Node.new
- node.each.each { |n| list << n }
- assert_equal [node], list
- end
-
- def test_enumerable
- node = Nodes::Node.new
- assert_kind_of Enumerable, node
- end
end
end
diff --git a/activerecord/test/cases/arel/select_manager_test.rb b/activerecord/test/cases/arel/select_manager_test.rb
index e6c49cd429..526fe6787a 100644
--- a/activerecord/test/cases/arel/select_manager_test.rb
+++ b/activerecord/test/cases/arel/select_manager_test.rb
@@ -369,16 +369,6 @@ module Arel
mgr = table.from
assert mgr.ast
end
-
- it "should allow orders to work when the ast is grepped" do
- table = Table.new :users
- mgr = table.from
- mgr.project Arel.sql "*"
- mgr.from table
- mgr.orders << Arel::Nodes::Ascending.new(Arel.sql("foo"))
- mgr.ast.grep(Arel::Nodes::OuterJoin)
- mgr.to_sql.must_be_like %{ SELECT * FROM "users" ORDER BY foo ASC }
- end
end
describe "taken" do
diff --git a/activerecord/test/cases/arel/visitors/depth_first_test.rb b/activerecord/test/cases/arel/visitors/depth_first_test.rb
deleted file mode 100644
index 106be2311d..0000000000
--- a/activerecord/test/cases/arel/visitors/depth_first_test.rb
+++ /dev/null
@@ -1,276 +0,0 @@
-# frozen_string_literal: true
-
-require_relative "../helper"
-
-module Arel
- module Visitors
- class TestDepthFirst < Arel::Test
- Collector = Struct.new(:calls) do
- def call(object)
- calls << object
- end
- end
-
- def setup
- @collector = Collector.new []
- @visitor = Visitors::DepthFirst.new @collector
- end
-
- def test_raises_with_object
- assert_raises(TypeError) do
- @visitor.accept(Object.new)
- end
- end
-
-
- # unary ops
- [
- Arel::Nodes::Not,
- Arel::Nodes::Group,
- Arel::Nodes::On,
- Arel::Nodes::Grouping,
- Arel::Nodes::Offset,
- Arel::Nodes::Ordering,
- Arel::Nodes::StringJoin,
- Arel::Nodes::UnqualifiedColumn,
- Arel::Nodes::ValuesList,
- Arel::Nodes::Limit,
- Arel::Nodes::Else,
- ].each do |klass|
- define_method("test_#{klass.name.gsub('::', '_')}") do
- op = klass.new(:a)
- @visitor.accept op
- assert_equal [:a, op], @collector.calls
- end
- end
-
- # functions
- [
- Arel::Nodes::Exists,
- Arel::Nodes::Avg,
- Arel::Nodes::Min,
- Arel::Nodes::Max,
- Arel::Nodes::Sum,
- ].each do |klass|
- define_method("test_#{klass.name.gsub('::', '_')}") do
- func = klass.new(:a, "b")
- @visitor.accept func
- assert_equal [:a, "b", false, func], @collector.calls
- end
- end
-
- def test_named_function
- func = Arel::Nodes::NamedFunction.new(:a, :b, "c")
- @visitor.accept func
- assert_equal [:a, :b, false, "c", func], @collector.calls
- end
-
- def test_lock
- lock = Nodes::Lock.new true
- @visitor.accept lock
- assert_equal [lock], @collector.calls
- end
-
- def test_count
- count = Nodes::Count.new :a, :b, "c"
- @visitor.accept count
- assert_equal [:a, "c", :b, count], @collector.calls
- end
-
- def test_inner_join
- join = Nodes::InnerJoin.new :a, :b
- @visitor.accept join
- assert_equal [:a, :b, join], @collector.calls
- end
-
- def test_full_outer_join
- join = Nodes::FullOuterJoin.new :a, :b
- @visitor.accept join
- assert_equal [:a, :b, join], @collector.calls
- end
-
- def test_outer_join
- join = Nodes::OuterJoin.new :a, :b
- @visitor.accept join
- assert_equal [:a, :b, join], @collector.calls
- end
-
- def test_right_outer_join
- join = Nodes::RightOuterJoin.new :a, :b
- @visitor.accept join
- assert_equal [:a, :b, join], @collector.calls
- end
-
- def test_comment
- comment = Nodes::Comment.new ["foo"]
- @visitor.accept comment
- assert_equal ["foo", ["foo"], comment], @collector.calls
- end
-
- [
- Arel::Nodes::Assignment,
- Arel::Nodes::Between,
- Arel::Nodes::Concat,
- Arel::Nodes::DoesNotMatch,
- Arel::Nodes::Equality,
- Arel::Nodes::GreaterThan,
- Arel::Nodes::GreaterThanOrEqual,
- Arel::Nodes::In,
- Arel::Nodes::LessThan,
- Arel::Nodes::LessThanOrEqual,
- Arel::Nodes::Matches,
- Arel::Nodes::NotEqual,
- Arel::Nodes::NotIn,
- Arel::Nodes::Or,
- Arel::Nodes::TableAlias,
- Arel::Nodes::As,
- Arel::Nodes::DeleteStatement,
- Arel::Nodes::JoinSource,
- Arel::Nodes::When,
- ].each do |klass|
- define_method("test_#{klass.name.gsub('::', '_')}") do
- binary = klass.new(:a, :b)
- @visitor.accept binary
- assert_equal [:a, :b, binary], @collector.calls
- end
- end
-
- def test_Arel_Nodes_InfixOperation
- binary = Arel::Nodes::InfixOperation.new(:o, :a, :b)
- @visitor.accept binary
- assert_equal [:a, :b, binary], @collector.calls
- end
-
- # N-ary
- [
- Arel::Nodes::And,
- ].each do |klass|
- define_method("test_#{klass.name.gsub('::', '_')}") do
- binary = klass.new([:a, :b, :c])
- @visitor.accept binary
- assert_equal [:a, :b, :c, binary], @collector.calls
- end
- end
-
- [
- Arel::Attributes::Integer,
- Arel::Attributes::Float,
- Arel::Attributes::String,
- Arel::Attributes::Time,
- Arel::Attributes::Boolean,
- Arel::Attributes::Attribute
- ].each do |klass|
- define_method("test_#{klass.name.gsub('::', '_')}") do
- binary = klass.new(:a, :b)
- @visitor.accept binary
- assert_equal [:a, :b, binary], @collector.calls
- end
- end
-
- def test_table
- relation = Arel::Table.new(:users)
- @visitor.accept relation
- assert_equal ["users", relation], @collector.calls
- end
-
- def test_array
- node = Nodes::Or.new(:a, :b)
- list = [node]
- @visitor.accept list
- assert_equal [:a, :b, node, list], @collector.calls
- end
-
- def test_set
- node = Nodes::Or.new(:a, :b)
- set = Set.new([node])
- @visitor.accept set
- assert_equal [:a, :b, node, set], @collector.calls
- end
-
- def test_hash
- node = Nodes::Or.new(:a, :b)
- hash = { node => node }
- @visitor.accept hash
- assert_equal [:a, :b, node, :a, :b, node, hash], @collector.calls
- end
-
- def test_update_statement
- stmt = Nodes::UpdateStatement.new
- stmt.relation = :a
- stmt.values << :b
- stmt.wheres << :c
- stmt.orders << :d
- stmt.limit = :e
-
- @visitor.accept stmt
- assert_equal [:a, :b, stmt.values, :c, stmt.wheres, :d, stmt.orders,
- :e, stmt], @collector.calls
- end
-
- def test_select_core
- core = Nodes::SelectCore.new
- core.projections << :a
- core.froms = :b
- core.wheres << :c
- core.groups << :d
- core.windows << :e
- core.havings << :f
-
- @visitor.accept core
- assert_equal [
- :a, core.projections,
- :b, [],
- core.source,
- :c, core.wheres,
- :d, core.groups,
- :e, core.windows,
- :f, core.havings,
- core], @collector.calls
- end
-
- def test_select_statement
- ss = Nodes::SelectStatement.new
- ss.cores.replace [:a]
- ss.orders << :b
- ss.limit = :c
- ss.lock = :d
- ss.offset = :e
-
- @visitor.accept ss
- assert_equal [
- :a, ss.cores,
- :b, ss.orders,
- :c,
- :d,
- :e,
- ss], @collector.calls
- end
-
- def test_insert_statement
- stmt = Nodes::InsertStatement.new
- stmt.relation = :a
- stmt.columns << :b
- stmt.values = :c
-
- @visitor.accept stmt
- assert_equal [:a, :b, stmt.columns, :c, stmt], @collector.calls
- end
-
- def test_case
- node = Arel::Nodes::Case.new
- node.case = :a
- node.conditions << :b
- node.default = :c
-
- @visitor.accept node
- assert_equal [:a, :b, node.conditions, :c, node], @collector.calls
- end
-
- def test_node
- node = Nodes::Node.new
- @visitor.accept node
- assert_equal [node], @collector.calls
- end
- end
- end
-end
diff --git a/activerecord/test/cases/arel/visitors/dispatch_contamination_test.rb b/activerecord/test/cases/arel/visitors/dispatch_contamination_test.rb
index a07a1a050a..36f9eb49a2 100644
--- a/activerecord/test/cases/arel/visitors/dispatch_contamination_test.rb
+++ b/activerecord/test/cases/arel/visitors/dispatch_contamination_test.rb
@@ -48,7 +48,13 @@ module Arel
node = Nodes::Union.new(Nodes::True.new, Nodes::False.new)
assert_equal "( TRUE UNION FALSE )", node.to_sql
- node.first # from Nodes::Node's Enumerable mixin
+ visitor = Class.new(Visitor) {
+ def visit_Arel_Nodes_Union(o); end
+ alias :visit_Arel_Nodes_True :visit_Arel_Nodes_Union
+ alias :visit_Arel_Nodes_False :visit_Arel_Nodes_Union
+ }.new
+
+ visitor.accept(node)
assert_equal "( TRUE UNION FALSE )", node.to_sql
end
diff --git a/activerecord/test/cases/autosave_association_test.rb b/activerecord/test/cases/autosave_association_test.rb
index 2d223a3035..3528ac045f 100644
--- a/activerecord/test/cases/autosave_association_test.rb
+++ b/activerecord/test/cases/autosave_association_test.rb
@@ -13,12 +13,14 @@ require "models/developer"
require "models/computer"
require "models/invoice"
require "models/line_item"
+require "models/mouse"
require "models/order"
require "models/parrot"
require "models/pirate"
require "models/project"
require "models/ship"
require "models/ship_part"
+require "models/squeak"
require "models/tag"
require "models/tagging"
require "models/treasure"
@@ -386,6 +388,20 @@ class TestDefaultAutosaveAssociationOnABelongsToAssociation < ActiveRecord::Test
assert_predicate auditlog, :valid?
end
+
+ def test_validation_does_not_validate_non_dirty_association_target
+ mouse = Mouse.create!(name: "Will")
+ Squeak.create!(mouse: mouse)
+
+ mouse.name = nil
+ mouse.save! validate: false
+
+ squeak = Squeak.last
+
+ assert_equal true, squeak.valid?
+ assert_equal true, squeak.mouse.present?
+ assert_equal true, squeak.valid?
+ end
end
class TestDefaultAutosaveAssociationOnAHasManyAssociationWithAcceptsNestedAttributes < ActiveRecord::TestCase
diff --git a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
index 6372abbf3f..15a045ed23 100644
--- a/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
+++ b/activerecord/test/cases/connection_adapters/merge_and_resolve_default_url_config_test.rb
@@ -323,6 +323,30 @@ module ActiveRecord
assert_equal expected, actual
end
+
+ def test_tiered_configs_with_database_url
+ ENV["DATABASE_URL"] = "postgres://localhost/foo"
+
+ config = {
+ "default_env" => {
+ "primary" => { "pool" => 5 },
+ "animals" => { "pool" => 5 }
+ }
+ }
+
+ expected = {
+ "adapter" => "postgresql",
+ "database" => "foo",
+ "host" => "localhost",
+ "pool" => 5
+ }
+
+ ["primary", "animals"].each do |spec_name|
+ configs = ActiveRecord::DatabaseConfigurations.new(config)
+ actual = configs.configs_for(env_name: "default_env", spec_name: spec_name).config
+ assert_equal expected, actual
+ end
+ end
end
end
end
diff --git a/activerecord/test/models/mouse.rb b/activerecord/test/models/mouse.rb
new file mode 100644
index 0000000000..75a55c125d
--- /dev/null
+++ b/activerecord/test/models/mouse.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class Mouse < ActiveRecord::Base
+ has_many :squeaks, autosave: true
+ validates :name, presence: true
+end
diff --git a/activerecord/test/models/squeak.rb b/activerecord/test/models/squeak.rb
new file mode 100644
index 0000000000..e0a643c238
--- /dev/null
+++ b/activerecord/test/models/squeak.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+class Squeak < ActiveRecord::Base
+ belongs_to :mouse
+ accepts_nested_attributes_for :mouse
+end
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index b6c0ae0de2..dd0ff759b6 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -563,6 +563,10 @@ ActiveRecord::Schema.define do
t.string :type
end
+ create_table :mice, force: true do |t|
+ t.string :name
+ end
+
create_table :movies, force: true, id: false do |t|
t.primary_key :movieid
t.string :name
@@ -843,6 +847,10 @@ ActiveRecord::Schema.define do
end
end
+ create_table :squeaks, force: true do |t|
+ t.integer :mouse_id
+ end
+
create_table :prisoners, force: true do |t|
t.belongs_to :ship
end
diff --git a/activestorage/lib/active_storage/service/azure_storage_service.rb b/activestorage/lib/active_storage/service/azure_storage_service.rb
index 993cc0e5f7..8d77e9b20f 100644
--- a/activestorage/lib/active_storage/service/azure_storage_service.rb
+++ b/activestorage/lib/active_storage/service/azure_storage_service.rb
@@ -17,10 +17,10 @@ module ActiveStorage
@container = container
end
- def upload(key, io, checksum: nil, **)
+ def upload(key, io, checksum: nil, content_type: nil, **)
instrument :upload, key: key, checksum: checksum do
handle_errors do
- blobs.create_block_blob(container, key, IO.try_convert(io) || io, content_md5: checksum)
+ blobs.create_block_blob(container, key, IO.try_convert(io) || io, content_md5: checksum, content_type: content_type)
end
end
end
diff --git a/activestorage/test/service/azure_storage_service_test.rb b/activestorage/test/service/azure_storage_service_test.rb
index 2b07902d07..fc7b86ccb0 100644
--- a/activestorage/test/service/azure_storage_service_test.rb
+++ b/activestorage/test/service/azure_storage_service_test.rb
@@ -9,6 +9,20 @@ if SERVICE_CONFIGURATIONS[:azure]
include ActiveStorage::Service::SharedServiceTests
+ test "upload with content_type" do
+ key = SecureRandom.base58(24)
+ data = "Foobar"
+
+ @service.upload(key, StringIO.new(data), checksum: Digest::MD5.base64digest(data), filename: ActiveStorage::Filename.new("test.txt"), content_type: "text/plain")
+
+ url = @service.url(key, expires_in: 2.minutes, disposition: :attachment, content_type: nil, filename: ActiveStorage::Filename.new("test.html"))
+ response = Net::HTTP.get_response(URI(url))
+ assert_equal "text/plain", response.content_type
+ assert_match(/attachment;.*test\.html/, response["Content-Disposition"])
+ ensure
+ @service.delete key
+ end
+
test "signed URL generation" do
url = @service.url(@key, expires_in: 5.minutes,
disposition: :inline, filename: ActiveStorage::Filename.new("avatar.png"), content_type: "image/png")
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index d56d4c22de..5d6bb7a0b3 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,35 @@
+* Make ActiveSupport::Logger Fiber-safe. Fixes #36752.
+
+ Use `Fiber.current.__id__` in `ActiveSupport::Logger#local_level=` in order
+ to make log level local to Ruby Fibers in addition to Threads.
+
+ Example:
+
+ logger = ActiveSupport::Logger.new(STDOUT)
+ logger.level = 1
+ p "Main is debug? #{logger.debug?}"
+
+ Fiber.new {
+ logger.local_level = 0
+ p "Thread is debug? #{logger.debug?}"
+ }.resume
+
+ p "Main is debug? #{logger.debug?}"
+
+ Before:
+
+ Main is debug? false
+ Thread is debug? true
+ Main is debug? true
+
+ After:
+
+ Main is debug? false
+ Thread is debug? true
+ Main is debug? false
+
+ *Alexander Varnin*
+
* Allow the `on_rotation` proc used when decrypting/verifying a message to be
passed at the constructor level.
diff --git a/activesupport/lib/active_support/logger_thread_safe_level.rb b/activesupport/lib/active_support/logger_thread_safe_level.rb
index f16c90cfc6..1775a41492 100644
--- a/activesupport/lib/active_support/logger_thread_safe_level.rb
+++ b/activesupport/lib/active_support/logger_thread_safe_level.rb
@@ -3,6 +3,7 @@
require "active_support/concern"
require "active_support/core_ext/module/attribute_accessors"
require "concurrent"
+require "fiber"
module ActiveSupport
module LoggerThreadSafeLevel # :nodoc:
@@ -28,7 +29,7 @@ module ActiveSupport
end
def local_log_id
- Thread.current.__id__
+ Fiber.current.__id__
end
def local_level
diff --git a/activesupport/lib/active_support/secure_compare_rotator.rb b/activesupport/lib/active_support/secure_compare_rotator.rb
new file mode 100644
index 0000000000..14a0aee947
--- /dev/null
+++ b/activesupport/lib/active_support/secure_compare_rotator.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+require "active_support/security_utils"
+require "active_support/messages/rotator"
+
+module ActiveSupport
+ # The ActiveSupport::SecureCompareRotator is a wrapper around +ActiveSupport::SecurityUtils.secure_compare+
+ # and allows you to rotate a previously defined value to a new one.
+ #
+ # It can be used as follow:
+ #
+ # rotator = ActiveSupport::SecureCompareRotator.new('new_production_value')
+ # rotator.rotate('previous_production_value')
+ # rotator.secure_compare!('previous_production_value')
+ #
+ # One real use case example would be to rotate a basic auth credentials:
+ #
+ # class MyController < ApplicationController
+ # def authenticate_request
+ # rotator = ActiveSupport::SecureComparerotator.new('new_password')
+ # rotator.rotate('old_password')
+ #
+ # authenticate_or_request_with_http_basic do |username, password|
+ # rotator.secure_compare!(password)
+ # rescue ActiveSupport::SecureCompareRotator::InvalidMatch
+ # false
+ # end
+ # end
+ # end
+ class SecureCompareRotator
+ include SecurityUtils
+ prepend Messages::Rotator
+
+ InvalidMatch = Class.new(StandardError)
+
+ def initialize(value, **_options)
+ @value = value
+ end
+
+ def secure_compare!(other_value, on_rotation: @rotation)
+ secure_compare(@value, other_value) ||
+ run_rotations(on_rotation) { |wrapper| wrapper.secure_compare!(other_value) } ||
+ raise(InvalidMatch)
+ end
+
+ private
+
+ def build_rotation(previous_value, _options)
+ self.class.new(previous_value)
+ end
+ end
+end
diff --git a/activesupport/test/logger_test.rb b/activesupport/test/logger_test.rb
index 160e1156b6..6f7a186022 100644
--- a/activesupport/test/logger_test.rb
+++ b/activesupport/test/logger_test.rb
@@ -258,6 +258,50 @@ class LoggerTest < ActiveSupport::TestCase
assert_level(Logger::INFO)
end
+ def test_logger_level_main_fiber_safety
+ @logger.level = Logger::INFO
+ assert_level(Logger::INFO)
+
+ fiber = Fiber.new do
+ assert_level(Logger::INFO)
+ end
+
+ @logger.silence(Logger::ERROR) do
+ assert_level(Logger::ERROR)
+ fiber.resume
+ end
+ end
+
+ def test_logger_level_local_fiber_safety
+ @logger.level = Logger::INFO
+ assert_level(Logger::INFO)
+
+ another_fiber = Fiber.new do
+ @logger.silence(Logger::ERROR) do
+ assert_level(Logger::ERROR)
+ @logger.silence(Logger::DEBUG) do
+ assert_level(Logger::DEBUG)
+ end
+ end
+
+ assert_level(Logger::INFO)
+ end
+
+ Fiber.new do
+ @logger.silence(Logger::ERROR) do
+ assert_level(Logger::ERROR)
+ @logger.silence(Logger::DEBUG) do
+ another_fiber.resume
+ assert_level(Logger::DEBUG)
+ end
+ end
+
+ assert_level(Logger::INFO)
+ end.resume
+
+ assert_level(Logger::INFO)
+ end
+
private
def level_name(level)
::Logger::Severity.constants.find do |severity|
diff --git a/activesupport/test/secure_compare_rotator_test.rb b/activesupport/test/secure_compare_rotator_test.rb
new file mode 100644
index 0000000000..8acf13e38f
--- /dev/null
+++ b/activesupport/test/secure_compare_rotator_test.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require "abstract_unit"
+require "active_support/secure_compare_rotator"
+
+class SecureCompareRotatorTest < ActiveSupport::TestCase
+ test "#secure_compare! works correctly after rotation" do
+ wrapper = ActiveSupport::SecureCompareRotator.new("old_secret")
+ wrapper.rotate("new_secret")
+
+ assert_equal(true, wrapper.secure_compare!("new_secret"))
+ end
+
+ test "#secure_compare! works correctly after multiple rotation" do
+ wrapper = ActiveSupport::SecureCompareRotator.new("old_secret")
+ wrapper.rotate("new_secret")
+ wrapper.rotate("another_secret")
+ wrapper.rotate("and_another_one")
+
+ assert_equal(true, wrapper.secure_compare!("and_another_one"))
+ end
+
+ test "#secure_compare! fails correctly when credential is not part of the rotation" do
+ wrapper = ActiveSupport::SecureCompareRotator.new("old_secret")
+ wrapper.rotate("new_secret")
+
+ assert_raises(ActiveSupport::SecureCompareRotator::InvalidMatch) do
+ wrapper.secure_compare!("different_secret")
+ end
+ end
+
+ test "#secure_compare! calls the on_rotation proc" do
+ wrapper = ActiveSupport::SecureCompareRotator.new("old_secret")
+ wrapper.rotate("new_secret")
+ wrapper.rotate("another_secret")
+ wrapper.rotate("and_another_one")
+
+ @witness = nil
+
+ assert_changes(:@witness, from: nil, to: true) do
+ assert_equal(true, wrapper.secure_compare!("and_another_one", on_rotation: -> { @witness = true }))
+ end
+ end
+end
diff --git a/guides/source/5_2_release_notes.md b/guides/source/5_2_release_notes.md
index ac247bc3f9..7aac07dbbe 100644
--- a/guides/source/5_2_release_notes.md
+++ b/guides/source/5_2_release_notes.md
@@ -326,7 +326,7 @@ Please refer to the [Changelog][action-view] for detailed changes.
select divider `option`.
([Pull Request](https://github.com/rails/rails/pull/31088))
-* Change `form_with` to generates ids by default.
+* Change `form_with` to generate ids by default.
([Commit](https://github.com/rails/rails/commit/260d6f112a0ffdbe03e6f5051504cb441c1e94cd))
* Add `preload_link_tag` helper.
diff --git a/guides/source/active_storage_overview.md b/guides/source/active_storage_overview.md
index 932a5dc2e9..54f8f5c2b5 100644
--- a/guides/source/active_storage_overview.md
+++ b/guides/source/active_storage_overview.md
@@ -43,6 +43,8 @@ tables. Use `rails db:migrate` to run the migration.
WARNING: `active_storage_attachments` is a polymorphic join table that stores your model's class name. If your model's class name changes, you will need to run a migration on this table to update the underlying `record_type` to your model's new class name.
+WARNING: If you are using UUIDs instead of integers as the primary key on your models you will need to change the column type of `record_id` for the `active_storage_attachments` table in the generated migration accordingly.
+
Declare Active Storage services in `config/storage.yml`. For each service your
application uses, provide a name and the requisite configuration. The example
below declares three services named `local`, `test`, and `amazon`:
@@ -398,6 +400,10 @@ helper allows you to set the disposition.
rails_blob_path(user.avatar, disposition: "attachment")
```
+WARNING: To prevent XSS attacks, ActiveStorage forces the Content-Disposition header
+to "attachment" for some kind of files. To change this behaviour see the
+available configuration opions in [Configuring Rails Applications](configuring.html#configuring-active-storage).
+
If you need to create a link from outside of controller/view context (Background
jobs, Cronjobs, etc.), you can access the rails_blob_path like this:
diff --git a/guides/source/command_line.md b/guides/source/command_line.md
index 4681574edd..60d0de17bc 100644
--- a/guides/source/command_line.md
+++ b/guides/source/command_line.md
@@ -601,6 +601,8 @@ The `tmp:` namespaced commands will help you clear and create the `Rails.root/tm
### Miscellaneous
+* `rails initializers` prints out all defined initializers in the order they are invoked by Rails.
+* `rails middleware` lists Rack middleware stack enabled for your app.
* `rails stats` is great for looking at statistics on your code, displaying things like KLOCs (thousands of lines of code) and your code to test ratio.
* `rails secret` will give you a pseudo-random key to use for your session secret.
* `rails time:zones:all` lists all the timezones Rails knows about.
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index ded985debe..c5d3d09bd0 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -844,6 +844,8 @@ You can find more detailed configuration options in the
* `config.active_storage.content_types_to_serve_as_binary` accepts an array of strings indicating the content types that Active Storage will always serve as an attachment, rather than inline. The default is `%w(text/html
text/javascript image/svg+xml application/postscript application/x-shockwave-flash text/xml application/xml application/xhtml+xml application/mathml+xml text/cache-manifest)`.
+* `config.active_storage.content_types_allowed_inline` accepts an array of strings indicating the content types that Active Storage allows to serve as inline. The default is `%w(image/png image/gif image/jpg image/jpeg image/vnd.adobe.photoshop image/vnd.microsoft.icon application/pdf)`.
+
* `config.active_storage.queues.analysis` accepts a symbol indicating the Active Job queue to use for analysis jobs. When this option is `nil`, analysis jobs are sent to the default Active Job queue (see `config.active_job.default_queue_name`).
```ruby
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt
index 4a994e1e7b..eea99edb65 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/filter_parameter_logging.rb.tt
@@ -1,4 +1,6 @@
# Be sure to restart your server when you modify this file.
# Configure sensitive parameters which will be filtered from the log file.
-Rails.application.config.filter_parameters += [:password]
+Rails.application.config.filter_parameters += [
+ :password, :secret, :token, :_key, :auth, :crypt, :salt, :certificate, :otp, :access, :private, :protected, :ssn
+]
diff --git a/railties/test/application/rake/dbs_test.rb b/railties/test/application/rake/dbs_test.rb
index 79c521dbf6..c9931c45a6 100644
--- a/railties/test/application/rake/dbs_test.rb
+++ b/railties/test/application/rake/dbs_test.rb
@@ -630,6 +630,22 @@ module ApplicationTests
assert_match(/CreateRecipes: migrated/, output)
end
end
+
+ test "db:prepare does not touch schema when dumping is disabled" do
+ Dir.chdir(app_path) do
+ rails "generate", "model", "book", "title:string"
+ rails "db:create", "db:migrate"
+
+ app_file "db/schema.rb", "Not touched"
+ app_file "config/initializers/disable_dumping_schema.rb", <<-RUBY
+ Rails.application.config.active_record.dump_schema_after_migration = false
+ RUBY
+
+ rails "db:prepare"
+
+ assert_equal("Not touched", File.read("db/schema.rb").strip)
+ end
+ end
end
end
end
diff --git a/railties/test/application/system_test_case_test.rb b/railties/test/application/system_test_case_test.rb
new file mode 100644
index 0000000000..d15a0d9210
--- /dev/null
+++ b/railties/test/application/system_test_case_test.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require "isolation/abstract_unit"
+require "rack/test"
+
+class SystemTestCaseTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+
+ def setup
+ build_app
+ end
+
+ def teardown
+ teardown_app
+ end
+
+ test "url helpers are delegated to a proxy class" do
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get 'foo', to: 'foo#index', as: 'test_foo'
+ end
+ RUBY
+
+ app("test")
+
+ assert_not_includes(ActionDispatch::SystemTestCase.runnable_methods, :test_foo_url)
+ end
+
+ test "system tests set the Capybara host in the url_options by default" do
+ app_file "config/routes.rb", <<-RUBY
+ Rails.application.routes.draw do
+ get 'foo', to: 'foo#index', as: 'test_foo'
+ end
+ RUBY
+
+ app("test")
+ system_test = ActionDispatch::SystemTestCase.new("my_test")
+ previous_app_host = ::Capybara.app_host
+ ::Capybara.app_host = "https://my_test_example.com"
+
+ assert_equal("https://my_test_example.com/foo", system_test.test_foo_url)
+ ensure
+ ::Capybara.app_host = previous_app_host
+ end
+end