aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/no-response.yml9
-rw-r--r--actionview/lib/action_view/helpers/form_helper.rb3
-rw-r--r--activejob/CHANGELOG.md5
-rw-r--r--activejob/lib/active_job/test_helper.rb6
-rw-r--r--activejob/test/cases/test_helper_test.rb24
-rw-r--r--activemodel/lib/active_model/dirty.rb2
-rw-r--r--activerecord/CHANGELOG.md23
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb19
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb11
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb45
-rw-r--r--activerecord/lib/active_record/core.rb41
-rw-r--r--activerecord/lib/active_record/relation/finder_methods.rb2
-rw-r--r--activerecord/lib/active_record/relation/predicate_builder/array_handler.rb5
-rw-r--r--activerecord/test/cases/associations/eager_test.rb28
-rw-r--r--activerecord/test/cases/bind_parameter_test.rb2
-rw-r--r--activerecord/test/cases/defaults_test.rb7
-rw-r--r--activerecord/test/cases/filter_attributes_test.rb56
-rw-r--r--activerecord/test/cases/finder_test.rb22
-rw-r--r--activerecord/test/cases/helper.rb9
-rw-r--r--activerecord/test/cases/schema_dumper_test.rb7
-rw-r--r--activerecord/test/cases/tasks/mysql_rake_test.rb2
-rw-r--r--activerecord/test/config.example.yml1
-rw-r--r--activerecord/test/fixtures/citations.yml1
-rw-r--r--activerecord/test/models/country.rb2
-rw-r--r--activerecord/test/models/treaty.rb2
-rw-r--r--activerecord/test/schema/mysql2_specific_schema.rb3
-rw-r--r--activerecord/test/schema/schema.rb18
-rw-r--r--activestorage/app/models/active_storage/attachment.rb5
-rw-r--r--activesupport/CHANGELOG.md4
-rw-r--r--activesupport/lib/active_support/callbacks.rb3
-rw-r--r--activesupport/lib/active_support/dependencies.rb6
-rw-r--r--activesupport/lib/active_support/parameter_filter.rb62
-rw-r--r--activesupport/lib/active_support/testing/method_call_assertions.rb2
-rw-r--r--activesupport/lib/active_support/values/time_zone.rb5
-rw-r--r--activesupport/test/autoloading_fixtures/module_folder/nested_with_require.rb8
-rw-r--r--activesupport/test/autoloading_fixtures/nested_with_require_parent.rb5
-rw-r--r--activesupport/test/cache/behaviors/cache_instrumentation_behavior.rb2
-rw-r--r--activesupport/test/dependencies/module_folder/lib_class.rb8
-rw-r--r--activesupport/test/dependencies_test.rb26
-rw-r--r--activesupport/test/parameter_filter_test.rb54
-rw-r--r--activesupport/test/testing/method_call_assertions_test.rb12
-rw-r--r--guides/source/action_mailer_basics.md29
-rw-r--r--guides/source/documents.yaml22
-rw-r--r--guides/source/i18n.md20
-rw-r--r--railties/lib/rails/app_updater.rb2
-rw-r--r--railties/lib/rails/application/configuration.rb2
-rw-r--r--railties/lib/rails/generators/app_base.rb3
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb10
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/setup.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/bin/update.tt4
-rw-r--r--railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt4
-rw-r--r--railties/lib/rails/generators/rails/plugin/plugin_generator.rb2
-rw-r--r--railties/lib/rails/generators/rails/plugin/templates/gitignore.tt2
-rw-r--r--railties/test/application/configuration_test.rb2
-rw-r--r--railties/test/generators/app_generator_test.rb4
-rw-r--r--railties/test/generators/shared_generator_tests.rb9
57 files changed, 479 insertions, 201 deletions
diff --git a/.github/no-response.yml b/.github/no-response.yml
index 7193eaa3b2..326fa84b7e 100644
--- a/.github/no-response.yml
+++ b/.github/no-response.yml
@@ -6,8 +6,7 @@ daysUntilClose: 14
responseRequiredLabel: more-information-needed
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
closeComment: >
- This issue has been automatically closed because there has been no response
- to our request for more information from the original author. With only the
- information that is currently in the issue, we don't have enough information
- to take action. Please reach out if you have or find the answers we need so
- that we can investigate further.
+ This issue has been automatically closed because there has been no follow-up
+ response from the original author. We currently don't have enough
+ information in order to take action. Please reach out if you have any additional
+ information that will help us move this issue forward.
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index 6e769aa560..c2caa77afb 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -1130,6 +1130,9 @@ module ActionView
# text_field(:post, :title, class: "create_input")
# # => <input type="text" id="post_title" name="post[title]" value="#{@post.title}" class="create_input" />
#
+ # text_field(:post, :title, maxlength: 30, class: "title_input")
+ # # => <input type="text" id="post_title" name="post[title]" maxlength="30" size="30" value="#{@post.title}" class="title_input" />
+ #
# text_field(:session, :user, onchange: "if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }")
# # => <input type="text" id="session_user" name="session[user]" value="#{@session.user}" onchange="if ($('#session_user').val() === 'admin') { alert('Your login cannot be admin!'); }"/>
#
diff --git a/activejob/CHANGELOG.md b/activejob/CHANGELOG.md
index af5c197bac..768e6bd250 100644
--- a/activejob/CHANGELOG.md
+++ b/activejob/CHANGELOG.md
@@ -1,3 +1,8 @@
+* Include deserialized arguments in job instances returned from
+ `assert_enqueued_with` and `assert_performed_with`
+
+ *Alan Wu*
+
* Allow `assert_enqueued_with`/`assert_performed_with` methods to accept
a proc for the `args` argument. This is useful to check if only a subset of arguments
matches your expectations.
diff --git a/activejob/lib/active_job/test_helper.rb b/activejob/lib/active_job/test_helper.rb
index 229855c5d9..0deb68d0d2 100644
--- a/activejob/lib/active_job/test_helper.rb
+++ b/activejob/lib/active_job/test_helper.rb
@@ -594,8 +594,7 @@ module ActiveJob
def flush_enqueued_jobs(only: nil, except: nil, queue: nil)
enqueued_jobs_with(only: only, except: except, queue: queue) do |payload|
- args = ActiveJob::Arguments.deserialize(payload[:args])
- instantiate_job(payload.merge(args: args)).perform_now
+ instantiate_job(payload).perform_now
queue_adapter.performed_jobs << payload
end
end
@@ -613,7 +612,8 @@ module ActiveJob
end
def instantiate_job(payload)
- job = payload[:job].new(*payload[:args])
+ args = ActiveJob::Arguments.deserialize(payload[:args])
+ job = payload[:job].new(*args)
job.scheduled_at = Time.at(payload[:at]) if payload.key?(:at)
job.queue_name = payload[:queue]
job
diff --git a/activejob/test/cases/test_helper_test.rb b/activejob/test/cases/test_helper_test.rb
index 83c71ab1c4..8dc32037ff 100644
--- a/activejob/test/cases/test_helper_test.rb
+++ b/activejob/test/cases/test_helper_test.rb
@@ -476,23 +476,23 @@ class EnqueuedJobsTest < ActiveJob::TestCase
def test_assert_enqueued_with_returns
job = assert_enqueued_with(job: LoggingJob) do
- LoggingJob.set(wait_until: 5.minutes.from_now).perform_later(1, 2, 3)
+ LoggingJob.set(wait_until: 5.minutes.from_now).perform_later(1, 2, 3, keyword: true)
end
assert_instance_of LoggingJob, job
assert_in_delta 5.minutes.from_now, job.scheduled_at, 1
assert_equal "default", job.queue_name
- assert_equal [1, 2, 3], job.arguments
+ assert_equal [1, 2, 3, { keyword: true }], job.arguments
end
def test_assert_enqueued_with_with_no_block_returns
- LoggingJob.set(wait_until: 5.minutes.from_now).perform_later(1, 2, 3)
+ LoggingJob.set(wait_until: 5.minutes.from_now).perform_later(1, 2, 3, keyword: true)
job = assert_enqueued_with(job: LoggingJob)
assert_instance_of LoggingJob, job
assert_in_delta 5.minutes.from_now, job.scheduled_at, 1
assert_equal "default", job.queue_name
- assert_equal [1, 2, 3], job.arguments
+ assert_equal [1, 2, 3, { keyword: true }], job.arguments
end
def test_assert_enqueued_with_failure
@@ -1515,26 +1515,26 @@ class PerformedJobsTest < ActiveJob::TestCase
end
def test_assert_performed_with_returns
- job = assert_performed_with(job: NestedJob, queue: "default") do
- NestedJob.perform_later
+ job = assert_performed_with(job: LoggingJob, queue: "default") do
+ LoggingJob.perform_later(keyword: :sym)
end
- assert_instance_of NestedJob, job
+ assert_instance_of LoggingJob, job
assert_nil job.scheduled_at
- assert_equal [], job.arguments
+ assert_equal [{ keyword: :sym }], job.arguments
assert_equal "default", job.queue_name
end
def test_assert_performed_with_without_block_returns
- NestedJob.perform_later
+ LoggingJob.perform_later(keyword: :sym)
perform_enqueued_jobs
- job = assert_performed_with(job: NestedJob, queue: "default")
+ job = assert_performed_with(job: LoggingJob, queue: "default")
- assert_instance_of NestedJob, job
+ assert_instance_of LoggingJob, job
assert_nil job.scheduled_at
- assert_equal [], job.arguments
+ assert_equal [{ keyword: :sym }], job.arguments
assert_equal "default", job.queue_name
end
diff --git a/activemodel/lib/active_model/dirty.rb b/activemodel/lib/active_model/dirty.rb
index 093052a70c..f0e1242554 100644
--- a/activemodel/lib/active_model/dirty.rb
+++ b/activemodel/lib/active_model/dirty.rb
@@ -151,7 +151,7 @@ module ActiveModel
@mutations_from_database = nil
end
- # Returns +true+ if any of the attributes have unsaved changes, +false+ otherwise.
+ # Returns +true+ if any of the attributes has unsaved changes, +false+ otherwise.
#
# person.changed? # => false
# person.name = 'bob'
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 97b7ad93d1..0d9bc3cec7 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,20 @@
+* Support default expression for MySQL.
+
+ MySQL 8.0.13 and higher supports default value to be a function or expression.
+
+ https://dev.mysql.com/doc/refman/8.0/en/create-table.html
+
+ *Ryuta Kamizono*
+
+* Support expression indexes for MySQL.
+
+ MySQL 8.0.13 and higher supports functional key parts that index
+ expression values rather than column or column prefix values.
+
+ https://dev.mysql.com/doc/refman/8.0/en/create-index.html
+
+ *Ryuta Kamizono*
+
* Fix collection cache key with limit and custom select to avoid ambiguous timestamp column error.
Fixes #33056.
@@ -144,13 +161,13 @@
specify sensitive attributes to specific model.
```
- Rails.application.config.filter_parameters += [:credit_card_number]
- Account.last.inspect # => #<Account id: 123, name: "DHH", credit_card_number: [FILTERED] ...>
+ Rails.application.config.filter_parameters += [:credit_card_number, /phone/]
+ Account.last.inspect # => #<Account id: 123, name: "DHH", credit_card_number: [FILTERED], telephone_number: [FILTERED] ...>
SecureAccount.filter_attributes += [:name]
SecureAccount.last.inspect # => #<SecureAccount id: 42, name: [FILTERED], credit_card_number: [FILTERED] ...>
```
- *Zhang Kang*
+ *Zhang Kang*, *Yoshiyuki Kinjo*
* Deprecate `column_name_length`, `table_name_length`, `columns_per_table`,
`indexes_per_table`, `columns_per_multicolumn_index`, `sql_query_length`,
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index eeb88e4b7a..1e92ee3b96 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -329,14 +329,7 @@ module ActiveRecord
# # => "[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]"
def attribute_for_inspect(attr_name)
value = read_attribute(attr_name)
-
- if value.is_a?(String) && value.length > 50
- "#{value[0, 50]}...".inspect
- elsif value.is_a?(Date) || value.is_a?(Time)
- %("#{value.to_s(:db)}")
- else
- value.inspect
- end
+ format_for_inspect(value)
end
# Returns +true+ if the specified +attribute+ has been set by the user or by a
@@ -456,6 +449,16 @@ module ActiveRecord
end
end
+ def format_for_inspect(value)
+ if value.is_a?(String) && value.length > 50
+ "#{value[0, 50]}...".inspect
+ elsif value.is_a?(Date) || value.is_a?(Time)
+ %("#{value.to_s(:db)}")
+ else
+ value.inspect
+ end
+ end
+
def readonly_attribute?(name)
self.class.readonly_attributes.include?(name)
end
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 0f73641369..13c799b64a 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -72,6 +72,10 @@ module ActiveRecord
!mariadb? && version >= "8.0.1"
end
+ def supports_expression_index?
+ !mariadb? && version >= "8.0.13"
+ end
+
def supports_transaction_isolation?
true
end
diff --git a/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb b/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb
index 3dcb916d99..f158946c6d 100644
--- a/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb
+++ b/activerecord/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb
@@ -10,8 +10,17 @@ module ActiveRecord
super
end
- def visit_Arel_Nodes_In(*)
+ def visit_Arel_Nodes_In(o, collector)
@preparable = false
+
+ if Array === o.right && !o.right.empty?
+ o.right.delete_if do |bind|
+ if Arel::Nodes::BindParam === bind && Relation::QueryAttribute === bind.value
+ !bind.value.boundable?
+ end
+ end
+ end
+
super
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
index e167c01802..4894fd1c08 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_statements.rb
@@ -35,13 +35,39 @@ module ActiveRecord
]
end
- indexes.last[-2] << row[:Column_name]
- indexes.last[-1][:lengths][row[:Column_name]] = row[:Sub_part].to_i if row[:Sub_part]
- indexes.last[-1][:orders].merge!(row[:Column_name] => :desc) if row[:Collation] == "D"
+ if row[:Expression]
+ expression = row[:Expression]
+ expression = +"(#{expression})" unless expression.start_with?("(")
+ indexes.last[-2] << expression
+ indexes.last[-1][:expressions] ||= {}
+ indexes.last[-1][:expressions][expression] = expression
+ indexes.last[-1][:orders][expression] = :desc if row[:Collation] == "D"
+ else
+ indexes.last[-2] << row[:Column_name]
+ indexes.last[-1][:lengths][row[:Column_name]] = row[:Sub_part].to_i if row[:Sub_part]
+ indexes.last[-1][:orders][row[:Column_name]] = :desc if row[:Collation] == "D"
+ end
end
end
- indexes.map { |index| IndexDefinition.new(*index) }
+ indexes.map do |index|
+ options = index.last
+
+ if expressions = options.delete(:expressions)
+ orders = options.delete(:orders)
+ lengths = options.delete(:lengths)
+
+ columns = index[-2].map { |name|
+ [ name.to_sym, expressions[name] || +quote_column_name(name) ]
+ }.to_h
+
+ index[-2] = add_options_for_index_columns(
+ columns, order: orders, length: lengths
+ ).values.join(", ")
+ end
+
+ IndexDefinition.new(*index)
+ end
end
def remove_column(table_name, column_name, type = nil, options = {})
@@ -80,10 +106,13 @@ module ActiveRecord
def new_column_from_field(table_name, field)
type_metadata = fetch_type_metadata(field[:Type], field[:Extra])
- if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(field[:Default])
- default, default_function = nil, field[:Default]
- else
- default, default_function = field[:Default], nil
+ default, default_function = field[:Default], nil
+
+ if type_metadata.type == :datetime && /\ACURRENT_TIMESTAMP(?:\([0-6]?\))?\z/i.match?(default)
+ default, default_function = nil, default
+ elsif type_metadata.extra == "DEFAULT_GENERATED"
+ default = +"(#{default})" unless default.start_with?("(")
+ default, default_function = nil, default
end
MySQL::Column.new(
diff --git a/activerecord/lib/active_record/core.rb b/activerecord/lib/active_record/core.rb
index 0718688863..da3e2549a2 100644
--- a/activerecord/lib/active_record/core.rb
+++ b/activerecord/lib/active_record/core.rb
@@ -2,15 +2,13 @@
require "active_support/core_ext/hash/indifferent_access"
require "active_support/core_ext/string/filters"
+require "active_support/parameter_filter"
require "concurrent/map"
-require "set"
module ActiveRecord
module Core
extend ActiveSupport::Concern
- FILTERED = "[FILTERED]" # :nodoc:
-
included do
##
# :singleton-method:
@@ -239,9 +237,7 @@ module ActiveRecord
end
# Specifies columns which shouldn't be exposed while calling +#inspect+.
- def filter_attributes=(attributes_names)
- @filter_attributes = attributes_names.map(&:to_s).to_set
- end
+ attr_writer :filter_attributes
# Returns a string like 'Post(id:integer, title:string, body:text)'
def inspect # :nodoc:
@@ -502,11 +498,14 @@ module ActiveRecord
inspection = if defined?(@attributes) && @attributes
self.class.attribute_names.collect do |name|
if has_attribute?(name)
- if filter_attribute?(name)
- "#{name}: #{ActiveRecord::Core::FILTERED}"
+ attr = read_attribute(name)
+ value = if attr.nil?
+ attr.inspect
else
- "#{name}: #{attribute_for_inspect(name)}"
+ attr = format_for_inspect(attr)
+ inspection_filter.filter_param(name, attr)
end
+ "#{name}: #{value}"
end
end.compact.join(", ")
else
@@ -522,18 +521,16 @@ module ActiveRecord
return super if custom_inspect_method_defined?
pp.object_address_group(self) do
if defined?(@attributes) && @attributes
- column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? }
- pp.seplist(column_names, proc { pp.text "," }) do |column_name|
+ attr_names = self.class.attribute_names.select { |name| has_attribute?(name) }
+ pp.seplist(attr_names, proc { pp.text "," }) do |attr_name|
pp.breakable " "
pp.group(1) do
- pp.text column_name
+ pp.text attr_name
pp.text ":"
pp.breakable
- if filter_attribute?(column_name)
- pp.text ActiveRecord::Core::FILTERED
- else
- pp.pp read_attribute(column_name)
- end
+ value = read_attribute(attr_name)
+ value = inspection_filter.filter_param(attr_name, value) unless value.nil?
+ pp.pp value
end
end
else
@@ -585,8 +582,14 @@ module ActiveRecord
self.class.instance_method(:inspect).owner != ActiveRecord::Base.instance_method(:inspect).owner
end
- def filter_attribute?(attribute_name)
- self.class.filter_attributes.include?(attribute_name) && !read_attribute(attribute_name).nil?
+ def inspection_filter
+ @inspection_filter ||= begin
+ mask = DelegateClass(::String).new(ActiveSupport::ParameterFilter::FILTERED)
+ def mask.pretty_print(pp)
+ pp.text __getobj__
+ end
+ ActiveSupport::ParameterFilter.new(self.class.filter_attributes, mask: mask)
+ end
end
end
end
diff --git a/activerecord/lib/active_record/relation/finder_methods.rb b/activerecord/lib/active_record/relation/finder_methods.rb
index 6f420fe6bb..afaa900442 100644
--- a/activerecord/lib/active_record/relation/finder_methods.rb
+++ b/activerecord/lib/active_record/relation/finder_methods.rb
@@ -363,7 +363,7 @@ module ActiveRecord
case conditions
when Array, Hash
- relation.where!(conditions)
+ relation.where!(conditions) unless conditions.empty?
else
relation.where!(primary_key => conditions) unless conditions == :none
end
diff --git a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
index fadb3c420d..ee2ece1560 100644
--- a/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
+++ b/activerecord/lib/active_record/relation/predicate_builder/array_handler.rb
@@ -22,9 +22,8 @@ module ActiveRecord
when 1 then predicate_builder.build(attribute, values.first)
else
values.map! do |v|
- bind = predicate_builder.build_bind_attribute(attribute.name, v)
- bind if bind.value.boundable?
- end.compact!
+ predicate_builder.build_bind_attribute(attribute.name, v)
+ end
values.empty? ? NullPredicate : attribute.in(values)
end
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index 39034746c9..b37e59038e 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -34,7 +34,7 @@ class EagerLoadingTooManyIdsTest < ActiveRecord::TestCase
fixtures :citations
def test_preloading_too_many_ids
- assert_equal Citation.count, Citation.preload(:citations).to_a.size
+ assert_equal Citation.count, Citation.preload(:reference_of).to_a.size
end
def test_eager_loading_too_may_ids
@@ -1618,32 +1618,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
end
end
- # Associations::Preloader#preloaders_on works with hash-like objects
- test "preloading works with an object that responds to :to_hash" do
- CustomHash = Class.new(Hash)
-
- assert_nothing_raised do
- Post.preload(CustomHash.new(comments: [{ author: :essays }])).first
- end
- end
-
- # Associations::Preloader#preloaders_on works with string-like objects
- test "preloading works with an object that responds to :to_str" do
- CustomString = Class.new(String)
-
- assert_nothing_raised do
- Post.preload(CustomString.new("comments")).first
- end
- end
-
- # Associations::Preloader#preloaders_on does not work with ranges
- test "preloading fails when Range is passed" do
- exception = assert_raises(ArgumentError) do
- Post.preload(1..10).first
- end
- assert_equal("1..10 was not recognized for preload", exception.message)
- end
-
private
def find_all_ordered(klass, include = nil)
klass.order("#{klass.table_name}.#{klass.primary_key}").includes(include).to_a
diff --git a/activerecord/test/cases/bind_parameter_test.rb b/activerecord/test/cases/bind_parameter_test.rb
index 9c1f7aaef2..fddc2781b8 100644
--- a/activerecord/test/cases/bind_parameter_test.rb
+++ b/activerecord/test/cases/bind_parameter_test.rb
@@ -36,7 +36,7 @@ if ActiveRecord::Base.connection.prepared_statements
def test_too_many_binds
bind_params_length = @connection.send(:bind_params_length)
- topics = Topic.where(id: (1 .. bind_params_length + 1).to_a)
+ topics = Topic.where(id: (1 .. bind_params_length).to_a << 2**63)
assert_equal Topic.count, topics.count
end
diff --git a/activerecord/test/cases/defaults_test.rb b/activerecord/test/cases/defaults_test.rb
index 0f957d41cf..5d02e59ef6 100644
--- a/activerecord/test/cases/defaults_test.rb
+++ b/activerecord/test/cases/defaults_test.rb
@@ -106,6 +106,13 @@ if current_adapter?(:Mysql2Adapter)
class MysqlDefaultExpressionTest < ActiveRecord::TestCase
include SchemaDumpingHelper
+ if supports_default_expression?
+ test "schema dump includes default expression" do
+ output = dump_table_schema("defaults")
+ assert_match %r/t\.binary\s+"uuid",\s+limit: 36,\s+default: -> { "\(uuid\(\)\)" }/i, output
+ end
+ end
+
if subsecond_precision_supported?
test "schema dump datetime includes default expression" do
output = dump_table_schema("datetime_defaults")
diff --git a/activerecord/test/cases/filter_attributes_test.rb b/activerecord/test/cases/filter_attributes_test.rb
index af5badd87d..47161a547a 100644
--- a/activerecord/test/cases/filter_attributes_test.rb
+++ b/activerecord/test/cases/filter_attributes_test.rb
@@ -4,6 +4,7 @@ require "cases/helper"
require "models/admin"
require "models/admin/user"
require "models/admin/account"
+require "models/user"
require "pp"
class FilterAttributesTest < ActiveRecord::TestCase
@@ -30,6 +31,32 @@ class FilterAttributesTest < ActiveRecord::TestCase
end
end
+ test "string filter_attributes perform pertial match" do
+ ActiveRecord::Base.filter_attributes = ["n"]
+ Admin::Account.all.each do |account|
+ assert_includes account.inspect, "name: [FILTERED]"
+ assert_equal 1, account.inspect.scan("[FILTERED]").length
+ end
+ end
+
+ test "regex filter_attributes are accepted" do
+ ActiveRecord::Base.filter_attributes = [/\An\z/]
+ account = Admin::Account.find_by(name: "37signals")
+ assert_includes account.inspect, 'name: "37signals"'
+ assert_equal 0, account.inspect.scan("[FILTERED]").length
+
+ ActiveRecord::Base.filter_attributes = [/\An/]
+ account = Admin::Account.find_by(name: "37signals")
+ assert_includes account.reload.inspect, "name: [FILTERED]"
+ assert_equal 1, account.inspect.scan("[FILTERED]").length
+ end
+
+ test "proc filter_attributes are accepted" do
+ ActiveRecord::Base.filter_attributes = [ lambda { |key, value| value.reverse! if key == "name" } ]
+ account = Admin::Account.find_by(name: "37signals")
+ assert_includes account.inspect, 'name: "slangis73"'
+ end
+
test "filter_attributes could be overwritten by models" do
Admin::Account.all.each do |account|
assert_includes account.inspect, "name: [FILTERED]"
@@ -37,7 +64,6 @@ class FilterAttributesTest < ActiveRecord::TestCase
end
begin
- previous_account_filter_attributes = Admin::Account.filter_attributes
Admin::Account.filter_attributes = []
# Above changes should not impact other models
@@ -51,7 +77,7 @@ class FilterAttributesTest < ActiveRecord::TestCase
assert_equal 0, account.inspect.scan("[FILTERED]").length
end
ensure
- Admin::Account.filter_attributes = previous_account_filter_attributes
+ Admin::Account.remove_instance_variable(:@filter_attributes)
end
end
@@ -63,6 +89,18 @@ class FilterAttributesTest < ActiveRecord::TestCase
assert_equal 0, account.inspect.scan("[FILTERED]").length
end
+ test "filter_attributes should handle [FILTERED] value properly" do
+ begin
+ User.filter_attributes = ["auth"]
+ user = User.new(token: "[FILTERED]", auth_token: "[FILTERED]")
+
+ assert_includes user.inspect, "auth_token: [FILTERED]"
+ assert_includes user.inspect, 'token: "[FILTERED]"'
+ ensure
+ User.remove_instance_variable(:@filter_attributes)
+ end
+ end
+
test "filter_attributes on pretty_print" do
user = admin_users(:david)
actual = "".dup
@@ -81,4 +119,18 @@ class FilterAttributesTest < ActiveRecord::TestCase
assert_not_includes actual, "name: [FILTERED]"
assert_equal 0, actual.scan("[FILTERED]").length
end
+
+ test "filter_attributes on pretty_print should handle [FILTERED] value properly" do
+ begin
+ User.filter_attributes = ["auth"]
+ user = User.new(token: "[FILTERED]", auth_token: "[FILTERED]")
+ actual = "".dup
+ PP.pp(user, StringIO.new(actual))
+
+ assert_includes actual, "auth_token: [FILTERED]"
+ assert_includes actual, 'token: "[FILTERED]"'
+ ensure
+ User.remove_instance_variable(:@filter_attributes)
+ end
+ end
end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 355fb4517f..52fd9291b2 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -20,6 +20,7 @@ require "models/matey"
require "models/dog"
require "models/car"
require "models/tyre"
+require "models/subscriber"
class FinderTest < ActiveRecord::TestCase
fixtures :companies, :topics, :entrants, :developers, :developers_projects, :posts, :comments, :accounts, :authors, :author_addresses, :customers, :categories, :categorizations, :cars
@@ -167,6 +168,7 @@ class FinderTest < ActiveRecord::TestCase
assert_equal true, Topic.exists?(id: [1, 9999])
assert_equal false, Topic.exists?(45)
+ assert_equal false, Topic.exists?(9999999999999999999999999999999)
assert_equal false, Topic.exists?(Topic.new.id)
assert_raise(NoMethodError) { Topic.exists?([1, 2]) }
@@ -211,17 +213,23 @@ class FinderTest < ActiveRecord::TestCase
assert_equal false, relation.exists?(false)
end
+ def test_exists_with_string
+ assert_equal false, Subscriber.exists?("foo")
+ assert_equal false, Subscriber.exists?(" ")
+
+ Subscriber.create!(id: "foo")
+ Subscriber.create!(id: " ")
+
+ assert_equal true, Subscriber.exists?("foo")
+ assert_equal true, Subscriber.exists?(" ")
+ end
+
def test_exists_passing_active_record_object_is_not_permitted
assert_raises(ArgumentError) do
Topic.exists?(Topic.new)
end
end
- def test_exists_returns_false_when_parameter_has_invalid_type
- assert_equal false, Topic.exists?("foo")
- assert_equal false, Topic.exists?(("9" * 53).to_i) # number that's bigger than int
- end
-
def test_exists_does_not_select_columns_without_alias
assert_sql(/SELECT\W+1 AS one FROM ["`]topics["`]/i) do
Topic.exists?
@@ -246,6 +254,10 @@ class FinderTest < ActiveRecord::TestCase
assert_equal true, Topic.first.replies.exists?
end
+ def test_exists_with_empty_hash_arg
+ assert_equal true, Topic.exists?({})
+ end
+
# Ensure +exists?+ runs without an error by excluding distinct value.
# See https://github.com/rails/rails/pull/26981.
def test_exists_with_order_and_distinct
diff --git a/activerecord/test/cases/helper.rb b/activerecord/test/cases/helper.rb
index 68be685e4b..730cd663a2 100644
--- a/activerecord/test/cases/helper.rb
+++ b/activerecord/test/cases/helper.rb
@@ -48,6 +48,15 @@ def mysql_enforcing_gtid_consistency?
current_adapter?(:Mysql2Adapter) && "ON" == ActiveRecord::Base.connection.show_variable("enforce_gtid_consistency")
end
+def supports_default_expression?
+ if current_adapter?(:PostgreSQLAdapter)
+ true
+ elsif current_adapter?(:Mysql2Adapter)
+ conn = ActiveRecord::Base.connection
+ !conn.mariadb? && conn.version >= "8.0.13"
+ end
+end
+
def supports_savepoints?
ActiveRecord::Base.connection.supports_savepoints?
end
diff --git a/activerecord/test/cases/schema_dumper_test.rb b/activerecord/test/cases/schema_dumper_test.rb
index db13f20a39..dda3efa47c 100644
--- a/activerecord/test/cases/schema_dumper_test.rb
+++ b/activerecord/test/cases/schema_dumper_test.rb
@@ -229,11 +229,14 @@ class SchemaDumperTest < ActiveRecord::TestCase
if ActiveRecord::Base.connection.supports_expression_index?
def test_schema_dump_expression_indices
index_definition = dump_table_schema("companies").split(/\n/).grep(/t\.index.*company_expression_index/).first.strip
+ index_definition.sub!(/, name: "company_expression_index"\z/, "")
if current_adapter?(:PostgreSQLAdapter)
- assert_match %r{CASE.+lower\(\(name\)::text\)}i, index_definition
+ assert_match %r{CASE.+lower\(\(name\)::text\).+END\) DESC"\z}i, index_definition
+ elsif current_adapter?(:Mysql2Adapter)
+ assert_match %r{CASE.+lower\(`name`\).+END\) DESC"\z}i, index_definition
elsif current_adapter?(:SQLite3Adapter)
- assert_match %r{CASE.+lower\(name\)}i, index_definition
+ assert_match %r{CASE.+lower\(name\).+END\) DESC"\z}i, index_definition
else
assert false
end
diff --git a/activerecord/test/cases/tasks/mysql_rake_test.rb b/activerecord/test/cases/tasks/mysql_rake_test.rb
index 4d6dff68f9..552e623fd4 100644
--- a/activerecord/test/cases/tasks/mysql_rake_test.rb
+++ b/activerecord/test/cases/tasks/mysql_rake_test.rb
@@ -272,7 +272,7 @@ if current_adapter?(:Mysql2Adapter)
def test_db_retrieves_collation
ActiveRecord::Base.stub(:connection, @connection) do
- assert_called_with(@connection, :collation) do
+ assert_called(@connection, :collation) do
ActiveRecord::Tasks::DatabaseTasks.collation @configuration
end
end
diff --git a/activerecord/test/config.example.yml b/activerecord/test/config.example.yml
index be337ddcd8..18347cd07d 100644
--- a/activerecord/test/config.example.yml
+++ b/activerecord/test/config.example.yml
@@ -59,6 +59,7 @@ connections:
arunit2:
username: rails
encoding: utf8mb4
+ collation: utf8mb4_general_ci
oracle:
arunit:
diff --git a/activerecord/test/fixtures/citations.yml b/activerecord/test/fixtures/citations.yml
index d31cb8efa1..396099621c 100644
--- a/activerecord/test/fixtures/citations.yml
+++ b/activerecord/test/fixtures/citations.yml
@@ -1,4 +1,5 @@
<% 65536.times do |i| %>
fixture_no_<%= i %>:
id: <%= i %>
+ book2_id: <%= i*i %>
<% end %>
diff --git a/activerecord/test/models/country.rb b/activerecord/test/models/country.rb
index 0c84a40de2..4b4a276a98 100644
--- a/activerecord/test/models/country.rb
+++ b/activerecord/test/models/country.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
class Country < ActiveRecord::Base
- self.primary_key = :country_id
-
has_and_belongs_to_many :treaties
end
diff --git a/activerecord/test/models/treaty.rb b/activerecord/test/models/treaty.rb
index 5c1d75aa09..b87a757d2a 100644
--- a/activerecord/test/models/treaty.rb
+++ b/activerecord/test/models/treaty.rb
@@ -1,7 +1,5 @@
# frozen_string_literal: true
class Treaty < ActiveRecord::Base
- self.primary_key = :treaty_id
-
has_and_belongs_to_many :countries
end
diff --git a/activerecord/test/schema/mysql2_specific_schema.rb b/activerecord/test/schema/mysql2_specific_schema.rb
index 499280cb0c..ccca9a2d9b 100644
--- a/activerecord/test/schema/mysql2_specific_schema.rb
+++ b/activerecord/test/schema/mysql2_specific_schema.rb
@@ -19,6 +19,9 @@ ActiveRecord::Schema.define do
t.datetime :fixed_time, default: "2004-01-01 00:00:00"
t.column :char1, "char(1)", default: "Y"
t.string :char2, limit: 50, default: "a varchar field"
+ if supports_default_expression?
+ t.binary :uuid, limit: 36, default: -> { "(uuid())" }
+ end
end
create_table :binary_fields, force: true do |t|
diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb
index 2aaf393009..a3989cc66d 100644
--- a/activerecord/test/schema/schema.rb
+++ b/activerecord/test/schema/schema.rb
@@ -93,7 +93,7 @@ ActiveRecord::Schema.define do
t.integer :pirate_id
end
- create_table :books, force: true do |t|
+ create_table :books, id: :integer, force: true do |t|
t.references :author
t.string :format
t.column :name, :string
@@ -158,8 +158,8 @@ ActiveRecord::Schema.define do
end
create_table :citations, force: true do |t|
- t.column :book1_id, :integer
- t.column :book2_id, :integer
+ t.references :book1
+ t.references :book2
t.references :citation
end
@@ -216,7 +216,7 @@ ActiveRecord::Schema.define do
t.index [:firm_id, :type, :rating], name: "company_index", length: { type: 10 }, order: { rating: :desc }
t.index [:firm_id, :type], name: "company_partial_index", where: "(rating > 10)"
t.index :name, name: "company_name_index", using: :btree
- t.index "(CASE WHEN rating > 0 THEN lower(name) END)", name: "company_expression_index" if supports_expression_index?
+ t.index "(CASE WHEN rating > 0 THEN lower(name) END) DESC", name: "company_expression_index" if supports_expression_index?
end
create_table :content, force: true do |t|
@@ -983,14 +983,16 @@ ActiveRecord::Schema.define do
t.references :wheelable, polymorphic: true
end
- create_table :countries, force: true, id: false, primary_key: "country_id" do |t|
- t.string :country_id
+ create_table :countries, force: true, id: false do |t|
+ t.string :country_id, primary_key: true
t.string :name
end
- create_table :treaties, force: true, id: false, primary_key: "treaty_id" do |t|
- t.string :treaty_id
+
+ create_table :treaties, force: true, id: false do |t|
+ t.string :treaty_id, primary_key: true
t.string :name
end
+
create_table :countries_treaties, force: true, primary_key: [:country_id, :treaty_id] do |t|
t.string :country_id, null: false
t.string :treaty_id, null: false
diff --git a/activestorage/app/models/active_storage/attachment.rb b/activestorage/app/models/active_storage/attachment.rb
index 4bdd1c0224..13758d9179 100644
--- a/activestorage/app/models/active_storage/attachment.rb
+++ b/activestorage/app/models/active_storage/attachment.rb
@@ -3,9 +3,8 @@
require "active_support/core_ext/module/delegation"
# Attachments associate records with blobs. Usually that's a one record-many blobs relationship,
-# but it is possible to associate many different records with the same blob. If you're doing that,
-# you'll want to declare with <tt>has_one/many_attached :thingy, dependent: false</tt>, so that destroying
-# any one record won't destroy the blob as well. (Then you'll need to do your own garbage collecting, though).
+# but it is possible to associate many different records with the same blob. A foreign-key constraint
+# on the attachments table prevents blobs from being purged if they’re still attached to any records.
class ActiveStorage::Attachment < ActiveRecord::Base
self.table_name = "active_storage_attachments"
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index a53e4eb5a2..92f5f64719 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,7 @@
+* Improve the logic that detects non-autoloaded constants.
+
+ *Jan Habermann*, *Xavier Noria*
+
* Deprecate `ActiveSupport::Multibyte::Unicode#pack_graphemes(array)` and `ActiveSuppport::Multibyte::Unicode#unpack_graphemes(string)`
in favor of `array.flatten.pack("U*")` and `string.scan(/\X/).map(&:codepoints)`, respectively.
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 487fe79f41..d0644a0f7e 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -23,6 +23,9 @@ module ActiveSupport
# +ClassMethods.set_callback+), and run the installed callbacks at the
# appropriate times (via +run_callbacks+).
#
+ # By default callbacks are halted by throwing +:abort+.
+ # See +ClassMethods.define_callbacks+ for details.
+ #
# Three kinds of callbacks are supported: before callbacks, run before a
# certain event; after callbacks, run after the event; and around callbacks,
# blocks that surround the event, triggering it when they yield. Callback code
diff --git a/activesupport/lib/active_support/dependencies.rb b/activesupport/lib/active_support/dependencies.rb
index 66e0bea00e..d5d00b5e6e 100644
--- a/activesupport/lib/active_support/dependencies.rb
+++ b/activesupport/lib/active_support/dependencies.rb
@@ -103,6 +103,8 @@ module ActiveSupport #:nodoc:
# parent.rb then requires namespace/child.rb, the stack will look like
# [[Object], [Namespace]].
+ attr_reader :watching
+
def initialize
@watching = []
@stack = Hash.new { |h, k| h[k] = [] }
@@ -254,7 +256,9 @@ module ActiveSupport #:nodoc:
def load_dependency(file)
if Dependencies.load? && Dependencies.constant_watch_stack.watching?
- Dependencies.new_constants_in(Object) { yield }
+ descs = Dependencies.constant_watch_stack.watching.flatten.uniq
+
+ Dependencies.new_constants_in(*descs) { yield }
else
yield
end
diff --git a/activesupport/lib/active_support/parameter_filter.rb b/activesupport/lib/active_support/parameter_filter.rb
index 59945e9daa..1389d82523 100644
--- a/activesupport/lib/active_support/parameter_filter.rb
+++ b/activesupport/lib/active_support/parameter_filter.rb
@@ -28,22 +28,36 @@ module ActiveSupport
class ParameterFilter
FILTERED = "[FILTERED]" # :nodoc:
- def initialize(filters = [])
+ # Create instance with given filters. Supported type of filters are +String+, +Regexp+, and +Proc+.
+ # Other types of filters are treated as +String+ using +to_s+.
+ # For +Proc+ filters, key, value, and optional original hash is passed to block arguments.
+ #
+ # ==== Options
+ #
+ # * <tt>:mask</tt> - A replaced object when filtered. Defaults to +"[FILTERED]"+
+ def initialize(filters = [], mask: FILTERED)
@filters = filters
+ @mask = mask
end
+ # Mask value of +params+ if key matches one of filters.
def filter(params)
compiled_filter.call(params)
end
+ # Returns filtered value for given key. For +Proc+ filters, third block argument is not populated.
+ def filter_param(key, value)
+ @filters.empty? ? value : compiled_filter.value_for_key(key, value)
+ end
+
private
def compiled_filter
- @compiled_filter ||= CompiledFilter.compile(@filters)
+ @compiled_filter ||= CompiledFilter.compile(@filters, mask: @mask)
end
class CompiledFilter # :nodoc:
- def self.compile(filters)
+ def self.compile(filters, mask:)
return lambda { |params| params.dup } if filters.empty?
strings, regexps, blocks = [], [], []
@@ -65,42 +79,46 @@ module ActiveSupport
regexps << Regexp.new(strings.join("|"), true) unless strings.empty?
deep_regexps << Regexp.new(deep_strings.join("|"), true) unless deep_strings.empty?
- new regexps, deep_regexps, blocks
+ new regexps, deep_regexps, blocks, mask: mask
end
attr_reader :regexps, :deep_regexps, :blocks
- def initialize(regexps, deep_regexps, blocks)
+ def initialize(regexps, deep_regexps, blocks, mask:)
@regexps = regexps
@deep_regexps = deep_regexps.any? ? deep_regexps : nil
@blocks = blocks
+ @mask = mask
end
def call(params, parents = [], original_params = params)
filtered_params = params.class.new
params.each do |key, value|
- parents.push(key) if deep_regexps
- if regexps.any? { |r| key =~ r }
- value = FILTERED
- elsif deep_regexps && (joined = parents.join(".")) && deep_regexps.any? { |r| joined =~ r }
- value = FILTERED
- elsif value.is_a?(Hash)
- value = call(value, parents, original_params)
- elsif value.is_a?(Array)
- value = value.map { |v| v.is_a?(Hash) ? call(v, parents, original_params) : v }
- elsif blocks.any?
- key = key.dup if key.duplicable?
- value = value.dup if value.duplicable?
- blocks.each { |b| b.arity == 2 ? b.call(key, value) : b.call(key, value, original_params) }
- end
- parents.pop if deep_regexps
-
- filtered_params[key] = value
+ filtered_params[key] = value_for_key(key, value, parents, original_params)
end
filtered_params
end
+
+ def value_for_key(key, value, parents = [], original_params = nil)
+ parents.push(key) if deep_regexps
+ if regexps.any? { |r| r.match?(key) }
+ value = @mask
+ elsif deep_regexps && (joined = parents.join(".")) && deep_regexps.any? { |r| r.match?(joined) }
+ value = @mask
+ elsif value.is_a?(Hash)
+ value = call(value, parents, original_params)
+ elsif value.is_a?(Array)
+ value = value.map { |v| v.is_a?(Hash) ? call(v, parents, original_params) : v }
+ elsif blocks.any?
+ key = key.dup if key.duplicable?
+ value = value.dup if value.duplicable?
+ blocks.each { |b| b.arity == 2 ? b.call(key, value) : b.call(key, value, original_params) }
+ end
+ parents.pop if deep_regexps
+ value
+ end
end
end
end
diff --git a/activesupport/lib/active_support/testing/method_call_assertions.rb b/activesupport/lib/active_support/testing/method_call_assertions.rb
index fdc70e1cd3..eba41aa907 100644
--- a/activesupport/lib/active_support/testing/method_call_assertions.rb
+++ b/activesupport/lib/active_support/testing/method_call_assertions.rb
@@ -17,7 +17,7 @@ module ActiveSupport
assert_equal times, times_called, error
end
- def assert_called_with(object, method_name, args = [], returns: nil)
+ def assert_called_with(object, method_name, args, returns: nil)
mock = Minitest::Mock.new
if args.all? { |arg| arg.is_a?(Array) }
diff --git a/activesupport/lib/active_support/values/time_zone.rb b/activesupport/lib/active_support/values/time_zone.rb
index fd07a3a6a2..d9e033e23b 100644
--- a/activesupport/lib/active_support/values/time_zone.rb
+++ b/activesupport/lib/active_support/values/time_zone.rb
@@ -182,8 +182,9 @@ module ActiveSupport
"Samoa" => "Pacific/Apia"
}
- UTC_OFFSET_WITH_COLON = "%s%02d:%02d"
- UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.tr(":", "")
+ UTC_OFFSET_WITH_COLON = "%s%02d:%02d" # :nodoc:
+ UTC_OFFSET_WITHOUT_COLON = UTC_OFFSET_WITH_COLON.tr(":", "") # :nodoc:
+ private_constant :UTC_OFFSET_WITH_COLON, :UTC_OFFSET_WITHOUT_COLON
@lazy_zones_map = Concurrent::Map.new
@country_zones = Concurrent::Map.new
diff --git a/activesupport/test/autoloading_fixtures/module_folder/nested_with_require.rb b/activesupport/test/autoloading_fixtures/module_folder/nested_with_require.rb
new file mode 100644
index 0000000000..f9d6e675d7
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/module_folder/nested_with_require.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+require "dependencies/module_folder/lib_class"
+
+module ModuleFolder
+ class NestedWithRequire
+ end
+end
diff --git a/activesupport/test/autoloading_fixtures/nested_with_require_parent.rb b/activesupport/test/autoloading_fixtures/nested_with_require_parent.rb
new file mode 100644
index 0000000000..e8fb321077
--- /dev/null
+++ b/activesupport/test/autoloading_fixtures/nested_with_require_parent.rb
@@ -0,0 +1,5 @@
+# frozen_string_literal: true
+
+class NestedWithRequireParent
+ ModuleFolder::NestedWithRequire
+end
diff --git a/activesupport/test/cache/behaviors/cache_instrumentation_behavior.rb b/activesupport/test/cache/behaviors/cache_instrumentation_behavior.rb
index 4e8ff60eb3..a4abdd37b9 100644
--- a/activesupport/test/cache/behaviors/cache_instrumentation_behavior.rb
+++ b/activesupport/test/cache/behaviors/cache_instrumentation_behavior.rb
@@ -2,7 +2,7 @@
module CacheInstrumentationBehavior
def test_fetch_multi_uses_write_multi_entries_store_provider_interface
- assert_called_with(@cache, :write_multi_entries) do
+ assert_called(@cache, :write_multi_entries) do
@cache.fetch_multi "a", "b", "c" do |key|
key * 2
end
diff --git a/activesupport/test/dependencies/module_folder/lib_class.rb b/activesupport/test/dependencies/module_folder/lib_class.rb
new file mode 100644
index 0000000000..c6b52610c1
--- /dev/null
+++ b/activesupport/test/dependencies/module_folder/lib_class.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+ConstFromLib = 1
+
+module ModuleFolder
+ class LibClass
+ end
+end
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index e144971e9f..9f2755a25c 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -282,6 +282,32 @@ class DependenciesTest < ActiveSupport::TestCase
remove_constants(:ModuleFolder)
end
+ def test_module_with_nested_class_requiring_lib_class
+ with_autoloading_fixtures do
+ ModuleFolder::NestedWithRequire
+
+ assert defined?(ModuleFolder::LibClass)
+ assert_not ActiveSupport::Dependencies.autoloaded_constants.include?("ModuleFolder::LibClass")
+ assert_not ActiveSupport::Dependencies.autoloaded_constants.include?("ConstFromLib")
+ end
+ ensure
+ remove_constants(:ModuleFolder)
+ remove_constants(:ConstFromLib)
+ end
+
+ def test_module_with_nested_class_and_parent_requiring_lib_class
+ with_autoloading_fixtures do
+ NestedWithRequireParent
+
+ assert defined?(ModuleFolder::LibClass)
+ assert_not ActiveSupport::Dependencies.autoloaded_constants.include?("ModuleFolder::LibClass")
+ assert_not ActiveSupport::Dependencies.autoloaded_constants.include?("ConstFromLib")
+ end
+ ensure
+ remove_constants(:ModuleFolder)
+ remove_constants(:ConstFromLib)
+ end
+
def test_directories_may_manifest_as_nested_classes
with_autoloading_fixtures do
assert_kind_of Class, ClassFolder
diff --git a/activesupport/test/parameter_filter_test.rb b/activesupport/test/parameter_filter_test.rb
index 3403a3188b..d2dc71061d 100644
--- a/activesupport/test/parameter_filter_test.rb
+++ b/activesupport/test/parameter_filter_test.rb
@@ -36,6 +36,51 @@ class ParameterFilterTest < ActiveSupport::TestCase
end
end
+ test "filter should return mask option when value is filtered" do
+ mask = Object.new.freeze
+ test_hashes = [
+ [{ "foo" => "bar" }, { "foo" => "bar" }, %w'food'],
+ [{ "foo" => "bar" }, { "foo" => mask }, %w'foo'],
+ [{ "foo" => "bar", "bar" => "foo" }, { "foo" => mask, "bar" => "foo" }, %w'foo baz'],
+ [{ "foo" => "bar", "baz" => "foo" }, { "foo" => mask, "baz" => mask }, %w'foo baz'],
+ [{ "bar" => { "foo" => "bar", "bar" => "foo" } }, { "bar" => { "foo" => mask, "bar" => "foo" } }, %w'fo'],
+ [{ "foo" => { "foo" => "bar", "bar" => "foo" } }, { "foo" => mask }, %w'f banana'],
+ [{ "deep" => { "cc" => { "code" => "bar", "bar" => "foo" }, "ss" => { "code" => "bar" } } }, { "deep" => { "cc" => { "code" => mask, "bar" => "foo" }, "ss" => { "code" => "bar" } } }, %w'deep.cc.code'],
+ [{ "baz" => [{ "foo" => "baz" }, "1"] }, { "baz" => [{ "foo" => mask }, "1"] }, [/foo/]]]
+
+ test_hashes.each do |before_filter, after_filter, filter_words|
+ parameter_filter = ActiveSupport::ParameterFilter.new(filter_words, mask: mask)
+ assert_equal after_filter, parameter_filter.filter(before_filter)
+
+ filter_words << "blah"
+ filter_words << lambda { |key, value|
+ value.reverse! if key =~ /bargain/
+ }
+ filter_words << lambda { |key, value, original_params|
+ value.replace("world!") if original_params["barg"]["blah"] == "bar" && key == "hello"
+ }
+
+ parameter_filter = ActiveSupport::ParameterFilter.new(filter_words, mask: mask)
+ before_filter["barg"] = { :bargain => "gain", "blah" => "bar", "bar" => { "bargain" => { "blah" => "foo", "hello" => "world" } } }
+ after_filter["barg"] = { :bargain => "niag", "blah" => mask, "bar" => { "bargain" => { "blah" => mask, "hello" => "world!" } } }
+
+ assert_equal after_filter, parameter_filter.filter(before_filter)
+ end
+ end
+
+ test "filter_param" do
+ parameter_filter = ActiveSupport::ParameterFilter.new(["foo", /bar/])
+ assert_equal "[FILTERED]", parameter_filter.filter_param("food", "secret vlaue")
+ assert_equal "[FILTERED]", parameter_filter.filter_param("baz.foo", "secret vlaue")
+ assert_equal "[FILTERED]", parameter_filter.filter_param("barbar", "secret vlaue")
+ assert_equal "non secret value", parameter_filter.filter_param("baz", "non secret value")
+ end
+
+ test "filter_param can work with empty filters" do
+ parameter_filter = ActiveSupport::ParameterFilter.new
+ assert_equal "bar", parameter_filter.filter_param("foo", "bar")
+ end
+
test "parameter filter should maintain hash with indifferent access" do
test_hashes = [
[{ "foo" => "bar" }.with_indifferent_access, ["blah"]],
@@ -48,4 +93,13 @@ class ParameterFilterTest < ActiveSupport::TestCase
parameter_filter.filter(before_filter)
end
end
+
+ test "filter_param should return mask option when value is filtered" do
+ mask = Object.new.freeze
+ parameter_filter = ActiveSupport::ParameterFilter.new(["foo", /bar/], mask: mask)
+ assert_equal mask, parameter_filter.filter_param("food", "secret vlaue")
+ assert_equal mask, parameter_filter.filter_param("baz.foo", "secret vlaue")
+ assert_equal mask, parameter_filter.filter_param("barbar", "secret vlaue")
+ assert_equal "non secret value", parameter_filter.filter_param("baz", "non secret value")
+ end
end
diff --git a/activesupport/test/testing/method_call_assertions_test.rb b/activesupport/test/testing/method_call_assertions_test.rb
index 7438a0490e..669463bd31 100644
--- a/activesupport/test/testing/method_call_assertions_test.rb
+++ b/activesupport/test/testing/method_call_assertions_test.rb
@@ -60,12 +60,6 @@ class MethodCallAssertionsTest < ActiveSupport::TestCase
assert_match(/dang it.\nExpected increment/, error.message)
end
- def test_assert_called_with
- assert_called_with(@object, :increment) do
- @object.increment
- end
- end
-
def test_assert_called_with_arguments
assert_called_with(@object, :<<, [ 2 ]) do
@object << 2
@@ -88,12 +82,6 @@ class MethodCallAssertionsTest < ActiveSupport::TestCase
end
end
- def test_assert_called_with_returns
- assert_called_with(@object, :increment, returns: 1) do
- @object.increment
- end
- end
-
def test_assert_called_with_multiple_expected_arguments
assert_called_with(@object, :<<, [ [ 1 ], [ 2 ] ]) do
@object << 1
diff --git a/guides/source/action_mailer_basics.md b/guides/source/action_mailer_basics.md
index 041a427f7c..1acb993cad 100644
--- a/guides/source/action_mailer_basics.md
+++ b/guides/source/action_mailer_basics.md
@@ -839,13 +839,14 @@ Mailer Testing
You can find detailed instructions on how to test your mailers in the
[testing guide](testing.html#testing-your-mailers).
-Intercepting Emails
+Intercepting and Observing Emails
-------------------
-There are situations where you need to edit an email before it's
-delivered. Fortunately Action Mailer provides hooks to intercept every
-email. You can register an interceptor to make modifications to mail messages
-right before they are handed to the delivery agents.
+Action Mailer provides hooks into the Mail observer and interceptor methods. These allow you to register classes that are called during the mail delivery life cycle of every email sent.
+
+### Intercepting Emails
+
+Interceptors allow you to make modifications to emails before they are handed off to the delivery agents. An interceptor class must implement the `:delivering_email(message)` method which will be called before the email is sent.
```ruby
class SandboxEmailInterceptor
@@ -869,3 +870,21 @@ NOTE: The example above uses a custom environment called "staging" for a
production like server but for testing purposes. You can read
[Creating Rails environments](configuring.html#creating-rails-environments)
for more information about custom Rails environments.
+
+### Observing Emails
+
+Observers give you access to the email message after it has been sent. An observer class must implement the `:delivered_email(message)` method, which will be called after the email is sent.
+
+```ruby
+class EmailDeliveryObserver
+ def self.delivered_email(message)
+ EmailDelivery.log(message)
+ end
+end
+```
+Like interceptors, you need to register observers with the Action Mailer framework. You can do this in an initializer file
+`config/initializers/email_delivery_observer.rb`
+
+```ruby
+ActionMailer::Base.register_observer(EmailDeliveryObserver)
+```
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 551179c523..25c159d471 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -207,46 +207,46 @@
url: 6_0_release_notes.html
description: Release notes for Rails 6.0.
-
- name: 5.2 Release Notes
+ name: Version 5.2 - April 2018
url: 5_2_release_notes.html
description: Release notes for Rails 5.2.
-
- name: 5.1 Release Notes
+ name: Version 5.1 - April 2017
url: 5_1_release_notes.html
description: Release notes for Rails 5.1.
-
- name: 5.0 Release Notes
+ name: Version 5.0 - June 2016
url: 5_0_release_notes.html
description: Release notes for Rails 5.0.
-
- name: 4.2 Release Notes
+ name: Version 4.2 - December 2014
url: 4_2_release_notes.html
description: Release notes for Rails 4.2.
-
- name: 4.1 Release Notes
+ name: Version 4.1 - April 2014
url: 4_1_release_notes.html
description: Release notes for Rails 4.1.
-
- name: 4.0 Release Notes
+ name: Version 4.0 - June 2013
url: 4_0_release_notes.html
description: Release notes for Rails 4.0.
-
- name: 3.2 Release Notes
+ name: Version 3.2 - January 2012
url: 3_2_release_notes.html
description: Release notes for Rails 3.2.
-
- name: 3.1 Release Notes
+ name: Version 3.1 - August 2011
url: 3_1_release_notes.html
description: Release notes for Rails 3.1.
-
- name: 3.0 Release Notes
+ name: Version 3.0 - August 2010
url: 3_0_release_notes.html
description: Release notes for Rails 3.0.
-
- name: 2.3 Release Notes
+ name: Version 2.3 - March 2009
url: 2_3_release_notes.html
description: Release notes for Rails 2.3.
-
- name: 2.2 Release Notes
+ name: Version 2.2 - November 2008
url: 2_2_release_notes.html
description: Release notes for Rails 2.2.
diff --git a/guides/source/i18n.md b/guides/source/i18n.md
index 7465726dca..c61b6ad214 100644
--- a/guides/source/i18n.md
+++ b/guides/source/i18n.md
@@ -664,6 +664,26 @@ I18n.t 'activerecord.errors.messages'
# => {:inclusion=>"is not included in the list", :exclusion=> ... }
```
+If you want to perform interpolation on a bulk hash of translations, you need to pass `deep_interpolation: true` as a parameter. When you have the following dictionary:
+
+```yaml
+en:
+ welcome:
+ title: "Welcome!"
+ content: "Welcome to the %{app_name}"
+```
+
+then the nested interpolation will be ignored without the setting:
+
+```ruby
+I18n.t 'welcome', app_name: 'book store'
+# => {:title=>"Welcome!", :content=>"Welcome to the %{app_name}"}
+
+I18n.t 'welcome', deep_interpolation: true, app_name: 'book store'
+# => {:title=>"Welcome!", :content=>"Welcome to the book store"}
+```
+
+
#### "Lazy" Lookup
Rails implements a convenient way to look up the locale inside _views_. When you have the following dictionary:
diff --git a/railties/lib/rails/app_updater.rb b/railties/lib/rails/app_updater.rb
index a243968a39..19d136e041 100644
--- a/railties/lib/rails/app_updater.rb
+++ b/railties/lib/rails/app_updater.rb
@@ -21,7 +21,7 @@ module Rails
private
def generator_options
options = { api: !!Rails.application.config.api_only, update: true }
- options[:skip_yarn] = !File.exist?(Rails.root.join("bin", "yarn"))
+ options[:skip_javascript] = !File.exist?(Rails.root.join("bin", "yarn"))
options[:skip_active_record] = !defined?(ActiveRecord::Railtie)
options[:skip_active_storage] = !defined?(ActiveStorage::Engine) || !defined?(ActiveRecord::Railtie)
options[:skip_action_mailer] = !defined?(ActionMailer::Railtie)
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index eae902a938..d6f8c4f47c 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -202,7 +202,7 @@ module Rails
"Please note that YAML must be consistently indented using spaces. Tabs are not allowed. " \
"Error: #{e.message}"
rescue => e
- raise e, "Cannot load `Rails.application.database_configuration`:\n#{e.message}", e.backtrace
+ raise e, "Cannot load database configuration:\n#{e.message}", e.backtrace
end
def colorize_logging
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 2e4797e5bb..4dc4d27a46 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -31,9 +31,6 @@ module Rails
class_option :database, type: :string, aliases: "-d", default: "sqlite3",
desc: "Preconfigure for selected database (options: #{DATABASES.join('/')})"
- class_option :skip_yarn, type: :boolean, default: false,
- desc: "Don't use Yarn for managing JavaScript dependencies"
-
class_option :skip_gemfile, type: :boolean, default: false,
desc: "Don't create a Gemfile"
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 1c7cf1a951..f56f79b8d4 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -95,7 +95,7 @@ module Rails
def bin_when_updating
bin
- if options[:skip_yarn]
+ if options[:skip_javascript]
remove_file "bin/yarn"
end
end
@@ -274,7 +274,7 @@ module Rails
# Force sprockets and yarn to be skipped when generating API only apps.
# Can't modify options hash as it's frozen by default.
if options[:api]
- self.options = options.merge(skip_sprockets: true, skip_javascript: true, skip_yarn: true).freeze
+ self.options = options.merge(skip_sprockets: true, skip_javascript: true).freeze
end
end
@@ -289,7 +289,7 @@ module Rails
build(:gitignore) unless options[:skip_git]
build(:gemfile) unless options[:skip_gemfile]
build(:version_control)
- build(:package_json) unless options[:skip_yarn]
+ build(:package_json) unless options[:skip_javascript]
end
def create_app_files
@@ -462,8 +462,8 @@ module Rails
end
end
- def delete_bin_yarn_if_skip_yarn_option
- remove_file "bin/yarn" if options[:skip_yarn]
+ def delete_bin_yarn
+ remove_file "bin/yarn" if options[:skip_javascript]
end
def finish_template
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/setup.tt b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt
index 955a424878..a18e03e7db 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/setup.tt
+++ b/railties/lib/rails/generators/rails/app/templates/bin/setup.tt
@@ -14,9 +14,9 @@ FileUtils.chdir APP_ROOT do
puts '== Installing dependencies =='
system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install')
-<% unless options.skip_yarn? -%>
+<% unless options.skip_javascript? -%>
- # Install JavaScript dependencies if using Yarn
+ # Install JavaScript dependencies
# system('bin/yarn')
<% end -%>
<% unless options.skip_active_record? -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/bin/update.tt b/railties/lib/rails/generators/rails/app/templates/bin/update.tt
index ed17959e1d..03b77d0d46 100644
--- a/railties/lib/rails/generators/rails/app/templates/bin/update.tt
+++ b/railties/lib/rails/generators/rails/app/templates/bin/update.tt
@@ -14,9 +14,9 @@ FileUtils.chdir APP_ROOT do
puts '== Installing dependencies =='
system! 'gem install bundler --conservative'
system('bundle check') || system!('bundle install')
-<% unless options.skip_yarn? -%>
+<% unless options.skip_javascript? -%>
- # Install JavaScript dependencies if using Yarn
+ # Install JavaScript dependencies
# system('bin/yarn')
<% end -%>
<% unless options.skip_active_record? -%>
diff --git a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
index 51196ae743..fe48fc34ee 100644
--- a/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
+++ b/railties/lib/rails/generators/rails/app/templates/config/initializers/assets.rb.tt
@@ -5,10 +5,6 @@ Rails.application.config.assets.version = '1.0'
# Add additional assets to the asset load path.
# Rails.application.config.assets.paths << Emoji.images_path
-<%- unless options[:skip_yarn] -%>
-# Add Yarn node_modules folder to the asset load path.
-Rails.application.config.assets.paths << Rails.root.join('node_modules')
-<%- end -%>
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in the app/assets
diff --git a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
index 532278f215..239b3a5739 100644
--- a/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
+++ b/railties/lib/rails/generators/rails/plugin/plugin_generator.rb
@@ -88,7 +88,7 @@ task default: :test
PASSTHROUGH_OPTIONS = [
:skip_active_record, :skip_active_storage, :skip_action_mailer, :skip_javascript, :skip_action_cable, :skip_sprockets, :database,
- :skip_yarn, :api, :quiet, :pretend, :skip
+ :api, :quiet, :pretend, :skip
]
def generate_test_dummy(force = false)
diff --git a/railties/lib/rails/generators/rails/plugin/templates/gitignore.tt b/railties/lib/rails/generators/rails/plugin/templates/gitignore.tt
index 7a68da5c4b..0aabf09252 100644
--- a/railties/lib/rails/generators/rails/plugin/templates/gitignore.tt
+++ b/railties/lib/rails/generators/rails/plugin/templates/gitignore.tt
@@ -7,7 +7,7 @@ pkg/
<%= dummy_path %>/db/*.sqlite3-journal
<% end -%>
<%= dummy_path %>/log/*.log
-<% unless options[:skip_yarn] -%>
+<% unless options[:skip_javascript] -%>
<%= dummy_path %>/node_modules/
<%= dummy_path %>/yarn-error.log
<% end -%>
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 9b01d42b1e..fa418f564b 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -2109,7 +2109,7 @@ module ApplicationTests
RUBY
app "development"
assert_equal [ :password, :credit_card_number ], Rails.application.config.filter_parameters
- assert_equal [ "password", "credit_card_number" ].to_set, ActiveRecord::Base.filter_attributes
+ assert_equal [ :password, :credit_card_number ], ActiveRecord::Base.filter_attributes
end
test "ActiveStorage.routes_prefix can be configured via config.active_storage.routes_prefix" do
diff --git a/railties/test/generators/app_generator_test.rb b/railties/test/generators/app_generator_test.rb
index f5356a358f..bb3aaa9d14 100644
--- a/railties/test/generators/app_generator_test.rb
+++ b/railties/test/generators/app_generator_test.rb
@@ -300,10 +300,10 @@ class AppGeneratorTest < Rails::Generators::TestCase
def test_app_update_does_not_generate_yarn_contents_when_bin_yarn_is_not_used
app_root = File.join(destination_root, "myapp")
- run_generator [app_root, "--skip-yarn"]
+ run_generator [app_root, "--skip-javascript"]
stub_rails_application(app_root) do
- generator = Rails::Generators::AppGenerator.new ["rails"], { update: true, skip_yarn: true }, { destination_root: app_root, shell: @shell }
+ generator = Rails::Generators::AppGenerator.new ["rails"], { update: true, skip_javascript: true }, { destination_root: app_root, shell: @shell }
generator.send(:app_const)
quietly { generator.send(:update_bin_files) }
diff --git a/railties/test/generators/shared_generator_tests.rb b/railties/test/generators/shared_generator_tests.rb
index 9b980bd52b..2dda856f25 100644
--- a/railties/test/generators/shared_generator_tests.rb
+++ b/railties/test/generators/shared_generator_tests.rb
@@ -336,18 +336,15 @@ module SharedGeneratorTests
end
def test_generator_for_yarn
+ skip "#34009 disabled JS by default for plugins" if generator_class.name == "Rails::Generators::PluginGenerator"
run_generator
assert_file "#{application_path}/package.json", /dependencies/
- assert_file "#{application_path}/config/initializers/assets.rb", /node_modules/
+ assert_file "#{application_path}/bin/yarn"
end
def test_generator_for_yarn_skipped
- run_generator([destination_root, "--skip-yarn"])
+ run_generator([destination_root, "--skip-javascript"])
assert_no_file "#{application_path}/package.json"
assert_no_file "#{application_path}/bin/yarn"
-
- assert_file "#{application_path}/config/initializers/assets.rb" do |content|
- assert_no_match(/node_modules/, content)
- end
end
end