aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.codeclimate.yml2
-rw-r--r--.rubocop.yml13
-rw-r--r--Gemfile3
-rw-r--r--Gemfile.lock46
-rw-r--r--actionmailbox/CHANGELOG.md5
-rw-r--r--actionpack/CHANGELOG.md8
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb30
-rw-r--r--actionpack/test/controller/helper_test.rb4
-rw-r--r--actionpack/test/controller/resources_test.rb2
-rw-r--r--actionpack/test/dispatch/request_test.rb1
-rw-r--r--actionpack/test/dispatch/system_testing/driver_test.rb4
-rw-r--r--actiontext/CHANGELOG.md26
-rw-r--r--actionview/test/abstract_unit.rb2
-rw-r--r--actionview/test/actionpack/abstract/layouts_test.rb2
-rw-r--r--actionview/test/active_record_unit.rb10
-rw-r--r--actionview/test/template/compiled_templates_test.rb40
-rw-r--r--activerecord/CHANGELOG.md13
-rw-r--r--activerecord/lib/active_record/attribute_methods.rb53
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb22
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/quoting.rb37
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_adapter.rb14
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql/quoting.rb31
-rw-r--r--activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb22
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb1
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb20
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb4
-rw-r--r--activerecord/lib/active_record/connection_handling.rb8
-rw-r--r--activerecord/lib/active_record/railtie.rb1
-rw-r--r--activerecord/lib/active_record/railties/databases.rake40
-rw-r--r--activerecord/lib/active_record/relation/query_methods.rb2
-rw-r--r--activerecord/lib/active_record/sanitization.rb33
-rw-r--r--activerecord/lib/active_record/tasks/database_tasks.rb28
-rw-r--r--activerecord/test/cases/adapters/sqlite3/collation_test.rb9
-rw-r--r--activerecord/test/cases/associations/eager_test.rb4
-rw-r--r--activerecord/test/cases/connection_adapters/connection_handler_test.rb13
-rw-r--r--activerecord/test/cases/connection_pool_test.rb1
-rw-r--r--activerecord/test/cases/relations_test.rb4
-rw-r--r--activerecord/test/cases/unsafe_raw_sql_test.rb30
-rw-r--r--activestorage/CHANGELOG.md83
-rw-r--r--activesupport/CHANGELOG.md17
-rw-r--r--activesupport/lib/active_support/evented_file_update_checker.rb5
-rw-r--r--activesupport/lib/active_support/messages/rotator.rb9
-rw-r--r--activesupport/test/message_encryptor_test.rb28
-rw-r--r--guides/source/2_3_release_notes.md2
-rw-r--r--guides/source/6_0_release_notes.md2
-rw-r--r--guides/source/active_record_multiple_databases.md8
-rw-r--r--guides/source/active_storage_overview.md2
-rw-r--r--guides/source/configuring.md2
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md4
-rw-r--r--guides/source/documents.yaml2
-rw-r--r--guides/source/getting_started.md2
-rw-r--r--guides/source/upgrading_ruby_on_rails.md8
-rw-r--r--railties/lib/rails/application/bootstrap.rb12
-rw-r--r--railties/lib/rails/application/configuration.rb12
-rw-r--r--railties/lib/rails/generators/rails/app/app_generator.rb3
-rw-r--r--railties/test/application/configuration_test.rb7
-rw-r--r--railties/test/application/loading_test.rb6
-rw-r--r--railties/test/application/rake/multi_dbs_test.rb20
-rw-r--r--railties/test/isolation/abstract_unit.rb42
60 files changed, 568 insertions, 297 deletions
diff --git a/.codeclimate.yml b/.codeclimate.yml
index 7114a98266..952b330d8c 100644
--- a/.codeclimate.yml
+++ b/.codeclimate.yml
@@ -25,6 +25,6 @@ checks:
plugins:
rubocop:
enabled: true
- channel: rubocop-0-67
+ channel: rubocop-0-71
exclude_patterns: []
diff --git a/.rubocop.yml b/.rubocop.yml
index 0cfe5d5d84..22161b1e16 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -1,4 +1,6 @@
-require: rubocop-performance
+require:
+ - rubocop-performance
+ - rubocop-rails
AllCops:
TargetRubyVersion: 2.5
@@ -18,9 +20,6 @@ Performance:
Exclude:
- '**/test/**/*'
-Rails:
- Enabled: true
-
# Prefer assert_not over assert !
Rails/AssertNot:
Include:
@@ -77,13 +76,13 @@ Layout/EmptyLinesAroundMethodBody:
Layout/EmptyLinesAroundModuleBody:
Enabled: true
-Layout/FirstParameterIndentation:
- Enabled: true
-
# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }.
Style/HashSyntax:
Enabled: true
+Layout/IndentFirstArgument:
+ Enabled: true
+
# Method definitions after `private` or `protected` isolated calls need one
# extra level of indentation.
Layout/IndentationConsistency:
diff --git a/Gemfile b/Gemfile
index 69f277be29..9eb449316c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -10,7 +10,7 @@ gemspec
gem "rake", ">= 11.1"
gem "capybara", ">= 2.15"
-gem "selenium-webdriver", ">= 3.5.0", "< 3.13.0"
+gem "selenium-webdriver", ">= 3.5.0"
gem "rack-cache", "~> 1.2"
gem "sass-rails"
@@ -30,6 +30,7 @@ gem "json", ">= 2.0.0"
gem "rubocop", ">= 0.47", require: false
gem "rubocop-performance", require: false
+gem "rubocop-rails", require: false
group :doc do
gem "sdoc", "~> 1.0"
diff --git a/Gemfile.lock b/Gemfile.lock
index 6633c7a741..39ea426ed9 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -183,8 +183,8 @@ GEM
rack-test (>= 0.6.3)
regexp_parser (~> 1.2)
xpath (~> 3.2)
- childprocess (0.9.0)
- ffi (~> 1.0, >= 1.0.11)
+ childprocess (1.0.1)
+ rake (< 13.0)
coffee-script (2.4.1)
coffee-script-source
execjs
@@ -236,10 +236,10 @@ GEM
faye-websocket (0.10.7)
eventmachine (>= 0.12.0)
websocket-driver (>= 0.5.1)
- ffi (1.10.0)
- ffi (1.10.0-java)
- ffi (1.10.0-x64-mingw32)
- ffi (1.10.0-x86-mingw32)
+ ffi (1.11.1)
+ ffi (1.11.1-java)
+ ffi (1.11.1-x64-mingw32)
+ ffi (1.11.1-x86-mingw32)
fugit (1.2.1)
et-orbi (~> 1.1, >= 1.1.8)
raabro (~> 1.1)
@@ -279,7 +279,6 @@ GEM
image_processing (1.7.1)
mini_magick (~> 4.0)
ruby-vips (>= 2.0.13, < 3)
- jar-dependencies (0.4.0)
jaro_winkler (1.5.2)
jaro_winkler (1.5.2-java)
jdbc-mysql (5.1.46)
@@ -349,18 +348,14 @@ GEM
nokogiri (1.9.1-x86-mingw32)
mini_portile2 (~> 2.4.0)
os (1.0.0)
- parallel (1.13.0)
- parser (2.6.2.0)
+ parallel (1.17.0)
+ parser (2.6.3.0)
ast (~> 2.4.0)
path_expander (1.0.3)
pg (1.1.3)
pg (1.1.3-x64-mingw32)
pg (1.1.3-x86-mingw32)
psych (3.1.0)
- psych (3.1.0-java)
- jar-dependencies (>= 0.1.7)
- psych (3.1.0-x64-mingw32)
- psych (3.1.0-x86-mingw32)
public_suffix (3.0.3)
puma (3.12.1)
puma (3.12.1-java)
@@ -411,21 +406,23 @@ GEM
resque (>= 1.26)
rufus-scheduler (~> 3.2)
retriable (3.1.2)
- rubocop (0.67.2)
+ rubocop (0.71.0)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
- parser (>= 2.5, != 2.5.1.1)
- psych (>= 3.1.0)
+ parser (>= 2.6)
rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.7)
- unicode-display_width (>= 1.4.0, < 1.6)
+ unicode-display_width (>= 1.4.0, < 1.7)
rubocop-performance (1.1.0)
rubocop (>= 0.67.0)
- ruby-progressbar (1.10.0)
+ rubocop-rails (2.0.0)
+ rack (>= 2.0)
+ rubocop (>= 0.70.0)
+ ruby-progressbar (1.10.1)
ruby-vips (2.0.13)
ffi (~> 1.9)
ruby_dep (1.5.0)
- rubyzip (1.2.2)
+ rubyzip (1.2.3)
rufus-scheduler (3.6.0)
fugit (~> 1.1, >= 1.1.6)
safe_yaml (1.0.4)
@@ -442,9 +439,9 @@ GEM
tilt (>= 1.1, < 3)
sdoc (1.0.0)
rdoc (>= 5.0)
- selenium-webdriver (3.12.0)
- childprocess (~> 0.5)
- rubyzip (~> 1.2)
+ selenium-webdriver (3.142.3)
+ childprocess (>= 0.5, < 2.0)
+ rubyzip (~> 1.2, >= 1.2.2)
sequel (5.14.0)
serverengine (2.0.7)
sigdump (~> 0.2.2)
@@ -499,7 +496,7 @@ GEM
uber (0.1.0)
uglifier (4.1.19)
execjs (>= 0.3.0, < 3)
- unicode-display_width (1.5.0)
+ unicode-display_width (1.6.0)
useragent (0.16.10)
vegas (0.1.11)
rack (>= 1.0.0)
@@ -584,9 +581,10 @@ DEPENDENCIES
resque-scheduler
rubocop (>= 0.47)
rubocop-performance
+ rubocop-rails
sass-rails
sdoc (~> 1.0)
- selenium-webdriver (>= 3.5.0, < 3.13.0)
+ selenium-webdriver (>= 3.5.0)
sequel
sidekiq
sneakers
diff --git a/actionmailbox/CHANGELOG.md b/actionmailbox/CHANGELOG.md
index 605a38b06b..bca3d1d9ed 100644
--- a/actionmailbox/CHANGELOG.md
+++ b/actionmailbox/CHANGELOG.md
@@ -1,5 +1,6 @@
-* Add `ApplicationMailbox.mailbox_for` to expose mailbox routing.
+* Add `ApplicationMailbox.mailbox_for` to expose mailbox routing.
+
+ *James Dabbs*
- *James Dabbs*
Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/actionmailbox/CHANGELOG.md) for previous changes.
diff --git a/actionpack/CHANGELOG.md b/actionpack/CHANGELOG.md
index 4bcf313ecc..55592585ea 100644
--- a/actionpack/CHANGELOG.md
+++ b/actionpack/CHANGELOG.md
@@ -1,11 +1,11 @@
-* Keep part when scope option has value
+* Keep part when scope option has value.
When a route was defined within an optional scope, if that route didn't
take parameters the scope was lost when using path helpers. This commit
ensures scope is kept both when the route takes parameters or when it
doesn't.
- Fixes #33219
+ Fixes #33219.
*Alberto Almagro*
@@ -18,8 +18,10 @@
*Eugene Kenny*
-* Fix strong parameters blocks all attributes even when only some keys are invalid (non-numerical). It should only block invalid key's values instead.
+* Fix strong parameters blocks all attributes even when only some keys are invalid (non-numerical).
+ It should only block invalid key's values instead.
*Stan Lo*
+
Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/actionpack/CHANGELOG.md) for previous changes.
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb
index 3913259ecc..abb09456e0 100644
--- a/actionpack/lib/abstract_controller/helpers.rb
+++ b/actionpack/lib/abstract_controller/helpers.rb
@@ -7,7 +7,7 @@ module AbstractController
extend ActiveSupport::Concern
included do
- class_attribute :_helpers, default: Module.new
+ class_attribute :_helpers, default: define_helpers_module(self)
class_attribute :_helper_methods, default: Array.new
end
@@ -31,7 +31,7 @@ module AbstractController
# independently of the child class's.
def inherited(klass)
helpers = _helpers
- klass._helpers = Module.new { include helpers }
+ klass._helpers = define_helpers_module(klass, helpers)
klass.class_eval { default_helper_module! } unless klass.anonymous?
super
end
@@ -61,12 +61,17 @@ module AbstractController
meths.flatten!
self._helper_methods += meths
+ location = caller_locations(1, 1).first
+ file, line = location.path, location.lineno
+
meths.each do |meth|
- _helpers.class_eval <<-ruby_eval, __FILE__, __LINE__ + 1
- def #{meth}(*args, &blk) # def current_user(*args, &blk)
- controller.send(%(#{meth}), *args, &blk) # controller.send(:current_user, *args, &blk)
- end # end
- ruby_eval
+ method_def = [
+ "def #{meth}(*args, &blk)",
+ " controller.send(%(#{meth}), *args, &blk)",
+ "end"
+ ].join(";")
+
+ _helpers.class_eval method_def, file, line
end
end
@@ -170,6 +175,17 @@ module AbstractController
end
private
+ def define_helpers_module(klass, helpers = nil)
+ # In some tests inherited is called explicitly. In that case, just
+ # return the module from the first time it was defined
+ return klass.const_get(:HelperMethods) if klass.const_defined?(:HelperMethods, false)
+
+ mod = Module.new
+ klass.const_set(:HelperMethods, mod)
+ mod.include(helpers) if helpers
+ mod
+ end
+
# Makes all the (instance) methods in the helper module available to templates
# rendered through this controller.
#
diff --git a/actionpack/test/controller/helper_test.rb b/actionpack/test/controller/helper_test.rb
index de8072a994..93a2ba1071 100644
--- a/actionpack/test/controller/helper_test.rb
+++ b/actionpack/test/controller/helper_test.rb
@@ -150,8 +150,8 @@ class HelperTest < ActiveSupport::TestCase
end
def test_default_helpers_only
- assert_equal [JustMeHelper], JustMeController._helpers.ancestors.reject(&:anonymous?)
- assert_equal [MeTooHelper, JustMeHelper], MeTooController._helpers.ancestors.reject(&:anonymous?)
+ assert_equal %w[JustMeHelper], JustMeController._helpers.ancestors.reject(&:anonymous?).map(&:to_s)
+ assert_equal %w[MeTooController::HelperMethods MeTooHelper JustMeHelper], MeTooController._helpers.ancestors.reject(&:anonymous?).map(&:to_s)
end
def test_base_helper_methods_after_clear_helpers
diff --git a/actionpack/test/controller/resources_test.rb b/actionpack/test/controller/resources_test.rb
index d2146f12a5..1e42a3a90d 100644
--- a/actionpack/test/controller/resources_test.rb
+++ b/actionpack/test/controller/resources_test.rb
@@ -36,7 +36,6 @@ class ResourcesTest < ActionController::TestCase
collection: collection_methods,
member: member_methods,
path_names: path_names do
-
assert_restful_routes_for :messages,
collection: collection_methods,
member: member_methods,
@@ -58,7 +57,6 @@ class ResourcesTest < ActionController::TestCase
collection: collection_methods,
member: member_methods,
path_names: path_names do |options|
-
collection_methods.each_key do |action|
assert_named_route "/messages/#{path_names[action] || action}", "#{action}_messages_path", action: action
end
diff --git a/actionpack/test/dispatch/request_test.rb b/actionpack/test/dispatch/request_test.rb
index eb49396145..0ec8dd25e0 100644
--- a/actionpack/test/dispatch/request_test.rb
+++ b/actionpack/test/dispatch/request_test.rb
@@ -681,7 +681,6 @@ end
class RequestMethod < BaseRequestTest
test "method returns environment's request method when it has not been
overridden by middleware".squish do
-
ActionDispatch::Request::HTTP_METHODS.each do |method|
request = stub_request("REQUEST_METHOD" => method)
diff --git a/actionpack/test/dispatch/system_testing/driver_test.rb b/actionpack/test/dispatch/system_testing/driver_test.rb
index 0d08f17af3..7ef306d04b 100644
--- a/actionpack/test/dispatch/system_testing/driver_test.rb
+++ b/actionpack/test/dispatch/system_testing/driver_test.rb
@@ -66,7 +66,7 @@ class DriverTest < ActiveSupport::TestCase
end
driver.use
- expected = { args: ["start-maximized"], mobileEmulation: { deviceName: "iphone 6" }, prefs: { detach: true } }
+ expected = { "goog:chromeOptions" => { args: ["start-maximized"], mobileEmulation: { deviceName: "iphone 6" }, prefs: { detach: true } } }
assert_equal expected, driver_option.as_json
end
@@ -81,7 +81,7 @@ class DriverTest < ActiveSupport::TestCase
end
driver.use
- expected = { args: ["start-maximized"], mobileEmulation: { deviceName: "iphone 6" }, prefs: { detach: true } }
+ expected = { "goog:chromeOptions" => { args: ["start-maximized"], mobileEmulation: { deviceName: "iphone 6" }, prefs: { detach: true } } }
assert_equal expected, driver_option.as_json
end
diff --git a/actiontext/CHANGELOG.md b/actiontext/CHANGELOG.md
index 9a314db75c..d28799279f 100644
--- a/actiontext/CHANGELOG.md
+++ b/actiontext/CHANGELOG.md
@@ -1,21 +1,21 @@
-* The `fill_in_rich_text_area` system test helper locates a Trix editor and fills it in with the given HTML:
+* The `fill_in_rich_text_area` system test helper locates a Trix editor and fills it in with the given HTML:
- ```ruby
- # <trix-editor id="message_content" ...></trix-editor>
- fill_in_rich_text_area "message_content", with: "Hello <em>world!</em>"
+ ```ruby
+ # <trix-editor id="message_content" ...></trix-editor>
+ fill_in_rich_text_area "message_content", with: "Hello <em>world!</em>"
- # <trix-editor placeholder="Your message here" ...></trix-editor>
- fill_in_rich_text_area "Your message here", with: "Hello <em>world!</em>"
+ # <trix-editor placeholder="Your message here" ...></trix-editor>
+ fill_in_rich_text_area "Your message here", with: "Hello <em>world!</em>"
- # <trix-editor aria-label="Message content" ...></trix-editor>
- fill_in_rich_text_area "Message content", with: "Hello <em>world!</em>"
+ # <trix-editor aria-label="Message content" ...></trix-editor>
+ fill_in_rich_text_area "Message content", with: "Hello <em>world!</em>"
- # <input id="trix_input_1" name="message[content]" type="hidden">
- # <trix-editor input="trix_input_1"></trix-editor>
- fill_in_rich_text_area "message[content]", with: "Hello <em>world!</em>"
- ```
+ # <input id="trix_input_1" name="message[content]" type="hidden">
+ # <trix-editor input="trix_input_1"></trix-editor>
+ fill_in_rich_text_area "message[content]", with: "Hello <em>world!</em>"
+ ```
- *George Claghorn*
+ *George Claghorn*
Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/actiontext/CHANGELOG.md) for previous changes.
diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb
index 2c1b277b41..b652f2e6cb 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -192,6 +192,8 @@ module ActionDispatch
end
class ActiveSupport::TestCase
+ parallelize
+
include ActiveSupport::Testing::MethodCallAssertions
private
diff --git a/actionview/test/actionpack/abstract/layouts_test.rb b/actionview/test/actionpack/abstract/layouts_test.rb
index 1146e6f64b..72d8e54bf8 100644
--- a/actionview/test/actionpack/abstract/layouts_test.rb
+++ b/actionview/test/actionpack/abstract/layouts_test.rb
@@ -295,7 +295,7 @@ module AbstractControllerTests
10.times do |x|
controller = WithString.new
controller.define_singleton_method :index do
- render template: ActionView::Template::Text.new("Hello string!"), locals: { :"x#{x}" => :omg }
+ render template: ActionView::Template::Text.new("Hello string!"), locals: { "x#{x}": :omg }
end
controller.process(:index)
end
diff --git a/actionview/test/active_record_unit.rb b/actionview/test/active_record_unit.rb
index e4ea6a426d..4efb31a360 100644
--- a/actionview/test/active_record_unit.rb
+++ b/actionview/test/active_record_unit.rb
@@ -42,6 +42,12 @@ class ActiveRecordTestConnector
self.able_to_connect = false
end
+ def reconnect
+ return unless able_to_connect
+ ActiveRecord::Base.connection.reconnect!
+ load_schema
+ end
+
private
def setup_connection
if Object.const_defined?(:ActiveRecord)
@@ -102,3 +108,7 @@ class ActiveRecordTestCase < ActionController::TestCase
end
ActiveRecordTestConnector.setup
+
+ActiveSupport::Testing::Parallelization.after_fork_hook do
+ ActiveRecordTestConnector.reconnect
+end
diff --git a/actionview/test/template/compiled_templates_test.rb b/actionview/test/template/compiled_templates_test.rb
index d7f2e8ee07..59fa058bed 100644
--- a/actionview/test/template/compiled_templates_test.rb
+++ b/actionview/test/template/compiled_templates_test.rb
@@ -60,46 +60,8 @@ class CompiledTemplatesTest < ActiveSupport::TestCase
assert_equal "two", render(template: "test/render_file_with_locals_and_default", locals: { secret: "two" })
end
- def test_template_changes_are_not_reflected_with_cached_templates
- assert_equal "Hello world!", render(template: "test/hello_world")
- modify_template "test/hello_world.erb", "Goodbye world!" do
- assert_equal "Hello world!", render(template: "test/hello_world")
- end
- assert_equal "Hello world!", render(template: "test/hello_world")
- end
-
- def test_template_changes_are_reflected_with_uncached_templates
- assert_equal "Hello world!", render_without_cache(template: "test/hello_world")
- modify_template "test/hello_world.erb", "Goodbye world!" do
- assert_equal "Goodbye world!", render_without_cache(template: "test/hello_world")
- end
- assert_equal "Hello world!", render_without_cache(template: "test/hello_world")
- end
-
private
def render(*args)
- render_with_cache(*args)
- end
-
- def render_with_cache(*args)
- view_paths = ActionController::Base.view_paths
- view_class.with_view_paths(view_paths, {}).render(*args)
- end
-
- def render_without_cache(*args)
- path = ActionView::FileSystemResolver.new(FIXTURE_LOAD_PATH)
- view_paths = ActionView::PathSet.new([path])
- view_class.with_view_paths(view_paths, {}).render(*args)
- end
-
- def modify_template(template, content)
- filename = "#{FIXTURE_LOAD_PATH}/#{template}"
- old_content = File.read(filename)
- begin
- File.open(filename, "wb+") { |f| f.write(content) }
- yield
- ensure
- File.open(filename, "wb+") { |f| f.write(old_content) }
- end
+ ActionController::Base.render(*args)
end
end
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index 6f08b1b8fe..09fdc66788 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,15 +1,20 @@
-* Fix invalid schema when primary key column has a comment
+* Fix sqlite3 collation parsing when using decimal columns.
- Fixes #29966
+ *Martin R. Schuster*
+
+* Fix invalid schema when primary key column has a comment.
+
+ Fixes #29966.
*Guilherme Goettems Schneider*
-* Fix table comment also being applied to the primary key column
+* Fix table comment also being applied to the primary key column.
*Guilherme Goettems Schneider*
* Allow generated `create_table` migrations to include or skip timestamps.
- *Michael Duchemin*
+ *Michael Duchemin*
+
Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activerecord/CHANGELOG.md) for previous changes.
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb
index fd32eaaf3a..21f72bb6c7 100644
--- a/activerecord/lib/active_record/attribute_methods.rb
+++ b/activerecord/lib/active_record/attribute_methods.rb
@@ -159,59 +159,6 @@ module ActiveRecord
end
end
- # Regexp for column names (with or without a table name prefix). Matches
- # the following:
- # "#{table_name}.#{column_name}"
- # "#{column_name}"
- COLUMN_NAME = /\A(?:\w+\.)?\w+\z/i
-
- # Regexp for column names with order (with or without a table name
- # prefix, with or without various order modifiers). Matches the following:
- # "#{table_name}.#{column_name}"
- # "#{table_name}.#{column_name} #{direction}"
- # "#{table_name}.#{column_name} #{direction} NULLS FIRST"
- # "#{table_name}.#{column_name} NULLS LAST"
- # "#{column_name}"
- # "#{column_name} #{direction}"
- # "#{column_name} #{direction} NULLS FIRST"
- # "#{column_name} NULLS LAST"
- COLUMN_NAME_WITH_ORDER = /
- \A
- (?:\w+\.)?
- \w+
- (?:\s+asc|\s+desc)?
- (?:\s+nulls\s+(?:first|last))?
- \z
- /ix
-
- def disallow_raw_sql!(args, permit: COLUMN_NAME) # :nodoc:
- unexpected = nil
- args.each do |arg|
- next if arg.is_a?(Symbol) || Arel.arel_node?(arg) ||
- arg.to_s.split(/\s*,\s*/).all? { |part| permit.match?(part) }
- (unexpected ||= []) << arg
- end
-
- return unless unexpected
-
- if allow_unsafe_raw_sql == :deprecated
- ActiveSupport::Deprecation.warn(
- "Dangerous query method (method whose arguments are used as raw " \
- "SQL) called with non-attribute argument(s): " \
- "#{unexpected.map(&:inspect).join(", ")}. Non-attribute " \
- "arguments will be disallowed in Rails 6.1. This method should " \
- "not be called with user-provided values, such as request " \
- "parameters or model attributes. Known-safe values can be passed " \
- "by wrapping them in Arel.sql()."
- )
- else
- raise(ActiveRecord::UnknownAttributeReference,
- "Query method called with non-attribute argument(s): " +
- unexpected.map(&:inspect).join(", ")
- )
- end
- end
-
# Returns true if the given attribute exists, otherwise false.
#
# class Person < ActiveRecord::Base
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
index 7628ef5537..7dd5169e43 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb
@@ -20,6 +20,26 @@ module ActiveRecord
end
module ConnectionAdapters
+ module AbstractPool # :nodoc:
+ def get_schema_cache(connection)
+ @schema_cache ||= SchemaCache.new(connection)
+ @schema_cache.connection = connection
+ @schema_cache
+ end
+
+ def set_schema_cache(cache)
+ @schema_cache = cache
+ end
+ end
+
+ class NullPool # :nodoc:
+ include ConnectionAdapters::AbstractPool
+
+ def initialize
+ @schema_cache = nil
+ end
+ end
+
# Connection pool base class for managing Active Record database
# connections.
#
@@ -336,6 +356,7 @@ module ActiveRecord
include MonitorMixin
include QueryCache::ConnectionPoolConfiguration
+ include ConnectionAdapters::AbstractPool
attr_accessor :automatic_reconnect, :checkout_timeout, :schema_cache
attr_reader :spec, :connections, :size, :reaper
@@ -837,7 +858,6 @@ module ActiveRecord
def new_connection
Base.send(spec.adapter_method, spec.config).tap do |conn|
- conn.schema_cache = schema_cache.dup if schema_cache
conn.check_version
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
index 2877530917..99e1a11f30 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/quoting.rb
@@ -142,6 +142,43 @@ module ActiveRecord
value.to_s.gsub(%r{ (/ (?: | \g<1>) \*) \+? \s* | \s* (\* (?: | \g<2>) /) }x, "")
end
+ def column_name_matcher # :nodoc:
+ COLUMN_NAME
+ end
+
+ def column_name_with_order_matcher # :nodoc:
+ COLUMN_NAME_WITH_ORDER
+ end
+
+ # Regexp for column names (with or without a table name prefix).
+ # Matches the following:
+ #
+ # "#{table_name}.#{column_name}"
+ # "#{column_name}"
+ COLUMN_NAME = /\A(?:\w+\.)?\w+\z/i
+
+ # Regexp for column names with order (with or without a table name prefix,
+ # with or without various order modifiers). Matches the following:
+ #
+ # "#{table_name}.#{column_name}"
+ # "#{table_name}.#{column_name} #{direction}"
+ # "#{table_name}.#{column_name} #{direction} NULLS FIRST"
+ # "#{table_name}.#{column_name} NULLS LAST"
+ # "#{column_name}"
+ # "#{column_name} #{direction}"
+ # "#{column_name} #{direction} NULLS FIRST"
+ # "#{column_name} NULLS LAST"
+ COLUMN_NAME_WITH_ORDER = /
+ \A
+ (?:\w+\.)?
+ \w+
+ (?:\s+ASC|\s+DESC)?
+ (?:\s+NULLS\s+(?:FIRST|LAST))?
+ \z
+ /ix
+
+ private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
+
private
def type_casted_binds(binds)
if binds.first.is_a?(Array)
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
index bf0bb84c93..57a8e7ad98 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_adapter.rb
@@ -78,7 +78,7 @@ module ActiveRecord
SIMPLE_INT = /\A\d+\z/
attr_accessor :pool
- attr_reader :schema_cache, :visitor, :owner, :logger, :lock, :prepared_statements, :prevent_writes
+ attr_reader :visitor, :owner, :logger, :lock, :prepared_statements, :prevent_writes
alias :in_use? :owner
set_callback :checkin, :after, :enable_lazy_transactions!
@@ -114,9 +114,8 @@ module ActiveRecord
@instrumenter = ActiveSupport::Notifications.instrumenter
@logger = logger
@config = config
- @pool = nil
+ @pool = ActiveRecord::ConnectionAdapters::NullPool.new
@idle_since = Concurrent.monotonic_time
- @schema_cache = SchemaCache.new self
@quoted_column_names, @quoted_table_names = {}, {}
@prevent_writes = false
@visitor = arel_visitor
@@ -206,9 +205,13 @@ module ActiveRecord
@owner = Thread.current
end
+ def schema_cache
+ @pool.get_schema_cache(self)
+ end
+
def schema_cache=(cache)
cache.connection = self
- @schema_cache = cache
+ @pool.set_schema_cache(cache)
end
# this method must only be called while holding connection pool's mutex
@@ -487,6 +490,9 @@ module ActiveRecord
#
# Prevent @connection's finalizer from touching the socket, or
# otherwise communicating with its server, when it is collected.
+ if schema_cache.connection == self
+ schema_cache.connection = nil
+ end
end
# Reset the state of this connection, directing the DBMS to clear
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
index 75564a61d6..84354c0187 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/quoting.rb
@@ -32,12 +32,33 @@ module ActiveRecord
"x'#{value.hex}'"
end
- def _type_cast(value)
- case value
- when Date, Time then value
- else super
- end
+ def column_name_matcher
+ COLUMN_NAME
+ end
+
+ def column_name_with_order_matcher
+ COLUMN_NAME_WITH_ORDER
end
+
+ COLUMN_NAME = /\A(?:(`?)\w+\k<1>\.)?(`?)\w+\k<2>\z/i
+
+ COLUMN_NAME_WITH_ORDER = /
+ \A
+ (?:(`?)\w+\k<1>\.)?
+ (`?)\w+\k<2>
+ (?:\s+ASC|\s+DESC)?
+ \z
+ /ix
+
+ private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
+
+ private
+ def _type_cast(value)
+ case value
+ when Date, Time then value
+ else super
+ end
+ end
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
index 5b0335c22b..f1ee6a2bce 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql2_adapter.rb
@@ -109,6 +109,7 @@ module ActiveRecord
end
def discard! # :nodoc:
+ super
@connection.automatic_close = false
@connection = nil
end
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
index d40e0ef1f0..0ebed21717 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/quoting.rb
@@ -78,6 +78,28 @@ module ActiveRecord
type_map.lookup(column.oid, column.fmod, column.sql_type)
end
+ def column_name_matcher
+ COLUMN_NAME
+ end
+
+ def column_name_with_order_matcher
+ COLUMN_NAME_WITH_ORDER
+ end
+
+ COLUMN_NAME = /\A(?:("?)\w+\k<1>\.)?("?)\w+\k<2>(?:::\w+)?\z/i
+
+ COLUMN_NAME_WITH_ORDER = /
+ \A
+ (?:("?)\w+\k<1>\.)?
+ ("?)\w+\k<2>
+ (?:::\w+)?
+ (?:\s+ASC|\s+DESC)?
+ (?:\s+NULLS\s+(?:FIRST|LAST))?
+ \z
+ /ix
+
+ private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
+
private
def lookup_cast_type(sql_type)
super(query_value("SELECT #{quote(sql_type)}::regtype::oid", "SCHEMA").to_i)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
index 91318a0af1..738805a160 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb
@@ -302,6 +302,7 @@ module ActiveRecord
end
def discard! # :nodoc:
+ super
@connection.socket_io.reopen(IO::NULL) rescue nil
@connection = nil
end
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
index cb9d32a577..79d477cdb2 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/quoting.rb
@@ -45,6 +45,26 @@ module ActiveRecord
0
end
+ def column_name_matcher
+ COLUMN_NAME
+ end
+
+ def column_name_with_order_matcher
+ COLUMN_NAME_WITH_ORDER
+ end
+
+ COLUMN_NAME = /\A(?:("?)\w+\k<1>\.)?("?)\w+\k<2>\z/i
+
+ COLUMN_NAME_WITH_ORDER = /
+ \A
+ (?:("?)\w+\k<1>\.)?
+ ("?)\w+\k<2>
+ (?:\s+ASC|\s+DESC)?
+ \z
+ /ix
+
+ private_constant :COLUMN_NAME, :COLUMN_NAME_WITH_ORDER
+
private
def _type_cast(value)
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
index 7f3f32162e..9fc5ee3ab4 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3_adapter.rb
@@ -477,9 +477,9 @@ module ActiveRecord
result = exec_query(sql, "SCHEMA").first
if result
- # Splitting with left parentheses and picking up last will return all
+ # Splitting with left parentheses and discarding the first part will return all
# columns separated with comma(,).
- columns_string = result["sql"].split("(").last
+ columns_string = result["sql"].split("(", 2).last
columns_string.split(",").each do |column_string|
# This regex will match the column name and collation type and will save
diff --git a/activerecord/lib/active_record/connection_handling.rb b/activerecord/lib/active_record/connection_handling.rb
index 040ebdb960..d472eb2e2b 100644
--- a/activerecord/lib/active_record/connection_handling.rb
+++ b/activerecord/lib/active_record/connection_handling.rb
@@ -173,7 +173,7 @@ module ActiveRecord
raise "Anonymous class is not allowed." unless name
config_or_env ||= DEFAULT_ENV.call.to_sym
- pool_name = self == Base ? "primary" : name
+ pool_name = primary_class? ? "primary" : name
self.connection_specification_name = pool_name
resolver = ConnectionAdapters::ConnectionSpecification::Resolver.new(Base.configurations)
@@ -204,11 +204,15 @@ module ActiveRecord
# Return the specification name from the current class or its parent.
def connection_specification_name
if !defined?(@connection_specification_name) || @connection_specification_name.nil?
- return self == Base ? "primary" : superclass.connection_specification_name
+ return primary_class? ? "primary" : superclass.connection_specification_name
end
@connection_specification_name
end
+ def primary_class? # :nodoc:
+ self == Base || defined?(ApplicationRecord) && self == ApplicationRecord
+ end
+
# Returns the configuration of the associated connection as a hash:
#
# ActiveRecord::Base.connection_config
diff --git a/activerecord/lib/active_record/railtie.rb b/activerecord/lib/active_record/railtie.rb
index a1d7c893bf..d5375390c7 100644
--- a/activerecord/lib/active_record/railtie.rb
+++ b/activerecord/lib/active_record/railtie.rb
@@ -134,7 +134,6 @@ end_error
cache = YAML.load(File.read(filename))
if cache.version == current_version
- connection.schema_cache = cache
connection_pool.schema_cache = cache.dup
else
warn "Ignoring db/schema_cache.yml because it has expired. The current schema version is #{current_version}, but the one in the cache is #{cache.version}."
diff --git a/activerecord/lib/active_record/railties/databases.rake b/activerecord/lib/active_record/railties/databases.rake
index e0bc5180c0..befcbc8984 100644
--- a/activerecord/lib/active_record/railties/databases.rake
+++ b/activerecord/lib/active_record/railties/databases.rake
@@ -266,12 +266,31 @@ db_namespace = namespace :db do
desc "Runs setup if database does not exist, or runs migrations if it does"
task prepare: :load_config do
+ seed = false
+
ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
ActiveRecord::Base.establish_connection(db_config.config)
- db_namespace["migrate"].invoke
+
+ ActiveRecord::Tasks::DatabaseTasks.migrate
+
+ # Skipped when no database
+ ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config.config, ActiveRecord::Base.schema_format, db_config.spec_name)
+
rescue ActiveRecord::NoDatabaseError
- db_namespace["setup"].invoke
+ ActiveRecord::Tasks::DatabaseTasks.create_current(db_config.env_name, db_config.spec_name)
+ ActiveRecord::Tasks::DatabaseTasks.load_schema(
+ db_config.config,
+ ActiveRecord::Base.schema_format,
+ nil,
+ db_config.env_name,
+ db_config.spec_name
+ )
+
+ seed = true
end
+
+ ActiveRecord::Base.establish_connection
+ ActiveRecord::Tasks::DatabaseTasks.load_seed if seed
end
desc "Loads the seed data from db/seeds.rb"
@@ -336,13 +355,9 @@ db_namespace = namespace :db do
namespace :schema do
desc "Creates a db/schema.rb file that is portable against any DB supported by Active Record"
task dump: :load_config do
- require "active_record/schema_dumper"
ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
- filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(db_config.spec_name, :ruby)
- File.open(filename, "w:utf-8") do |file|
- ActiveRecord::Base.establish_connection(db_config.config)
- ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
- end
+ ActiveRecord::Base.establish_connection(db_config.config)
+ ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config.config, :ruby, db_config.spec_name)
end
db_namespace["schema:dump"].reenable
@@ -385,14 +400,7 @@ db_namespace = namespace :db do
task dump: :load_config do
ActiveRecord::Base.configurations.configs_for(env_name: ActiveRecord::Tasks::DatabaseTasks.env).each do |db_config|
ActiveRecord::Base.establish_connection(db_config.config)
- filename = ActiveRecord::Tasks::DatabaseTasks.dump_filename(db_config.spec_name, :sql)
- ActiveRecord::Tasks::DatabaseTasks.structure_dump(db_config.config, filename)
- if ActiveRecord::SchemaMigration.table_exists?
- File.open(filename, "a") do |f|
- f.puts ActiveRecord::Base.connection.dump_schema_information
- f.print "\n"
- end
- end
+ ActiveRecord::Tasks::DatabaseTasks.dump_schema(db_config.config, :sql, db_config.spec_name)
end
db_namespace["structure:dump"].reenable
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 50ff733dc7..588cb130f2 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -1254,7 +1254,7 @@ module ActiveRecord
@klass.disallow_raw_sql!(
order_args.flat_map { |a| a.is_a?(Hash) ? a.keys : a },
- permit: AttributeMethods::ClassMethods::COLUMN_NAME_WITH_ORDER
+ permit: connection.column_name_with_order_matcher
)
validate_order_args(order_args)
diff --git a/activerecord/lib/active_record/sanitization.rb b/activerecord/lib/active_record/sanitization.rb
index 750766714d..5296499bad 100644
--- a/activerecord/lib/active_record/sanitization.rb
+++ b/activerecord/lib/active_record/sanitization.rb
@@ -61,8 +61,9 @@ module ActiveRecord
# # => "id ASC"
def sanitize_sql_for_order(condition)
if condition.is_a?(Array) && condition.first.to_s.include?("?")
- disallow_raw_sql!([condition.first],
- permit: AttributeMethods::ClassMethods::COLUMN_NAME_WITH_ORDER
+ disallow_raw_sql!(
+ [condition.first],
+ permit: connection.column_name_with_order_matcher
)
# Ensure we aren't dealing with a subclass of String that might
@@ -133,6 +134,34 @@ module ActiveRecord
end
end
+ def disallow_raw_sql!(args, permit: connection.column_name_matcher) # :nodoc:
+ unexpected = nil
+ args.each do |arg|
+ next if arg.is_a?(Symbol) || Arel.arel_node?(arg) ||
+ arg.to_s.split(/\s*,\s*/).all? { |part| permit.match?(part) }
+ (unexpected ||= []) << arg
+ end
+
+ return unless unexpected
+
+ if allow_unsafe_raw_sql == :deprecated
+ ActiveSupport::Deprecation.warn(
+ "Dangerous query method (method whose arguments are used as raw " \
+ "SQL) called with non-attribute argument(s): " \
+ "#{unexpected.map(&:inspect).join(", ")}. Non-attribute " \
+ "arguments will be disallowed in Rails 6.1. This method should " \
+ "not be called with user-provided values, such as request " \
+ "parameters or model attributes. Known-safe values can be passed " \
+ "by wrapping them in Arel.sql()."
+ )
+ else
+ raise(ActiveRecord::UnknownAttributeReference,
+ "Query method called with non-attribute argument(s): " +
+ unexpected.map(&:inspect).join(", ")
+ )
+ end
+ end
+
private
def replace_bind_variables(statement, values)
raise_if_bind_arity_mismatch(statement, statement.count("?"), values.size)
diff --git a/activerecord/lib/active_record/tasks/database_tasks.rb b/activerecord/lib/active_record/tasks/database_tasks.rb
index c79ed8db60..636db0f33d 100644
--- a/activerecord/lib/active_record/tasks/database_tasks.rb
+++ b/activerecord/lib/active_record/tasks/database_tasks.rb
@@ -169,8 +169,8 @@ module ActiveRecord
end
end
- def create_current(environment = env)
- each_current_configuration(environment) { |configuration|
+ def create_current(environment = env, spec_name = nil)
+ each_current_configuration(environment, spec_name) { |configuration|
create configuration
}
ActiveRecord::Base.establish_connection(environment.to_sym)
@@ -325,6 +325,26 @@ module ActiveRecord
Migration.verbose = verbose_was
end
+ def dump_schema(configuration, format = ActiveRecord::Base.schema_format, spec_name = "primary")
+ require "active_record/schema_dumper"
+ filename = dump_filename(spec_name, format)
+
+ case format
+ when :ruby
+ File.open(filename, "w:utf-8") do |file|
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
+ end
+ when :sql
+ structure_dump(configuration, filename)
+ if ActiveRecord::SchemaMigration.table_exists?
+ File.open(filename, "a") do |f|
+ f.puts ActiveRecord::Base.connection.dump_schema_information
+ f.print "\n"
+ end
+ end
+ end
+ end
+
def schema_file(format = ActiveRecord::Base.schema_format)
File.join(db_dir, schema_file_type(format))
end
@@ -406,12 +426,14 @@ module ActiveRecord
task.is_a?(String) ? task.constantize : task
end
- def each_current_configuration(environment)
+ def each_current_configuration(environment, spec_name = nil)
environments = [environment]
environments << "test" if environment == "development"
environments.each do |env|
ActiveRecord::Base.configurations.configs_for(env_name: env).each do |db_config|
+ next if spec_name && spec_name != db_config.spec_name
+
yield db_config.config, db_config.spec_name, env
end
end
diff --git a/activerecord/test/cases/adapters/sqlite3/collation_test.rb b/activerecord/test/cases/adapters/sqlite3/collation_test.rb
index 76c8f7d8dd..d938b5ff2f 100644
--- a/activerecord/test/cases/adapters/sqlite3/collation_test.rb
+++ b/activerecord/test/cases/adapters/sqlite3/collation_test.rb
@@ -11,6 +11,10 @@ class SQLite3CollationTest < ActiveRecord::SQLite3TestCase
@connection.create_table :collation_table_sqlite3, force: true do |t|
t.string :string_nocase, collation: "NOCASE"
t.text :text_rtrim, collation: "RTRIM"
+ # The decimal column might interfere with collation parsing.
+ # Thus, add this column type and some other string column afterwards.
+ t.decimal :decimal_col, precision: 6, scale: 2
+ t.string :string_after_decimal_nocase, collation: "NOCASE"
end
end
@@ -22,6 +26,11 @@ class SQLite3CollationTest < ActiveRecord::SQLite3TestCase
column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == "string_nocase" }
assert_equal :string, column.type
assert_equal "NOCASE", column.collation
+
+ # Verify collation of a column behind the decimal column as well.
+ column = @connection.columns(:collation_table_sqlite3).find { |c| c.name == "string_after_decimal_nocase" }
+ assert_equal :string, column.type
+ assert_equal "NOCASE", column.collation
end
test "text column with collation" do
diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb
index f7aad9d775..38723b6c19 100644
--- a/activerecord/test/cases/associations/eager_test.rb
+++ b/activerecord/test/cases/associations/eager_test.rb
@@ -523,7 +523,7 @@ class EagerAssociationTest < ActiveRecord::TestCase
def test_eager_association_loading_with_belongs_to_and_order_string_with_quoted_table_name
quoted_posts_id = Comment.connection.quote_table_name("posts") + "." + Comment.connection.quote_column_name("id")
assert_nothing_raised do
- Comment.includes(:post).references(:posts).order(Arel.sql(quoted_posts_id))
+ Comment.includes(:post).references(:posts).order(quoted_posts_id)
end
end
@@ -789,7 +789,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
.where("comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment'")
.references(:comments)
.scoping do
-
posts = authors(:david).posts.limit(2).to_a
assert_equal 2, posts.size
end
@@ -798,7 +797,6 @@ class EagerAssociationTest < ActiveRecord::TestCase
.where("authors.name = 'David' AND (comments.body like 'Normal%' OR comments.#{QUOTED_TYPE}= 'SpecialComment')")
.references(:authors, :comments)
.scoping do
-
count = Post.limit(2).count
assert_equal count, posts.size
end
diff --git a/activerecord/test/cases/connection_adapters/connection_handler_test.rb b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
index 6282759a10..27589966af 100644
--- a/activerecord/test/cases/connection_adapters/connection_handler_test.rb
+++ b/activerecord/test/cases/connection_adapters/connection_handler_test.rb
@@ -367,11 +367,24 @@ module ActiveRecord
assert_same klass2.connection, ActiveRecord::Base.connection
end
+ class ApplicationRecord < ActiveRecord::Base
+ self.abstract_class = true
+ end
+
+ class MyClass < ApplicationRecord
+ end
+
def test_connection_specification_name_should_fallback_to_parent
klassA = Class.new(Base)
klassB = Class.new(klassA)
+ klassC = Class.new(MyClass)
assert_equal klassB.connection_specification_name, klassA.connection_specification_name
+ assert_equal klassC.connection_specification_name, klassA.connection_specification_name
+
+ assert_equal "primary", klassA.connection_specification_name
+ assert_equal "primary", klassC.connection_specification_name
+
klassA.connection_specification_name = "readonly"
assert_equal "readonly", klassB.connection_specification_name
end
diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb
index a15ad9a45b..82f5f45a4c 100644
--- a/activerecord/test/cases/connection_pool_test.rb
+++ b/activerecord/test/cases/connection_pool_test.rb
@@ -507,7 +507,6 @@ module ActiveRecord
pool.schema_cache = schema_cache
pool.with_connection do |conn|
- assert_not_same pool.schema_cache, conn.schema_cache
assert_equal pool.schema_cache.size, conn.schema_cache.size
assert_same pool.schema_cache.columns(:posts), conn.schema_cache.columns(:posts)
end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 4ec695c4c6..91353e4f9e 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -1679,7 +1679,7 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.order("comments.body")
assert_equal ["comments"], scope.references_values
- scope = Post.order(Arel.sql("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}"))
+ scope = Post.order("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}")
if current_adapter?(:OracleAdapter)
assert_equal ["COMMENTS"], scope.references_values
else
@@ -1704,7 +1704,7 @@ class RelationTest < ActiveRecord::TestCase
scope = Post.reorder("comments.body")
assert_equal %w(comments), scope.references_values
- scope = Post.reorder(Arel.sql("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}"))
+ scope = Post.reorder("#{Comment.quoted_table_name}.#{Comment.quoted_primary_key}")
if current_adapter?(:OracleAdapter)
assert_equal ["COMMENTS"], scope.references_values
else
diff --git a/activerecord/test/cases/unsafe_raw_sql_test.rb b/activerecord/test/cases/unsafe_raw_sql_test.rb
index d5d8f2a09a..fc92bf73c9 100644
--- a/activerecord/test/cases/unsafe_raw_sql_test.rb
+++ b/activerecord/test/cases/unsafe_raw_sql_test.rb
@@ -77,7 +77,7 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
assert_equal ids_expected, ids_disabled
end
- test "order: allows table and column name" do
+ test "order: allows table and column names" do
ids_expected = Post.order(Arel.sql("title")).pluck(:id)
ids_depr = with_unsafe_raw_sql_deprecated { Post.order("posts.title").pluck(:id) }
@@ -87,6 +87,17 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
assert_equal ids_expected, ids_disabled
end
+ test "order: allows quoted table and column names" do
+ ids_expected = Post.order(Arel.sql("title")).pluck(:id)
+
+ quoted_title = Post.connection.quote_table_name("posts.title")
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order(quoted_title).pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order(quoted_title).pluck(:id) }
+
+ assert_equal ids_expected, ids_depr
+ assert_equal ids_expected, ids_disabled
+ end
+
test "order: allows column name and direction in string" do
ids_expected = Post.order(Arel.sql("title desc")).pluck(:id)
@@ -116,10 +127,10 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
["asc", "desc", ""].each do |direction|
%w(first last).each do |position|
- ids_expected = Post.order(Arel.sql("type #{direction} nulls #{position}")).pluck(:id)
+ ids_expected = Post.order(Arel.sql("type::text #{direction} nulls #{position}")).pluck(:id)
- ids_depr = with_unsafe_raw_sql_deprecated { Post.order("type #{direction} nulls #{position}").pluck(:id) }
- ids_disabled = with_unsafe_raw_sql_disabled { Post.order("type #{direction} nulls #{position}").pluck(:id) }
+ ids_depr = with_unsafe_raw_sql_deprecated { Post.order("type::text #{direction} nulls #{position}").pluck(:id) }
+ ids_disabled = with_unsafe_raw_sql_disabled { Post.order("type::text #{direction} nulls #{position}").pluck(:id) }
assert_equal ids_expected, ids_depr
assert_equal ids_expected, ids_disabled
@@ -262,6 +273,17 @@ class UnsafeRawSqlTest < ActiveRecord::TestCase
assert_equal titles_expected, titles_disabled
end
+ test "pluck: allows quoted table and column names" do
+ titles_expected = Post.pluck(Arel.sql("title"))
+
+ quoted_title = Post.connection.quote_table_name("posts.title")
+ titles_depr = with_unsafe_raw_sql_deprecated { Post.pluck(quoted_title) }
+ titles_disabled = with_unsafe_raw_sql_disabled { Post.pluck(quoted_title) }
+
+ assert_equal titles_expected, titles_depr
+ assert_equal titles_expected, titles_disabled
+ end
+
test "pluck: disallows invalid column name" do
with_unsafe_raw_sql_disabled do
assert_raises(ActiveRecord::UnknownAttributeReference) do
diff --git a/activestorage/CHANGELOG.md b/activestorage/CHANGELOG.md
index 2d9fe56858..fdb0f143f4 100644
--- a/activestorage/CHANGELOG.md
+++ b/activestorage/CHANGELOG.md
@@ -1,59 +1,60 @@
-* Image analysis is skipped if ImageMagick returns an error.
+* Image analysis is skipped if ImageMagick returns an error.
- `ActiveStorage::Analyzer::ImageAnalyzer#metadata` would previously raise a
- `MiniMagick::Error`, which caused persistent `ActiveStorage::AnalyzeJob`
- failures. It now logs the error and returns `{}`, resulting in no metadata
- being added to the offending image blob.
+ `ActiveStorage::Analyzer::ImageAnalyzer#metadata` would previously raise a
+ `MiniMagick::Error`, which caused persistent `ActiveStorage::AnalyzeJob`
+ failures. It now logs the error and returns `{}`, resulting in no metadata
+ being added to the offending image blob.
- *George Claghorn*
+ *George Claghorn*
-* Method calls on singular attachments return `nil` when no file is attached.
+* Method calls on singular attachments return `nil` when no file is attached.
- Previously, assuming the following User model, `user.avatar.filename` would
- raise a `Module::DelegationError` if no avatar was attached:
+ Previously, assuming the following User model, `user.avatar.filename` would
+ raise a `Module::DelegationError` if no avatar was attached:
- ```ruby
- class User < ApplicationRecord
- has_one_attached :avatar
- end
- ```
+ ```ruby
+ class User < ApplicationRecord
+ has_one_attached :avatar
+ end
+ ```
- They now return `nil`.
+ They now return `nil`.
- *Matthew Tanous*
+ *Matthew Tanous*
-* The mirror service supports direct uploads.
+* The mirror service supports direct uploads.
- New files are directly uploaded to the primary service. When a
- directly-uploaded file is attached to a record, a background job is enqueued
- to copy it to each secondary service.
+ New files are directly uploaded to the primary service. When a
+ directly-uploaded file is attached to a record, a background job is enqueued
+ to copy it to each secondary service.
- Configure the queue used to process mirroring jobs by setting
- `config.active_storage.queues.mirror`. The default is `:active_storage_mirror`.
+ Configure the queue used to process mirroring jobs by setting
+ `config.active_storage.queues.mirror`. The default is `:active_storage_mirror`.
- *George Claghorn*
+ *George Claghorn*
-* The S3 service now permits uploading files larger than 5 gigabytes.
+* The S3 service now permits uploading files larger than 5 gigabytes.
- When uploading a file greater than 100 megabytes in size, the service
- transparently switches to [multipart uploads](https://docs.aws.amazon.com/AmazonS3/latest/dev/mpuoverview.html)
- using a part size computed from the file's total size and S3's part count limit.
+ When uploading a file greater than 100 megabytes in size, the service
+ transparently switches to [multipart uploads](https://docs.aws.amazon.com/AmazonS3/latest/dev/mpuoverview.html)
+ using a part size computed from the file's total size and S3's part count limit.
- No application changes are necessary to take advantage of this feature. You
- can customize the default 100 MB multipart upload threshold in your S3
- service's configuration:
+ No application changes are necessary to take advantage of this feature. You
+ can customize the default 100 MB multipart upload threshold in your S3
+ service's configuration:
- ```yaml
- production:
- service: s3
- access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
- secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
- region: us-east-1
- bucket: my-bucket
- upload:
- multipart_threshold: <%= 250.megabytes %>
- ```
+ ```yaml
+ production:
+ service: s3
+ access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
+ secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
+ region: us-east-1
+ bucket: my-bucket
+ upload:
+ multipart_threshold: <%= 250.megabytes %>
+ ```
+
+ *George Claghorn*
- *George Claghorn*
Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activestorage/CHANGELOG.md) for previous changes.
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 955eb7eef9..29b22bb3f9 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,3 +1,20 @@
+* Allow the on_rotation proc used when decrypting/verifying a message to be
+ be passed at the constructor level.
+
+ Before:
+
+ crypt = ActiveSupport::MessageEncryptor.new('long_secret')
+ crypt.decrypt_and_verify(encrypted_message, on_rotation: proc { ... })
+ crypt.decrypt_and_verify(another_encrypted_message, on_rotation: proc { ... })
+
+ After:
+
+ crypt = ActiveSupport::MessageEncryptor.new('long_secret', on_rotation: proc { ... })
+ crypt.decrypt_and_verify(encrypted_message)
+ crypt.decrypt_and_verify(another_encrypted_message)
+
+ *Edouard Chin*
+
* `delegate_missing_to` would raise a `DelegationError` if the object
delegated to was `nil`. Now the `allow_nil` option has been added to enable
the user to specify they want `nil` returned in this case.
diff --git a/activesupport/lib/active_support/evented_file_update_checker.rb b/activesupport/lib/active_support/evented_file_update_checker.rb
index 84caa00b58..5a79822c49 100644
--- a/activesupport/lib/active_support/evented_file_update_checker.rb
+++ b/activesupport/lib/active_support/evented_file_update_checker.rb
@@ -108,7 +108,10 @@ module ActiveSupport
private
def boot!
normalize_dirs!
- Listen.to(*@dtw, &method(:changed)).start
+
+ unless @dtw.empty?
+ Listen.to(*@dtw, &method(:changed)).start
+ end
end
def shutdown!
diff --git a/activesupport/lib/active_support/messages/rotator.rb b/activesupport/lib/active_support/messages/rotator.rb
index 823a399d67..50ea7dcd8d 100644
--- a/activesupport/lib/active_support/messages/rotator.rb
+++ b/activesupport/lib/active_support/messages/rotator.rb
@@ -3,11 +3,12 @@
module ActiveSupport
module Messages
module Rotator # :nodoc:
- def initialize(*, **options)
+ def initialize(*, on_rotation: nil, **options)
super
@options = options
@rotations = []
+ @on_rotation = on_rotation
end
def rotate(*secrets, **options)
@@ -17,7 +18,7 @@ module ActiveSupport
module Encryptor
include Rotator
- def decrypt_and_verify(*args, on_rotation: nil, **options)
+ def decrypt_and_verify(*args, on_rotation: @on_rotation, **options)
super
rescue MessageEncryptor::InvalidMessage, MessageVerifier::InvalidSignature
run_rotations(on_rotation) { |encryptor| encryptor.decrypt_and_verify(*args, options) } || raise
@@ -32,7 +33,7 @@ module ActiveSupport
module Verifier
include Rotator
- def verified(*args, on_rotation: nil, **options)
+ def verified(*args, on_rotation: @on_rotation, **options)
super || run_rotations(on_rotation) { |verifier| verifier.verified(*args, options) }
end
@@ -46,7 +47,7 @@ module ActiveSupport
def run_rotations(on_rotation)
@rotations.find do |rotation|
if message = yield(rotation) rescue next
- on_rotation.call if on_rotation
+ on_rotation&.call
return message
end
end
diff --git a/activesupport/test/message_encryptor_test.rb b/activesupport/test/message_encryptor_test.rb
index 9edf07f762..097aa8b5f8 100644
--- a/activesupport/test/message_encryptor_test.rb
+++ b/activesupport/test/message_encryptor_test.rb
@@ -171,6 +171,34 @@ class MessageEncryptorTest < ActiveSupport::TestCase
assert rotated
end
+ def test_on_rotation_can_be_passed_at_the_constructor_level
+ older_message = ActiveSupport::MessageEncryptor.new(secrets[:older], "older sign").encrypt_and_sign(encoded: "message")
+
+ rotated = false
+ encryptor = ActiveSupport::MessageEncryptor.new(@secret, on_rotation: proc { rotated = true })
+ encryptor.rotate secrets[:older], "older sign"
+
+ assert_changes(:rotated, from: false, to: true) do
+ message = encryptor.decrypt_and_verify(older_message)
+
+ assert_equal({ encoded: "message" }, message)
+ end
+ end
+
+ def test_on_rotation_option_takes_precedence_over_the_one_given_in_constructor
+ older_message = ActiveSupport::MessageEncryptor.new(secrets[:older], "older sign").encrypt_and_sign(encoded: "message")
+
+ rotated = false
+ encryptor = ActiveSupport::MessageEncryptor.new(@secret, on_rotation: proc { rotated = true })
+ encryptor.rotate secrets[:older], "older sign"
+
+ assert_changes(:rotated, from: false, to: "Yes") do
+ message = encryptor.decrypt_and_verify(older_message, on_rotation: proc { rotated = "Yes" })
+
+ assert_equal({ encoded: "message" }, message)
+ end
+ end
+
def test_with_rotated_metadata
old_message = ActiveSupport::MessageEncryptor.new(secrets[:old], cipher: "aes-256-gcm").
encrypt_and_sign("metadata", purpose: :rotation)
diff --git a/guides/source/2_3_release_notes.md b/guides/source/2_3_release_notes.md
index ee9a499953..b46d5ee4db 100644
--- a/guides/source/2_3_release_notes.md
+++ b/guides/source/2_3_release_notes.md
@@ -415,7 +415,7 @@ select_datetime(DateTime.now, :prompt =>
### AssetTag Timestamp Caching
-You're likely familiar with Rails' practice of adding timestamps to static asset paths as a "cache buster." This helps ensure that stale copies of things like images and stylesheets don't get served out of the user's browser cache when you change them on the server. You can now modify this behavior with the `cache_asset_timestamps` configuration option for Action View. If you enable the cache, then Rails will calculate the timestamp once when it first serves an asset, and save that value. This means fewer (expensive) file system calls to serve static assets - but it also means that you can't modify any of the assets while the server is running and expect the changes to get picked up by clients.
+You're likely familiar with Rails' practice of adding timestamps to static asset paths as a "cache buster". This helps ensure that stale copies of things like images and stylesheets don't get served out of the user's browser cache when you change them on the server. You can now modify this behavior with the `cache_asset_timestamps` configuration option for Action View. If you enable the cache, then Rails will calculate the timestamp once when it first serves an asset, and save that value. This means fewer (expensive) file system calls to serve static assets - but it also means that you can't modify any of the assets while the server is running and expect the changes to get picked up by clients.
### Asset Hosts as Objects
diff --git a/guides/source/6_0_release_notes.md b/guides/source/6_0_release_notes.md
index e421ae1ac7..c826b19f1a 100644
--- a/guides/source/6_0_release_notes.md
+++ b/guides/source/6_0_release_notes.md
@@ -686,7 +686,7 @@ Please refer to the [Changelog][active-storage] for detailed changes.
* Use the `image_processing` gem for Active Storage variants. This replaces using
`mini_magick` directly.
- ([Pull Request](https://github.com/rails/rails/pull/32471)
+ ([Pull Request](https://github.com/rails/rails/pull/32471))
* Replace existing images instead of adding to them when updating an
attached model via `update` or `update!` with, say, `@user.update!(images: [ … ])`.
diff --git a/guides/source/active_record_multiple_databases.md b/guides/source/active_record_multiple_databases.md
index e2c7878118..51f5cab2aa 100644
--- a/guides/source/active_record_multiple_databases.md
+++ b/guides/source/active_record_multiple_databases.md
@@ -191,7 +191,7 @@ should change this based on your database infrastructure. Rails doesn't guarante
a recent write" for other users within the delay window and will send GET and HEAD requests
to the replicas unless they wrote recently.
-The automatic connection switching in Rails is relatively primitive and deliberatly doesn't
+The automatic connection switching in Rails is relatively primitive and deliberately doesn't
do a whole lot. The goal was a system that demonstrated how to do automatic connection
switching that was flexible enough to be customizable by app developers.
@@ -210,7 +210,7 @@ And then pass it to the middleware:
```ruby
config.active_record.database_selector = { delay: 2.seconds }
config.active_record.database_resolver = ActiveRecord::Middleware::DatabaseSelector::Resolver
-config.active_record.database_resolver_context = MyCookieResovler
+config.active_record.database_resolver_context = MyCookieResolver
```
## Using manual connection switching
@@ -247,13 +247,13 @@ The `database` argument for `connected_to` will take a symbol or a config hash.
Note that `connected_to` with a role will look up an existing connection and switch
using the connection specification name. This means that if you pass an unknown role
-like `connected_to(role: :nonexistent)` you will get an error like that says
+like `connected_to(role: :nonexistent)` you will get an error that says
`ActiveRecord::ConnectionNotEstablished (No connection pool with 'AnimalsBase' found
for the 'nonexistent' role.)`
## Caveats
-As noted at the top Rails doesn't (yet) support sharding. We had to do a lot of work
+As noted at the top, Rails doesn't (yet) support sharding. We had to do a lot of work
to support multiple databases for Rails 6.0. The lack of support for sharding isn't
an oversight, but does require additional work that didn't make it in for 6.0. For now
if you need sharding it may be advisable to continue using one of the many gems
diff --git a/guides/source/active_storage_overview.md b/guides/source/active_storage_overview.md
index 2986850cef..932a5dc2e9 100644
--- a/guides/source/active_storage_overview.md
+++ b/guides/source/active_storage_overview.md
@@ -41,6 +41,8 @@ application (or upgrading your application to Rails 5.2), run
`rails active_storage:install` to generate a migration that creates these
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.
+
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`:
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index cc64c7eac6..4651290674 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -897,7 +897,7 @@ text/javascript image/svg+xml application/postscript application/x-shockwave-fla
#### With '5.2':
- `config.active_record.cache_versioning`: `true`
-- `action_dispatch.use_authenticated_cookie_encryption`: `true`
+- `config.action_dispatch.use_authenticated_cookie_encryption`: `true`
- `config.active_support.use_authenticated_message_encryption`: `true`
- `config.active_support.use_sha1_digests`: `true`
- `config.action_controller.default_protect_from_forgery`: `true`
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index a6eb9907a0..d3706a4dbf 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -13,7 +13,7 @@ After reading this guide, you will know:
* How to contribute to the Ruby on Rails documentation.
* How to contribute to the Ruby on Rails code.
-Ruby on Rails is not "someone else's framework." Over the years, thousands of people have contributed to Ruby on Rails ranging from a single character to massive architectural changes or significant documentation - all with the goal of making Ruby on Rails better for everyone. Even if you don't feel up to writing code or documentation yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches.
+Ruby on Rails is not "someone else's framework". Over the years, thousands of people have contributed to Ruby on Rails ranging from a single character to massive architectural changes or significant documentation - all with the goal of making Ruby on Rails better for everyone. Even if you don't feel up to writing code or documentation yet, there are a variety of other ways that you can contribute, from reporting issues to testing patches.
As mentioned in [Rails'
README](https://github.com/rails/rails/blob/master/README.md), everyone interacting in Rails and its sub-projects' codebases, issue trackers, chat rooms, and mailing lists is expected to follow the Rails [code of conduct](https://rubyonrails.org/conduct/).
@@ -76,7 +76,7 @@ a patch, please send an email to the [rails-core mailing
list](https://groups.google.com/forum/?fromgroups#!forum/rubyonrails-core). You
might get no response, which means that everyone is indifferent. You might find
someone who's also interested in building that feature. You might get a "This
-won't be accepted." But it's the proper place to discuss new ideas. GitHub
+won't be accepted". But it's the proper place to discuss new ideas. GitHub
Issues are not a particularly good venue for the sometimes long and involved
discussions new features require.
diff --git a/guides/source/documents.yaml b/guides/source/documents.yaml
index 90674e8456..8df1c601a7 100644
--- a/guides/source/documents.yaml
+++ b/guides/source/documents.yaml
@@ -198,7 +198,7 @@
-
name: Contributing to Ruby on Rails
url: contributing_to_ruby_on_rails.html
- description: Rails is not 'somebody else's framework.' This guide covers a variety of ways that you can get involved in the ongoing development of Rails.
+ description: Rails is not "someone else's framework". This guide covers a variety of ways that you can get involved in the ongoing development of Rails.
-
name: API Documentation Guidelines
url: api_documentation_guidelines.html
diff --git a/guides/source/getting_started.md b/guides/source/getting_started.md
index e8b224a1a3..64f4a3b6b3 100644
--- a/guides/source/getting_started.md
+++ b/guides/source/getting_started.md
@@ -55,7 +55,7 @@ The Rails philosophy includes two major guiding principles:
* **Don't Repeat Yourself:** DRY is a principle of software development which
states that "Every piece of knowledge must have a single, unambiguous, authoritative
- representation within a system." By not writing the same information over and over
+ representation within a system". By not writing the same information over and over
again, our code is more maintainable, more extensible, and less buggy.
* **Convention Over Configuration:** Rails has opinions about the best way to do many
things in a web application, and defaults to this set of conventions, rather than
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 1110592d5e..54f014293d 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -134,12 +134,12 @@ Action Cable JavaScript API:
+ ActionCable.logger.enabled = false
```
-### `ActionDispatch::Response#content_type` now returned Content-Type header as it is.
+### `ActionDispatch::Response#content_type` now returns the Content-Type header without modification
-Previously, `ActionDispatch::Response#content_type` returned value does NOT contain charset part.
-This behavior changed to returned Content-Type header containing charset part as it is.
+Previously, the return value of `ActionDispatch::Response#content_type` did NOT contain the charset part.
+This behavior has changed to include the previously omitted charset part as well.
-If you want just MIME type, please use `ActionDispatch::Response#media_type` instead.
+If you want just the MIME type, please use `ActionDispatch::Response#media_type` instead.
Before:
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index 50685a4d7a..1fdc7b2d71 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -34,20 +34,12 @@ module Rails
# Initialize the logger early in the stack in case we need to log some deprecation.
initializer :initialize_logger, group: :all do
Rails.logger ||= config.logger || begin
- path = config.paths["log"].first
- unless File.exist? File.dirname path
- FileUtils.mkdir_p File.dirname path
- end
-
- f = File.open path, "a"
- f.binmode
- f.sync = config.autoflush_log # if true make sure every write flushes
-
- logger = ActiveSupport::Logger.new f
+ logger = ActiveSupport::Logger.new(config.default_log_file)
logger.formatter = config.log_formatter
logger = ActiveSupport::TaggedLogging.new(logger)
logger
rescue StandardError
+ path = config.paths["log"].first
logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDERR))
logger.level = ActiveSupport::Logger::WARN
logger.warn(
diff --git a/railties/lib/rails/application/configuration.rb b/railties/lib/rails/application/configuration.rb
index 0b758dd3dd..da1c433e52 100644
--- a/railties/lib/rails/application/configuration.rb
+++ b/railties/lib/rails/application/configuration.rb
@@ -311,6 +311,18 @@ module Rails
end
end
+ def default_log_file
+ path = paths["log"].first
+ unless File.exist? File.dirname path
+ FileUtils.mkdir_p File.dirname path
+ end
+
+ f = File.open path, "a"
+ f.binmode
+ f.sync = autoflush_log # if true make sure every write flushes
+ f
+ end
+
class Custom #:nodoc:
def initialize
@configurations = Hash.new
diff --git a/railties/lib/rails/generators/rails/app/app_generator.rb b/railties/lib/rails/generators/rails/app/app_generator.rb
index 7336e235f6..578881d1ac 100644
--- a/railties/lib/rails/generators/rails/app/app_generator.rb
+++ b/railties/lib/rails/generators/rails/app/app_generator.rb
@@ -244,7 +244,6 @@ module Rails
RAILS_DEV_PATH = File.expand_path("../../../../../..", __dir__)
class AppGenerator < AppBase
-
# :stopdoc:
WEBPACKS = %w( react vue angular elm stimulus )
@@ -495,7 +494,7 @@ module Rails
"rails new #{arguments.map(&:usage).join(' ')} [options]"
end
- # :startdoc:
+ # :startdoc:
private
diff --git a/railties/test/application/configuration_test.rb b/railties/test/application/configuration_test.rb
index 7c613585e0..6f9711cb37 100644
--- a/railties/test/application/configuration_test.rb
+++ b/railties/test/application/configuration_test.rb
@@ -1607,6 +1607,13 @@ module ApplicationTests
assert_not_nil Rails::SourceAnnotationExtractor::Annotation.extensions[/\.(coffee)$/]
end
+ test "config.default_log_file returns a File instance" do
+ app "development"
+
+ assert_instance_of File, app.config.default_log_file
+ assert_equal Rails.application.config.paths["log"].first, app.config.default_log_file.path
+ end
+
test "rake_tasks block works at instance level" do
app_file "config/environments/development.rb", <<-RUBY
Rails.application.configure do
diff --git a/railties/test/application/loading_test.rb b/railties/test/application/loading_test.rb
index 9c98489590..322a6e1ca0 100644
--- a/railties/test/application/loading_test.rb
+++ b/railties/test/application/loading_test.rb
@@ -116,7 +116,7 @@ class LoadingTest < ActiveSupport::TestCase
RUBY
app_file "app/models/post.rb", <<-MODEL
- class Post < ActiveRecord::Base
+ class Post < ApplicationRecord
end
MODEL
@@ -133,9 +133,9 @@ class LoadingTest < ActiveSupport::TestCase
require "#{rails_root}/config/environment"
setup_ar!
- assert_equal [ActiveStorage::Blob, ActiveStorage::Attachment, ActiveRecord::SchemaMigration, ActiveRecord::InternalMetadata].collect(&:to_s).sort, ActiveRecord::Base.descendants.collect(&:to_s).sort
+ assert_equal [ActiveStorage::Blob, ActiveStorage::Attachment, ActiveRecord::SchemaMigration, ActiveRecord::InternalMetadata, ApplicationRecord].collect(&:to_s).sort, ActiveRecord::Base.descendants.collect(&:to_s).sort
get "/load"
- assert_equal [ActiveStorage::Blob, ActiveStorage::Attachment, ActiveRecord::SchemaMigration, ActiveRecord::InternalMetadata, Post].collect(&:to_s).sort, ActiveRecord::Base.descendants.collect(&:to_s).sort
+ assert_equal [ActiveStorage::Blob, ActiveStorage::Attachment, ActiveRecord::SchemaMigration, ActiveRecord::InternalMetadata, ApplicationRecord, Post].collect(&:to_s).sort, ActiveRecord::Base.descendants.collect(&:to_s).sort
get "/unload"
assert_equal [ActiveStorage::Blob, ActiveStorage::Attachment, ActiveRecord::SchemaMigration, ActiveRecord::InternalMetadata].collect(&:to_s).sort, ActiveRecord::Base.descendants.collect(&:to_s).sort
end
diff --git a/railties/test/application/rake/multi_dbs_test.rb b/railties/test/application/rake/multi_dbs_test.rb
index 31ea2246a9..8c41b252da 100644
--- a/railties/test/application/rake/multi_dbs_test.rb
+++ b/railties/test/application/rake/multi_dbs_test.rb
@@ -303,6 +303,26 @@ module ApplicationTests
require "#{app_path}/config/environment"
db_prepare
end
+
+ test "db:prepare setups missing database without clearing existing one" do
+ require "#{app_path}/config/environment"
+ Dir.chdir(app_path) do
+ # Bug not visible on SQLite3. Can be simplified when https://github.com/rails/rails/issues/36383 resolved
+ use_postgresql(multi_db: true)
+ generate_models_for_animals
+
+ rails "db:create:animals", "db:migrate:animals", "db:create:primary", "db:migrate:primary", "db:schema:dump"
+ rails "db:drop:primary"
+ Dog.create!
+ output = rails("db:prepare")
+
+ assert_match(/Created database/, output)
+ assert_equal 1, Dog.count
+ ensure
+ Dog.connection.disconnect!
+ rails "db:drop" rescue nil
+ end
+ end
end
end
end
diff --git a/railties/test/isolation/abstract_unit.rb b/railties/test/isolation/abstract_unit.rb
index fab704944b..0fe62df8ba 100644
--- a/railties/test/isolation/abstract_unit.rb
+++ b/railties/test/isolation/abstract_unit.rb
@@ -448,18 +448,36 @@ module TestHelpers
$:.reject! { |path| path =~ %r'/(#{to_remove.join('|')})/' }
end
- def use_postgresql
- File.open("#{app_path}/config/database.yml", "w") do |f|
- f.puts <<-YAML
- default: &default
- adapter: postgresql
- pool: 5
- database: railties_test
- development:
- <<: *default
- test:
- <<: *default
- YAML
+ def use_postgresql(multi_db: false)
+ if multi_db
+ File.open("#{app_path}/config/database.yml", "w") do |f|
+ f.puts <<-YAML
+ default: &default
+ adapter: postgresql
+ pool: 5
+ development:
+ primary:
+ <<: *default
+ database: railties_test
+ animals:
+ <<: *default
+ database: railties_animals_test
+ migrations_paths: db/animals_migrate
+ YAML
+ end
+ else
+ File.open("#{app_path}/config/database.yml", "w") do |f|
+ f.puts <<-YAML
+ default: &default
+ adapter: postgresql
+ pool: 5
+ database: railties_test
+ development:
+ <<: *default
+ test:
+ <<: *default
+ YAML
+ end
end
end
end