aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Gemfile.lock32
-rw-r--r--actionmailbox/CHANGELOG.md2
-rw-r--r--actionmailbox/lib/action_mailbox/router.rb10
-rw-r--r--actionmailbox/lib/action_mailbox/routing.rb4
-rw-r--r--actionmailbox/test/unit/mailbox/routing_test.rb5
-rw-r--r--actionmailbox/test/unit/router_test.rb14
-rw-r--r--actionpack/lib/action_dispatch/middleware/debug_view.rb6
-rw-r--r--actionpack/lib/action_dispatch/middleware/stack.rb15
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb4
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb2
-rw-r--r--actionpack/test/controller/show_exceptions_test.rb2
-rw-r--r--actionpack/test/dispatch/debug_exceptions_test.rb19
-rw-r--r--actionpack/test/dispatch/middleware_stack_test.rb6
-rw-r--r--actionview/lib/action_view/renderer/partial_renderer.rb6
-rw-r--r--actionview/test/template/render_test.rb19
-rw-r--r--activemodel/lib/active_model/type/integer.rb5
-rw-r--r--activemodel/test/cases/nested_error_test.rb2
-rw-r--r--activerecord/CHANGELOG.md2
-rw-r--r--activerecord/lib/active_record/associations/builder/collection_association.rb4
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract/transaction.rb16
-rw-r--r--activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb6
-rw-r--r--activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb6
-rw-r--r--activerecord/lib/active_record/middleware/database_selector.rb6
-rw-r--r--activerecord/lib/active_record/touch_later.rb1
-rw-r--r--activerecord/lib/active_record/transactions.rb16
-rw-r--r--activerecord/lib/rails/generators/active_record/migration/migration_generator.rb1
-rw-r--r--activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb4
-rw-r--r--activerecord/test/cases/adapters/sqlite3/bind_parameter_test.rb20
-rw-r--r--activerecord/test/cases/associations/extension_test.rb4
-rw-r--r--activerecord/test/cases/test_case.rb10
-rw-r--r--activerecord/test/cases/transaction_callbacks_test.rb56
-rw-r--r--activerecord/test/models/developer.rb16
-rw-r--r--activesupport/CHANGELOG.md17
-rw-r--r--activesupport/lib/active_support/notifications.rb35
-rw-r--r--activesupport/lib/active_support/notifications/fanout.rb25
-rw-r--r--activesupport/test/notifications_test.rb69
-rw-r--r--guides/source/6_0_release_notes.md245
-rw-r--r--guides/source/action_text_overview.md2
-rw-r--r--guides/source/active_record_migrations.md2
-rw-r--r--guides/source/active_support_instrumentation.md11
-rw-r--r--guides/source/configuring.md2
-rw-r--r--guides/source/contributing_to_ruby_on_rails.md2
-rw-r--r--guides/source/testing.md2
-rw-r--r--guides/source/upgrading_ruby_on_rails.md219
-rw-r--r--railties/lib/rails/application/bootstrap.rb12
-rw-r--r--railties/lib/rails/generators/app_base.rb2
-rw-r--r--railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt2
-rw-r--r--railties/lib/rails/generators/named_base.rb1
-rw-r--r--railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb12
-rw-r--r--railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb9
-rw-r--r--railties/test/application/middleware/exceptions_test.rb16
-rw-r--r--railties/test/generators/migration_generator_test.rb15
-rw-r--r--railties/test/generators/scaffold_controller_generator_test.rb9
-rw-r--r--railties/test/generators/scaffold_generator_test.rb30
58 files changed, 893 insertions, 183 deletions
diff --git a/Gemfile.lock b/Gemfile.lock
index 8d34d9a24b..6633c7a741 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -216,7 +216,7 @@ GEM
em-socksify (0.3.2)
eventmachine (>= 1.0.0.beta.4)
erubi (1.8.0)
- et-orbi (1.1.6)
+ et-orbi (1.2.1)
tzinfo
event_emitter (0.2.6)
eventmachine (1.2.7)
@@ -240,8 +240,8 @@ GEM
ffi (1.10.0-java)
ffi (1.10.0-x64-mingw32)
ffi (1.10.0-x86-mingw32)
- fugit (1.1.6)
- et-orbi (~> 1.1, >= 1.1.6)
+ fugit (1.2.1)
+ et-orbi (~> 1.1, >= 1.1.8)
raabro (~> 1.1)
globalid (0.4.2)
activesupport (>= 4.2.0)
@@ -370,10 +370,10 @@ GEM
thor
raabro (1.1.6)
racc (1.4.15)
- rack (2.0.6)
+ rack (2.0.7)
rack-cache (1.8.0)
rack (>= 0.4)
- rack-protection (2.0.4)
+ rack-protection (2.0.5)
rack
rack-proxy (0.6.5)
rack
@@ -391,7 +391,7 @@ GEM
ffi (~> 1.0)
rdoc (6.0.4)
redcarpet (3.2.3)
- redis (4.0.3)
+ redis (4.1.1)
redis-namespace (1.6.0)
redis (>= 3.0.4)
regexp_parser (1.3.0)
@@ -399,16 +399,16 @@ GEM
declarative (< 0.1.0)
declarative-option (< 0.2.0)
uber (< 0.2.0)
- resque (1.27.4)
+ resque (2.0.0)
mono_logger (~> 1.0)
multi_json (~> 1.0)
- redis-namespace (~> 1.3)
+ redis-namespace (~> 1.6)
sinatra (>= 0.9.2)
vegas (~> 0.1.2)
- resque-scheduler (4.3.1)
+ resque-scheduler (4.4.0)
mono_logger (~> 1.0)
- redis (>= 3.3, < 5)
- resque (~> 1.26)
+ redis (>= 3.3)
+ resque (>= 1.26)
rufus-scheduler (~> 3.2)
retriable (3.1.2)
rubocop (0.67.2)
@@ -426,8 +426,8 @@ GEM
ffi (~> 1.9)
ruby_dep (1.5.0)
rubyzip (1.2.2)
- rufus-scheduler (3.5.2)
- fugit (~> 1.1, >= 1.1.5)
+ rufus-scheduler (3.6.0)
+ fugit (~> 1.1, >= 1.1.6)
safe_yaml (1.0.4)
sass (3.7.2)
sass-listen (~> 4.0.0)
@@ -458,10 +458,10 @@ GEM
faraday (~> 0.9)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
- sinatra (2.0.4)
+ sinatra (2.0.5)
mustermann (~> 1.0)
rack (~> 2.0)
- rack-protection (= 2.0.4)
+ rack-protection (= 2.0.5)
tilt (~> 2.0)
sneakers (2.11.0)
bunny (~> 2.12)
@@ -488,7 +488,7 @@ GEM
thor (0.20.3)
thread_safe (0.3.6)
thread_safe (0.3.6-java)
- tilt (2.0.8)
+ tilt (2.0.9)
turbolinks (5.2.0)
turbolinks-source (~> 5.2)
turbolinks-source (5.2.0)
diff --git a/actionmailbox/CHANGELOG.md b/actionmailbox/CHANGELOG.md
index a86adae5a7..605a38b06b 100644
--- a/actionmailbox/CHANGELOG.md
+++ b/actionmailbox/CHANGELOG.md
@@ -1,3 +1,5 @@
+* Add `ApplicationMailbox.mailbox_for` to expose mailbox routing.
+ *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/actionmailbox/lib/action_mailbox/router.rb b/actionmailbox/lib/action_mailbox/router.rb
index 71370e409d..54982eae21 100644
--- a/actionmailbox/lib/action_mailbox/router.rb
+++ b/actionmailbox/lib/action_mailbox/router.rb
@@ -21,7 +21,7 @@ module ActionMailbox
end
def route(inbound_email)
- if mailbox = match_to_mailbox(inbound_email)
+ if mailbox = mailbox_for(inbound_email)
mailbox.receive(inbound_email)
else
inbound_email.bounced!
@@ -30,12 +30,12 @@ module ActionMailbox
end
end
+ def mailbox_for(inbound_email)
+ routes.detect { |route| route.match?(inbound_email) }.try(:mailbox_class)
+ end
+
private
attr_reader :routes
-
- def match_to_mailbox(inbound_email)
- routes.detect { |route| route.match?(inbound_email) }.try(:mailbox_class)
- end
end
end
diff --git a/actionmailbox/lib/action_mailbox/routing.rb b/actionmailbox/lib/action_mailbox/routing.rb
index 58462a44c6..8391bf9db0 100644
--- a/actionmailbox/lib/action_mailbox/routing.rb
+++ b/actionmailbox/lib/action_mailbox/routing.rb
@@ -17,6 +17,10 @@ module ActionMailbox
def route(inbound_email)
router.route(inbound_email)
end
+
+ def mailbox_for(inbound_email)
+ router.mailbox_for(inbound_email)
+ end
end
end
end
diff --git a/actionmailbox/test/unit/mailbox/routing_test.rb b/actionmailbox/test/unit/mailbox/routing_test.rb
index d4ba702ac5..8302b1d5cc 100644
--- a/actionmailbox/test/unit/mailbox/routing_test.rb
+++ b/actionmailbox/test/unit/mailbox/routing_test.rb
@@ -28,4 +28,9 @@ class ActionMailbox::Base::RoutingTest < ActiveSupport::TestCase
assert_equal "Discussion: Let's debate these attachments", $processed
end
end
+
+ test "mailbox_for" do
+ inbound_email = create_inbound_email_from_fixture "welcome.eml", status: :pending
+ assert_equal RepliesMailbox, ApplicationMailbox.mailbox_for(inbound_email)
+ end
end
diff --git a/actionmailbox/test/unit/router_test.rb b/actionmailbox/test/unit/router_test.rb
index c5dce60856..4dd3730604 100644
--- a/actionmailbox/test/unit/router_test.rb
+++ b/actionmailbox/test/unit/router_test.rb
@@ -135,5 +135,19 @@ module ActionMailbox
@router.add_route Array.new, to: :first
end
end
+
+ test "single string mailbox_for" do
+ @router.add_routes("first@example.com" => :first)
+
+ inbound_email = create_inbound_email_from_mail(to: "first@example.com", subject: "This is a reply")
+ assert_equal FirstMailbox, @router.mailbox_for(inbound_email)
+ end
+
+ test "mailbox_for with no matches" do
+ @router.add_routes("first@example.com" => :first)
+
+ inbound_email = create_inbound_email_from_mail(to: "second@example.com", subject: "This is a reply")
+ assert_nil @router.mailbox_for(inbound_email)
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/debug_view.rb b/actionpack/lib/action_dispatch/middleware/debug_view.rb
index a03650254e..148662a48b 100644
--- a/actionpack/lib/action_dispatch/middleware/debug_view.rb
+++ b/actionpack/lib/action_dispatch/middleware/debug_view.rb
@@ -56,5 +56,11 @@ module ActionDispatch
def protect_against_forgery?
false
end
+
+ def params_valid?
+ @request.parameters
+ rescue ActionController::BadRequest
+ false
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb
index f0c869fba0..57e4adb457 100644
--- a/actionpack/lib/action_dispatch/middleware/stack.rb
+++ b/actionpack/lib/action_dispatch/middleware/stack.rb
@@ -34,7 +34,11 @@ module ActionDispatch
end
def build(app)
- InstrumentationProxy.new(klass.new(app, *args, &block), inspect)
+ klass.new(app, *args, &block)
+ end
+
+ def build_instrumented(app)
+ InstrumentationProxy.new(build(app), inspect)
end
end
@@ -119,7 +123,14 @@ module ActionDispatch
end
def build(app = nil, &block)
- middlewares.freeze.reverse.inject(app || block) { |a, e| e.build(a) }
+ instrumenting = ActiveSupport::Notifications.notifier.listening?(InstrumentationProxy::EVENT_NAME)
+ middlewares.freeze.reverse.inject(app || block) do |a, e|
+ if instrumenting
+ e.build_instrumented(a)
+ else
+ e.build(a)
+ end
+ end
end
private
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb
index 49b1e83551..04271d8e8a 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb
@@ -6,7 +6,9 @@
<% end %>
<h2 style="margin-top: 30px">Request</h2>
-<p><b>Parameters</b>:</p> <pre><%= debug_params(@request.filtered_parameters) %></pre>
+<% if params_valid? %>
+ <p><b>Parameters</b>:</p> <pre><%= debug_params(@request.filtered_parameters) %></pre>
+<% end %>
<div class="details">
<div class="summary"><a href="#" onclick="return toggleSessionDump()">Toggle session dump</a></div>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb
index 396768ecee..ca42a6fa8b 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb
@@ -1,5 +1,5 @@
<%
- clean_params = @request.filtered_parameters.clone
+ clean_params = params_valid? ? @request.filtered_parameters.clone : {}
clean_params.delete("action")
clean_params.delete("controller")
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb
index 999e84e4d6..57cdcf9aaf 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb
@@ -1,7 +1,7 @@
<header>
<h1>
<%= @exception.class.to_s %>
- <% if @request.parameters['controller'] %>
+ <% if params_valid? && @request.parameters['controller'] %>
in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %>
<% end %>
</h1>
diff --git a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb
index 603de54b8b..d3265563a8 100644
--- a/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb
+++ b/actionpack/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb
@@ -1,5 +1,5 @@
<%= @exception.class.to_s %><%
- if @request.parameters['controller']
+ if params_valid? && @request.parameters['controller']
%> in <%= @request.parameters['controller'].camelize %>Controller<% if @request.parameters['action'] %>#<%= @request.parameters['action'] %><% end %>
<% end %>
diff --git a/actionpack/test/controller/show_exceptions_test.rb b/actionpack/test/controller/show_exceptions_test.rb
index 8724f9bcdb..1d68a359dc 100644
--- a/actionpack/test/controller/show_exceptions_test.rb
+++ b/actionpack/test/controller/show_exceptions_test.rb
@@ -99,7 +99,7 @@ module ShowExceptions
class ShowFailsafeExceptionsTest < ActionDispatch::IntegrationTest
def test_render_failsafe_exception
@app = ShowExceptionsOverriddenController.action(:boom)
- middleware = @app.instance_variable_get(:@middleware)
+ middleware = @app
@exceptions_app = middleware.instance_variable_get(:@exceptions_app)
middleware.instance_variable_set(:@exceptions_app, nil)
$stderr = StringIO.new
diff --git a/actionpack/test/dispatch/debug_exceptions_test.rb b/actionpack/test/dispatch/debug_exceptions_test.rb
index 5ae8a20ae4..3e57e8f4d9 100644
--- a/actionpack/test/dispatch/debug_exceptions_test.rb
+++ b/actionpack/test/dispatch/debug_exceptions_test.rb
@@ -620,4 +620,23 @@ class DebugExceptionsTest < ActionDispatch::IntegrationTest
assert_select 'input[value="Action 2"]'
end
end
+
+ test "debug exceptions app shows diagnostics when malformed query parameters are provided" do
+ @app = DevelopmentApp
+
+ get "/bad_request?x[y]=1&x[y][][w]=2"
+
+ assert_response 400
+ assert_match "ActionController::BadRequest", body
+ end
+
+ test "debug exceptions app shows diagnostics when malformed query parameters are provided by XHR" do
+ @app = DevelopmentApp
+ xhr_request_env = { "action_dispatch.show_exceptions" => true, "HTTP_X_REQUESTED_WITH" => "XMLHttpRequest" }
+
+ get "/bad_request?x[y]=1&x[y][][w]=2", headers: xhr_request_env
+
+ assert_response 400
+ assert_match "ActionController::BadRequest", body
+ end
end
diff --git a/actionpack/test/dispatch/middleware_stack_test.rb b/actionpack/test/dispatch/middleware_stack_test.rb
index 90f2eccd19..c534e60c74 100644
--- a/actionpack/test/dispatch/middleware_stack_test.rb
+++ b/actionpack/test/dispatch/middleware_stack_test.rb
@@ -121,9 +121,6 @@ class MiddlewareStackTest < ActiveSupport::TestCase
end
test "instruments the execution of middlewares" do
- app = @stack.build(proc { |env| [200, {}, []] })
- env = {}
-
events = []
subscriber = proc do |*args|
@@ -131,6 +128,9 @@ class MiddlewareStackTest < ActiveSupport::TestCase
end
ActiveSupport::Notifications.subscribed(subscriber, "process_middleware.action_dispatch") do
+ app = @stack.build(proc { |env| [200, {}, []] })
+
+ env = {}
app.call(env)
end
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index dc85750a22..608e417583 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -305,7 +305,7 @@ module ActionView
else
@template_keys = @locals.keys
end
- template = find_partial(@path, @template_keys)
+ template = find_template(@path, @template_keys)
@variable ||= template.variable
else
if options[:cached]
@@ -428,10 +428,6 @@ module ActionView
@object.to_ary if @object.respond_to?(:to_ary)
end
- def find_partial(path, template_keys)
- find_template(path, template_keys)
- end
-
def find_template(path, locals)
prefixes = path.include?(?/) ? [] : @lookup_context.prefixes
@lookup_context.find_template(path, prefixes, true, locals, @details)
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index f0b5d7d95e..2235a7816f 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -33,6 +33,11 @@ module RenderTestCases
assert_equal ORIGINAL_LOCALES, I18n.available_locales.map(&:to_s).sort
end
+ def teardown
+ I18n.reload!
+ ActionController::Base.view_paths.map(&:clear_cache)
+ end
+
def test_implicit_format_comes_from_parent_template
rendered_templates = JSON.parse(@controller_view.render(template: "test/mixing_formats"))
assert_equal({ "format" => "HTML",
@@ -677,11 +682,6 @@ class CachedViewRenderTest < ActiveSupport::TestCase
assert_equal ActionView::OptimizedFileSystemResolver, view_paths.first.class
setup_view(view_paths)
end
-
- def teardown
- GC.start
- I18n.reload!
- end
end
class LazyViewRenderTest < ActiveSupport::TestCase
@@ -697,11 +697,6 @@ class LazyViewRenderTest < ActiveSupport::TestCase
setup_view(view_paths)
end
- def teardown
- GC.start
- I18n.reload!
- end
-
def test_render_utf8_template_with_magic_comment
with_external_encoding Encoding::ASCII_8BIT do
result = @view.render(template: "test/utf8_magic", formats: [:html], layouts: "layouts/yield")
@@ -758,10 +753,6 @@ class CachedCollectionViewRenderTest < ActiveSupport::TestCase
setup_view(view_paths)
end
- teardown do
- I18n.reload!
- end
-
test "template body written to cache" do
customer = Customer.new("david", 1)
key = cache_key(customer, "test/_customer")
diff --git a/activemodel/lib/active_model/type/integer.rb b/activemodel/lib/active_model/type/integer.rb
index 1e1061ff60..e9bbdf4b7b 100644
--- a/activemodel/lib/active_model/type/integer.rb
+++ b/activemodel/lib/active_model/type/integer.rb
@@ -18,6 +18,11 @@ module ActiveModel
:integer
end
+ def deserialize(value)
+ return if value.blank?
+ value.to_i
+ end
+
def serialize(value)
return if value.is_a?(::String) && non_numeric_string?(value)
ensure_in_range(super)
diff --git a/activemodel/test/cases/nested_error_test.rb b/activemodel/test/cases/nested_error_test.rb
index 5bad100da5..6c2458ba83 100644
--- a/activemodel/test/cases/nested_error_test.rb
+++ b/activemodel/test/cases/nested_error_test.rb
@@ -5,7 +5,7 @@ require "active_model/nested_error"
require "models/topic"
require "models/reply"
-class ErrorTest < ActiveModel::TestCase
+class NestedErrorTest < ActiveModel::TestCase
def test_initialize
topic = Topic.new
inner_error = ActiveModel::Error.new(topic, :title, :not_enough, count: 2)
diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md
index f708d42e43..a787e6dca7 100644
--- a/activerecord/CHANGELOG.md
+++ b/activerecord/CHANGELOG.md
@@ -1,3 +1,5 @@
+* Allow generated create_table migrations to include or skip timestamps
+ *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/associations/builder/collection_association.rb b/activerecord/lib/active_record/associations/builder/collection_association.rb
index e53665ce82..e78d25441b 100644
--- a/activerecord/lib/active_record/associations/builder/collection_association.rb
+++ b/activerecord/lib/active_record/associations/builder/collection_association.rb
@@ -22,9 +22,9 @@ module ActiveRecord::Associations::Builder # :nodoc:
def self.define_extensions(model, name, &block)
if block_given?
- extension_module_name = "#{model.name.demodulize}#{name.to_s.camelize}AssociationExtension"
+ extension_module_name = "#{name.to_s.camelize}AssociationExtension"
extension = Module.new(&block)
- model.module_parent.const_set(extension_module_name, extension)
+ model.const_set(extension_module_name, extension)
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb b/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
index 52a796b926..d6dbef3fc8 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/savepoints.rb
@@ -8,15 +8,15 @@ module ActiveRecord
end
def create_savepoint(name = current_savepoint_name)
- execute("SAVEPOINT #{name}")
+ execute("SAVEPOINT #{name}", "TRANSACTION")
end
def exec_rollback_to_savepoint(name = current_savepoint_name)
- execute("ROLLBACK TO SAVEPOINT #{name}")
+ execute("ROLLBACK TO SAVEPOINT #{name}", "TRANSACTION")
end
def release_savepoint(name = current_savepoint_name)
- execute("RELEASE SAVEPOINT #{name}")
+ execute("RELEASE SAVEPOINT #{name}", "TRANSACTION")
end
end
end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
index c9e84e48cc..7b6321d83b 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/transaction.rb
@@ -98,9 +98,13 @@ module ActiveRecord
end
def rollback_records
- ite = records.uniq
+ ite = records.uniq(&:object_id)
+ already_run_callbacks = {}
while record = ite.shift
- record.rolledback!(force_restore_state: full_rollback?)
+ trigger_callbacks = record.trigger_transactional_callbacks?
+ should_run_callbacks = !already_run_callbacks[record] && trigger_callbacks
+ already_run_callbacks[record] ||= trigger_callbacks
+ record.rolledback!(force_restore_state: full_rollback?, should_run_callbacks: should_run_callbacks)
end
ensure
ite.each do |i|
@@ -113,10 +117,14 @@ module ActiveRecord
end
def commit_records
- ite = records.uniq
+ ite = records.uniq(&:object_id)
+ already_run_callbacks = {}
while record = ite.shift
if @run_commit_callbacks
- record.committed!
+ trigger_callbacks = record.trigger_transactional_callbacks?
+ should_run_callbacks = !already_run_callbacks[record] && trigger_callbacks
+ already_run_callbacks[record] ||= trigger_callbacks
+ record.committed!(should_run_callbacks: should_run_callbacks)
else
# if not running callbacks, only adds the record to the parent transaction
connection.add_transaction_record(record)
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 282b2b1838..d239ecff89 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -204,7 +204,7 @@ module ActiveRecord
end
def begin_db_transaction
- execute "BEGIN"
+ execute("BEGIN", "TRANSACTION")
end
def begin_isolated_db_transaction(isolation)
@@ -213,11 +213,11 @@ module ActiveRecord
end
def commit_db_transaction #:nodoc:
- execute "COMMIT"
+ execute("COMMIT", "TRANSACTION")
end
def exec_rollback_db_transaction #:nodoc:
- execute "ROLLBACK"
+ execute("ROLLBACK", "TRANSACTION")
end
def empty_insert_statement_value(primary_key = nil)
diff --git a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
index d872bd662f..45ec79ca78 100644
--- a/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/postgresql/database_statements.rb
@@ -145,7 +145,7 @@ module ActiveRecord
# Begins a transaction.
def begin_db_transaction
- execute "BEGIN"
+ execute("BEGIN", "TRANSACTION")
end
def begin_isolated_db_transaction(isolation)
@@ -155,12 +155,12 @@ module ActiveRecord
# Commits a transaction.
def commit_db_transaction
- execute "COMMIT"
+ execute("COMMIT", "TRANSACTION")
end
# Aborts a transaction.
def exec_rollback_db_transaction
- execute "ROLLBACK"
+ execute("ROLLBACK", "TRANSACTION")
end
private
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb b/activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb
index 46ce1a15b5..73b80e8c15 100644
--- a/activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/sqlite3/database_statements.rb
@@ -68,15 +68,15 @@ module ActiveRecord
alias :exec_update :exec_delete
def begin_db_transaction #:nodoc:
- log("begin transaction", nil) { @connection.transaction }
+ log("begin transaction", "TRANSACTION") { @connection.transaction }
end
def commit_db_transaction #:nodoc:
- log("commit transaction", nil) { @connection.commit }
+ log("commit transaction", "TRANSACTION") { @connection.commit }
end
def exec_rollback_db_transaction #:nodoc:
- log("rollback transaction", nil) { @connection.rollback }
+ log("rollback transaction", "TRANSACTION") { @connection.rollback }
end
diff --git a/activerecord/lib/active_record/middleware/database_selector.rb b/activerecord/lib/active_record/middleware/database_selector.rb
index b5b5df074c..93a1a39c3e 100644
--- a/activerecord/lib/active_record/middleware/database_selector.rb
+++ b/activerecord/lib/active_record/middleware/database_selector.rb
@@ -35,10 +35,10 @@ module ActiveRecord
# config.active_record.database_resolver = MyResolver
# config.active_record.database_resolver_context = MyResolver::MySession
class DatabaseSelector
- def initialize(app, resolver_klass = Resolver, context_klass = Resolver::Session, options = {})
+ def initialize(app, resolver_klass = nil, context_klass = nil, options = {})
@app = app
- @resolver_klass = resolver_klass
- @context_klass = context_klass
+ @resolver_klass = resolver_klass || Resolver
+ @context_klass = context_klass || Resolver::Session
@options = options
end
diff --git a/activerecord/lib/active_record/touch_later.rb b/activerecord/lib/active_record/touch_later.rb
index b60cc96165..6872b7844e 100644
--- a/activerecord/lib/active_record/touch_later.rb
+++ b/activerecord/lib/active_record/touch_later.rb
@@ -18,6 +18,7 @@ module ActiveRecord
surreptitiously_touch @_defer_touch_attrs
add_to_transaction
+ @_new_record_before_last_commit ||= false
# touch the parents as we are not calling the after_save callbacks
self.class.reflect_on_all_associations(:belongs_to).each do |r|
diff --git a/activerecord/lib/active_record/transactions.rb b/activerecord/lib/active_record/transactions.rb
index 9e03deede7..148bc0550c 100644
--- a/activerecord/lib/active_record/transactions.rb
+++ b/activerecord/lib/active_record/transactions.rb
@@ -333,7 +333,7 @@ module ActiveRecord
# Ensure that it is not called if the object was never persisted (failed create),
# but call it after the commit of a destroyed object.
def committed!(should_run_callbacks: true) #:nodoc:
- if should_run_callbacks && trigger_transactional_callbacks?
+ if should_run_callbacks
@_committed_already_called = true
_run_commit_without_transaction_enrollment_callbacks
_run_commit_callbacks
@@ -346,7 +346,7 @@ module ActiveRecord
# Call the #after_rollback callbacks. The +force_restore_state+ argument indicates if the record
# state should be rolled back to the beginning or just to the last savepoint.
def rolledback!(force_restore_state: false, should_run_callbacks: true) #:nodoc:
- if should_run_callbacks && trigger_transactional_callbacks?
+ if should_run_callbacks
_run_rollback_callbacks
_run_rollback_without_transaction_enrollment_callbacks
end
@@ -378,6 +378,11 @@ module ActiveRecord
status
end
+ def trigger_transactional_callbacks? # :nodoc:
+ (@_new_record_before_last_commit || _trigger_update_callback) && persisted? ||
+ _trigger_destroy_callback && destroyed?
+ end
+
private
attr_reader :_committed_already_called, :_trigger_update_callback, :_trigger_destroy_callback
@@ -392,10 +397,7 @@ module ActiveRecord
level: 0
}
@_start_transaction_state[:level] += 1
- remember_new_record_before_last_commit
- end
- def remember_new_record_before_last_commit
if _committed_already_called
@_new_record_before_last_commit = false
else
@@ -457,10 +459,6 @@ module ActiveRecord
self.class.connection.add_transaction_record(self)
end
- def trigger_transactional_callbacks?
- @_new_record_before_last_commit && !new_record? || _trigger_update_callback || _trigger_destroy_callback
- end
-
def has_transactional_callbacks?
!_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_before_commit_callbacks.empty?
end
diff --git a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
index cb2c74f1ca..0620a515bd 100644
--- a/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
+++ b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb
@@ -7,6 +7,7 @@ module ActiveRecord
class MigrationGenerator < Base # :nodoc:
argument :attributes, type: :array, default: [], banner: "field[:type][:index] field[:type][:index]"
+ class_option :timestamps, type: :boolean
class_option :primary_key_type, type: :string, desc: "The type for primary key"
class_option :database, type: :string, aliases: %i(--db), desc: "The database for your migration. By default, the current environment's primary database is used."
diff --git a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
index ba477c63f4..96cfabf58f 100644
--- a/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
+++ b/activerecord/test/cases/adapters/postgresql/referential_integrity_test.rb
@@ -13,7 +13,7 @@ class PostgreSQLReferentialIntegrityTest < ActiveRecord::PostgreSQLTestCase
end
module MissingSuperuserPrivileges
- def execute(sql)
+ def execute(sql, name = nil)
if IS_REFERENTIAL_INTEGRITY_SQL.call(sql)
super "BROKEN;" rescue nil # put transaction in broken state
raise ActiveRecord::StatementInvalid, "PG::InsufficientPrivilege"
@@ -24,7 +24,7 @@ class PostgreSQLReferentialIntegrityTest < ActiveRecord::PostgreSQLTestCase
end
module ProgrammerMistake
- def execute(sql)
+ def execute(sql, name = nil)
if IS_REFERENTIAL_INTEGRITY_SQL.call(sql)
raise ArgumentError, "something is not right."
else
diff --git a/activerecord/test/cases/adapters/sqlite3/bind_parameter_test.rb b/activerecord/test/cases/adapters/sqlite3/bind_parameter_test.rb
deleted file mode 100644
index 93a7dafebd..0000000000
--- a/activerecord/test/cases/adapters/sqlite3/bind_parameter_test.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-require "cases/helper"
-require "models/topic"
-
-module ActiveRecord
- module ConnectionAdapters
- class SQLite3Adapter
- class BindParameterTest < ActiveRecord::SQLite3TestCase
- def test_too_many_binds
- topics = Topic.where(id: (1..999).to_a << 2**63)
- assert_equal Topic.count, topics.count
-
- topics = Topic.where.not(id: (1..999).to_a << 2**63)
- assert_equal 0, topics.count
- end
- end
- end
- end
-end
diff --git a/activerecord/test/cases/associations/extension_test.rb b/activerecord/test/cases/associations/extension_test.rb
index d93d787f7c..5e6e3e8ec4 100644
--- a/activerecord/test/cases/associations/extension_test.rb
+++ b/activerecord/test/cases/associations/extension_test.rb
@@ -70,8 +70,8 @@ class AssociationsExtensionsTest < ActiveRecord::TestCase
extend!(Developer)
extend!(MyApplication::Business::Developer)
- assert Object.const_get "DeveloperAssociationNameAssociationExtension"
- assert MyApplication::Business.const_get "DeveloperAssociationNameAssociationExtension"
+ assert Developer.const_get "AssociationNameAssociationExtension"
+ assert MyApplication::Business::Developer.const_get "AssociationNameAssociationExtension"
end
def test_proxy_association_after_scoped
diff --git a/activerecord/test/cases/test_case.rb b/activerecord/test/cases/test_case.rb
index 78dc0a6d9f..81f7226718 100644
--- a/activerecord/test/cases/test_case.rb
+++ b/activerecord/test/cases/test_case.rb
@@ -107,20 +107,12 @@ module ActiveRecord
clear_log
- self.ignored_sql = [/^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/]
-
- attr_reader :ignore
-
- def initialize(ignore = Regexp.union(self.class.ignored_sql))
- @ignore = ignore
- end
-
def call(name, start, finish, message_id, values)
return if values[:cached]
sql = values[:sql]
self.class.log_all << sql
- self.class.log << sql unless values[:name] == "SCHEMA" || ignore.match?(sql)
+ self.class.log << sql unless ["SCHEMA", "TRANSACTION"].include? values[:name]
end
end
diff --git a/activerecord/test/cases/transaction_callbacks_test.rb b/activerecord/test/cases/transaction_callbacks_test.rb
index 135e2cb382..100bd6a925 100644
--- a/activerecord/test/cases/transaction_callbacks_test.rb
+++ b/activerecord/test/cases/transaction_callbacks_test.rb
@@ -551,6 +551,8 @@ class CallbacksOnMultipleActionsTest < ActiveRecord::TestCase
end
class CallbacksOnDestroyUpdateActionRaceTest < ActiveRecord::TestCase
+ self.use_transactional_tests = false
+
class TopicWithHistory < ActiveRecord::Base
self.table_name = :topics
@@ -564,11 +566,22 @@ class CallbacksOnDestroyUpdateActionRaceTest < ActiveRecord::TestCase
end
class TopicWithCallbacksOnDestroy < TopicWithHistory
- after_commit(on: :destroy) { |record| record.class.history << :destroy }
+ after_commit(on: :destroy) { |record| record.class.history << :commit_on_destroy }
+ after_rollback(on: :destroy) { |record| record.class.history << :rollback_on_destroy }
+
+ before_destroy :before_destroy_for_transaction
+
+ private
+ def before_destroy_for_transaction; end
end
class TopicWithCallbacksOnUpdate < TopicWithHistory
- after_commit(on: :update) { |record| record.class.history << :update }
+ after_commit(on: :update) { |record| record.class.history << :commit_on_update }
+
+ before_save :before_save_for_transaction
+
+ private
+ def before_save_for_transaction; end
end
def test_trigger_once_on_multiple_deletions
@@ -576,10 +589,39 @@ class CallbacksOnDestroyUpdateActionRaceTest < ActiveRecord::TestCase
topic = TopicWithCallbacksOnDestroy.new
topic.save
topic_clone = TopicWithCallbacksOnDestroy.find(topic.id)
+
+ topic.define_singleton_method(:before_destroy_for_transaction) do
+ topic_clone.destroy
+ end
+
topic.destroy
- topic_clone.destroy
- assert_equal [:destroy], TopicWithCallbacksOnDestroy.history
+ assert_equal [:commit_on_destroy], TopicWithCallbacksOnDestroy.history
+ end
+
+ def test_rollback_on_multiple_deletions
+ TopicWithCallbacksOnDestroy.clear_history
+ topic = TopicWithCallbacksOnDestroy.new
+ topic.save
+ topic_clone = TopicWithCallbacksOnDestroy.find(topic.id)
+
+ topic.define_singleton_method(:before_destroy_for_transaction) do
+ topic_clone.update!(author_name: "Test Author Clone")
+ topic_clone.destroy
+ end
+
+ TopicWithCallbacksOnDestroy.transaction do
+ topic.update!(author_name: "Test Author")
+ topic.destroy
+ raise ActiveRecord::Rollback
+ end
+
+ assert_not_predicate topic, :destroyed?
+ assert_not_predicate topic_clone, :destroyed?
+ assert_equal [nil, "Test Author"], topic.author_name_change_to_be_saved
+ assert_equal [nil, "Test Author Clone"], topic_clone.author_name_change_to_be_saved
+
+ assert_equal [:rollback_on_destroy], TopicWithCallbacksOnDestroy.history
end
def test_trigger_on_update_where_row_was_deleted
@@ -587,7 +629,11 @@ class CallbacksOnDestroyUpdateActionRaceTest < ActiveRecord::TestCase
topic = TopicWithCallbacksOnUpdate.new
topic.save
topic_clone = TopicWithCallbacksOnUpdate.find(topic.id)
- topic.destroy
+
+ topic_clone.define_singleton_method(:before_save_for_transaction) do
+ topic.destroy
+ end
+
topic_clone.author_name = "Test Author"
topic_clone.save
diff --git a/activerecord/test/models/developer.rb b/activerecord/test/models/developer.rb
index c6574cf6e7..92d01ba338 100644
--- a/activerecord/test/models/developer.rb
+++ b/activerecord/test/models/developer.rb
@@ -2,13 +2,13 @@
require "ostruct"
-module DeveloperProjectsAssociationExtension2
- def find_least_recent
- order("id ASC").first
+class Developer < ActiveRecord::Base
+ module ProjectsAssociationExtension2
+ def find_least_recent
+ order("id ASC").first
+ end
end
-end
-class Developer < ActiveRecord::Base
self.ignored_columns = %w(first_name last_name)
has_and_belongs_to_many :projects do
@@ -24,19 +24,19 @@ class Developer < ActiveRecord::Base
has_and_belongs_to_many :shared_computers, class_name: "Computer"
has_and_belongs_to_many :projects_extended_by_name,
- -> { extending(DeveloperProjectsAssociationExtension) },
+ -> { extending(ProjectsAssociationExtension) },
class_name: "Project",
join_table: "developers_projects",
association_foreign_key: "project_id"
has_and_belongs_to_many :projects_extended_by_name_twice,
- -> { extending(DeveloperProjectsAssociationExtension, DeveloperProjectsAssociationExtension2) },
+ -> { extending(ProjectsAssociationExtension, ProjectsAssociationExtension2) },
class_name: "Project",
join_table: "developers_projects",
association_foreign_key: "project_id"
has_and_belongs_to_many :projects_extended_by_name_and_block,
- -> { extending(DeveloperProjectsAssociationExtension) },
+ -> { extending(ProjectsAssociationExtension) },
class_name: "Project",
join_table: "developers_projects",
association_foreign_key: "project_id" do
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index ccdf2c3040..e494f66c5a 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -5,18 +5,19 @@
Before:
- 'foobar'.truncate(5).frozen?
- => true
- 'foobar'.truncate(6).frozen?
- => false
+ 'foobar'.truncate(5).frozen?
+ # => true
+ 'foobar'.truncate(6).frozen?
+ # => false
After:
- 'foobar'.truncate(5).frozen?
- => false
- 'foobar'.truncate(6).frozen?
- => false
+ 'foobar'.truncate(5).frozen?
+ # => false
+ 'foobar'.truncate(6).frozen?
+ # => false
*Jordan Thomas*
+
Please check [6-0-stable](https://github.com/rails/rails/blob/6-0-stable/activesupport/CHANGELOG.md) for previous changes.
diff --git a/activesupport/lib/active_support/notifications.rb b/activesupport/lib/active_support/notifications.rb
index d9e93b530c..555c0ad1d3 100644
--- a/activesupport/lib/active_support/notifications.rb
+++ b/activesupport/lib/active_support/notifications.rb
@@ -38,6 +38,19 @@ module ActiveSupport
# payload # => Hash, the payload
# end
#
+ # Here, the +start+ and +finish+ values represent wall-clock time. If you are
+ # concerned about accuracy, you can register a monotonic subscriber.
+ #
+ # ActiveSupport::Notifications.monotonic_subscribe('render') do |name, start, finish, id, payload|
+ # name # => String, name of the event (such as 'render' from above)
+ # start # => Monotonic time, when the instrumented block started execution
+ # finish # => Monotonic time, when the instrumented block ended execution
+ # id # => String, unique ID for the instrumenter that fired the event
+ # payload # => Hash, the payload
+ # end
+ #
+ # The +start+ and +finish+ values above represent monotonic time.
+ #
# For instance, let's store all "render" events in an array:
#
# events = []
@@ -135,6 +148,16 @@ module ActiveSupport
# during the execution of the block. The callback is unsubscribed automatically
# after that.
#
+ # To record +started+ and +finished+ values with monotonic time,
+ # specify the optional <tt>:monotonic</tt> option to the
+ # <tt>subscribed</tt> method. The <tt>:monotonic</tt> option is set
+ # to +false+ by default.
+ #
+ # callback = lambda {|name, started, finished, unique_id, payload| ... }
+ # ActiveSupport::Notifications.subscribed(callback, "sql.active_record", monotonic: true) do
+ # ...
+ # end
+ #
# === Manual Unsubscription
#
# The +subscribe+ method returns a subscriber object:
@@ -209,11 +232,17 @@ module ActiveSupport
# @event = event
# end
def subscribe(*args, &block)
- notifier.subscribe(*args, &block)
+ pattern, callback = *args
+ notifier.subscribe(pattern, callback, false, &block)
+ end
+
+ def monotonic_subscribe(*args, &block)
+ pattern, callback = *args
+ notifier.subscribe(pattern, callback, true, &block)
end
- def subscribed(callback, *args, &block)
- subscriber = subscribe(*args, &callback)
+ def subscribed(callback, pattern, monotonic: false, &block)
+ subscriber = notifier.subscribe(pattern, callback, monotonic)
yield
ensure
unsubscribe(subscriber)
diff --git a/activesupport/lib/active_support/notifications/fanout.rb b/activesupport/lib/active_support/notifications/fanout.rb
index c506b35b1e..c37bec4ee5 100644
--- a/activesupport/lib/active_support/notifications/fanout.rb
+++ b/activesupport/lib/active_support/notifications/fanout.rb
@@ -20,8 +20,8 @@ module ActiveSupport
super
end
- def subscribe(pattern = nil, callable = nil, &block)
- subscriber = Subscribers.new(pattern, callable || block)
+ def subscribe(pattern = nil, callable = nil, monotonic = false, &block)
+ subscriber = Subscribers.new(monotonic, pattern, callable || block)
synchronize do
if String === pattern
@string_subscribers[pattern] << subscriber
@@ -84,8 +84,8 @@ module ActiveSupport
end
module Subscribers # :nodoc:
- def self.new(pattern, listener)
- subscriber_class = Timed
+ def self.new(monotonic, pattern, listener)
+ subscriber_class = monotonic ? MonotonicTimed : Timed
if listener.respond_to?(:start) && listener.respond_to?(:finish)
subscriber_class = Evented
@@ -190,6 +190,23 @@ module ActiveSupport
end
end
+ class MonotonicTimed < Evented # :nodoc:
+ def publish(name, *args)
+ @delegate.call name, *args
+ end
+
+ def start(name, id, payload)
+ timestack = Thread.current[:_timestack_monotonic] ||= []
+ timestack.push Concurrent.monotonic_time
+ end
+
+ def finish(name, id, payload)
+ timestack = Thread.current[:_timestack_monotonic]
+ started = timestack.pop
+ @delegate.call(name, started, Concurrent.monotonic_time, id, payload)
+ end
+ end
+
class EventObject < Evented
def start(name, id, payload)
stack = Thread.current[:_event_stack] ||= []
diff --git a/activesupport/test/notifications_test.rb b/activesupport/test/notifications_test.rb
index d3c660a014..3b98749f1b 100644
--- a/activesupport/test/notifications_test.rb
+++ b/activesupport/test/notifications_test.rb
@@ -62,6 +62,38 @@ module Notifications
end
end
+ class TimedAndMonotonicTimedSubscriberTest < TestCase
+ def test_subscribe
+ event_name = "foo"
+ class_of_started = nil
+ class_of_finished = nil
+
+ ActiveSupport::Notifications.subscribe(event_name) do |name, started, finished, unique_id, data|
+ class_of_started = started.class
+ class_of_finished = finished.class
+ end
+
+ ActiveSupport::Notifications.instrument(event_name)
+
+ assert_equal [Time, Time], [class_of_started, class_of_finished]
+ end
+
+ def test_monotonic_subscribe
+ event_name = "foo"
+ class_of_started = nil
+ class_of_finished = nil
+
+ ActiveSupport::Notifications.monotonic_subscribe(event_name) do |name, started, finished, unique_id, data|
+ class_of_started = started.class
+ class_of_finished = finished.class
+ end
+
+ ActiveSupport::Notifications.instrument(event_name)
+
+ assert_equal [Float, Float], [class_of_started, class_of_finished]
+ end
+ end
+
class SubscribedTest < TestCase
def test_subscribed
name = "foo"
@@ -95,6 +127,42 @@ module Notifications
ensure
ActiveSupport::Notifications.notifier = old_notifier
end
+
+ def test_timed_subscribed
+ event_name = "foo"
+ class_of_started = nil
+ class_of_finished = nil
+ callback = lambda do |name, started, finished, unique_id, data|
+ class_of_started = started.class
+ class_of_finished = finished.class
+ end
+
+ ActiveSupport::Notifications.subscribed(callback, event_name) do
+ ActiveSupport::Notifications.instrument(event_name)
+ end
+
+ ActiveSupport::Notifications.instrument(event_name)
+
+ assert_equal [Time, Time], [class_of_started, class_of_finished]
+ end
+
+ def test_monotonic_timed_subscribed
+ event_name = "foo"
+ class_of_started = nil
+ class_of_finished = nil
+ callback = lambda do |name, started, finished, unique_id, data|
+ class_of_started = started.class
+ class_of_finished = finished.class
+ end
+
+ ActiveSupport::Notifications.subscribed(callback, event_name, monotonic: true) do
+ ActiveSupport::Notifications.instrument(event_name)
+ end
+
+ ActiveSupport::Notifications.instrument(event_name)
+
+ assert_equal [Float, Float], [class_of_started, class_of_finished]
+ end
end
class UnsubscribeTest < TestCase
@@ -317,7 +385,6 @@ module Notifications
assert_equal 0, event.cpu_time
end
-
def test_events_consumes_information_given_as_payload
event = event(:foo, Concurrent.monotonic_time, Concurrent.monotonic_time + 1, random_id, payload: :bar)
assert_equal Hash[payload: :bar], event.payload
diff --git a/guides/source/6_0_release_notes.md b/guides/source/6_0_release_notes.md
index 17d1132e73..1b37bf50e1 100644
--- a/guides/source/6_0_release_notes.md
+++ b/guides/source/6_0_release_notes.md
@@ -151,25 +151,44 @@ Please refer to the [Changelog][action-cable] for detailed changes.
### Removals
+* Replace `ActionCable.startDebugging()` and `ActionCable.stopDebugging()`
+ with `ActionCable.logger.enabled`.
+ ([Pull Request](https://github.com/rails/rails/pull/34370))
+
### Deprecations
+* There are no deprecations for Action Cable in Rails 6.0.
+
### Notable changes
-* The ActionCable javascript package has been converted from CoffeeScript
- to ES2015, and we now publish the source code in the npm distribution.
+* Add support for the `channel_prefix` option for PostgreSQL subscription adapters
+ in `cable.yml`.
+ ([Pull Request](https://github.com/rails/rails/pull/35276))
+
+* Allow passing a custom configuration to `ActionCable::Server::Base`.
+ ([Pull Request](https://github.com/rails/rails/pull/34714))
+
+* Add `:action_cable_connection` and `:action_cable_channel` load hooks.
+ ([Pull Request](https://github.com/rails/rails/pull/35094))
- This allows ActionCable users to depend on the javascript source code
- rather than the compiled code, which can produce smaller javascript bundles.
+* Add `Channel::Base#broadcast_to` and `Channel::Base.broadcasting_for`.
+ ([Pull Request](https://github.com/rails/rails/pull/35021))
- This change includes some breaking changes to optional parts of the
- ActionCable javascript API:
+* Close a connection when calling `reject_unauthorized_connection` from an
+ `ActionCable::Connection`.
+ ([Pull Request](https://github.com/rails/rails/pull/34194))
- - Configuration of the WebSocket adapter and logger adapter have been moved
- from properties of `ActionCable` to properties of `ActionCable.adapters`.
+* Convert the Action Cable JavaScript package from CoffeeScript to ES2015 and
+ publish the source code in the npm distribution.
+ ([Pull Request](https://github.com/rails/rails/pull/34370))
- - The `ActionCable.startDebugging()` and `ActionCable.stopDebugging()`
- methods have been removed and replaced with the property
- `ActionCable.logger.enabled`.
+* Move the configuration of the WebSocket adapter and logger adapter
+ from properties of `ActionCable` to `ActionCable.adapters`.
+ ([Pull Request](https://github.com/rails/rails/pull/34370))
+
+* Add an `id` option to the Redis adapter to distinguish Action Cable's Redis
+ connections.
+ ([Pull Request](https://github.com/rails/rails/pull/33798))
Action Pack
-----------
@@ -183,7 +202,7 @@ Please refer to the [Changelog][action-pack] for detailed changes.
* Remove deprecated methods in `ActionDispatch::TestResponse`:
`#success?` in favor of `#successful?`, `#missing?` in favor of `#not_found?`,
- `#error?` in favor of `#server_error?`
+ `#error?` in favor of `#server_error?`.
([Commit](https://github.com/rails/rails/commit/13ddc92e079e59a0b894e31bf5bb4fdecbd235d1))
### Deprecations
@@ -210,7 +229,7 @@ Please refer to the [Changelog][action-pack] for detailed changes.
* Allow the use of `parsed_body` in `ActionController::TestCase`.
([Pull Request](https://github.com/rails/rails/pull/34717))
-* Raise an `ArgumentError` when multiple root routes exists in the same context
+* Raise an `ArgumentError` when multiple root routes exist in the same context
without `as:` naming specifications.
([Pull Request](https://github.com/rails/rails/pull/34494))
@@ -279,14 +298,14 @@ Please refer to the [Changelog][action-view] for detailed changes.
### Notable changes
-* Clear ActionView cache in development only on file changes, speeding up
+* Clear Action View cache in development only on file changes, speeding up
development mode.
([Pull Request](https://github.com/rails/rails/pull/35629))
* Move all of the Rails npm packages into a `@rails` scope.
([Pull Request](https://github.com/rails/rails/pull/34905))
-* Only accept formats from registered Mime types.
+* Only accept formats from registered MIME types.
([Pull Request](https://github.com/rails/rails/pull/35604), [Pull Request](https://github.com/rails/rails/pull/35753))
* Add allocations to the template and partial rendering server output.
@@ -304,7 +323,7 @@ Please refer to the [Changelog][action-view] for detailed changes.
enable `ActionView::Template` finalizers.
([Pull Request](https://github.com/rails/rails/pull/32418))
-* Extract the JS `confirm` call to its own, overridable method in `rails_ujs`.
+* Extract the JavaScript `confirm` call to its own, overridable method in `rails_ujs`.
([Pull Request](https://github.com/rails/rails/pull/32404))
* Add a `action_controller.default_enforce_utf8` configuration option to handle
@@ -383,7 +402,7 @@ Please refer to the [Changelog][active-record] for detailed changes.
* Remove support for passing the column name to `count` when a block is passed.
([Commit](https://github.com/rails/rails/commit/67356f2034ab41305af7218f7c8b2fee2d614129))
-* Remove support for delegation of missing methods in a relation to arel.
+* Remove support for delegation of missing methods in a relation to Arel.
([Commit](https://github.com/rails/rails/commit/d97980a16d76ad190042b4d8578109714e9c53d0))
* Remove support for delegating missing methods in a relation to private methods of the class.
@@ -426,7 +445,7 @@ Please refer to the [Changelog][active-record] for detailed changes.
### Notable changes
-* Bump the minimum sqlite3 version to 1.4.
+* Bump the minimum version of the `sqlite3` gem to 1.4.
([Pull Request](https://github.com/rails/rails/pull/35844))
* Add `rails db:prepare` to create a database if it doesn't exist, and run its migrations.
@@ -448,7 +467,7 @@ Please refer to the [Changelog][active-record] for detailed changes.
([Pull Request](https://github.com/rails/rails/pull/35631))
* Add `rails db:seed:replant` that truncates tables of each database
- for ther current environment and loads the seeds.
+ for the current environment and loads the seeds.
([Pull Request](https://github.com/rails/rails/pull/34779))
* Add `reselect` method, which is a short-hand for `unscope(:select).select(fields)`.
@@ -558,7 +577,7 @@ Please refer to the [Changelog][active-record] for detailed changes.
* Allow the `:to_table` option of `remove_foreign_key` to be invertible.
([Pull Request](https://github.com/rails/rails/pull/33530))
-* Fix default value for mysql time types with specified precision.
+* Fix default value for MySQL time types with specified precision.
([Pull Request](https://github.com/rails/rails/pull/33280))
* Fix the `touch` option to behave consistently with `Persistence#touch` method.
@@ -723,10 +742,196 @@ Please refer to the [Changelog][active-support] for detailed changes.
### Removals
+* Remove deprecated `#acronym_regex` method from `Inflections`.
+ ([Commit](https://github.com/rails/rails/commit/0ce67d3cd6d1b7b9576b07fecae3dd5b422a5689))
+
+* Remove deprecated `Module#reachable?` method.
+ ([Commit](https://github.com/rails/rails/commit/6eb1d56a333fd2015610d31793ed6281acd66551))
+
+* Remove `` Kernel#` `` without any replacement.
+ ([Pull Request](https://github.com/rails/rails/pull/31253))
+
### Deprecations
+* Deprecate using negative integer arguments for `String#first` and
+ `String#last`.
+ ([Pull Request](https://github.com/rails/rails/pull/33058))
+
+* Deprecate `ActiveSupport::Multibyte::Unicode#downcase/upcase/swapcase`
+ in favor of `String#downcase/upcase/swapcase`.
+ ([Pull Request](https://github.com/rails/rails/pull/34123))
+
+* Deprecate `ActiveSupport::Multibyte::Unicode#normalize`
+ and `ActiveSupport::Multibyte::Chars#normalize` in favor of
+ `String#unicode_normalize`.
+ ([Pull Request](https://github.com/rails/rails/pull/34202))
+
+* Deprecate `ActiveSupport::Multibyte::Chars.consumes?` in favor of
+ `String#is_utf8?`.
+ ([Pull Request](https://github.com/rails/rails/pull/34215))
+
+* Deprecate `ActiveSupport::Multibyte::Unicode#pack_graphemes(array)`
+ and `ActiveSupport::Multibyte::Unicode#unpack_graphemes(string)`
+ in favor of `array.flatten.pack("U*")` and `string.scan(/\X/).map(&:codepoints)`,
+ respectively.
+ ([Pull Request](https://github.com/rails/rails/pull/34254))
+
### Notable changes
+* Add support for parallel testing.
+ ([Pull Request](https://github.com/rails/rails/pull/31900))
+
+* Make sure that `String#strip_heredoc` preserves frozen-ness of strings.
+ ([Pull Request](https://github.com/rails/rails/pull/32037))
+
+* Add `String#truncate_bytes` to truncate a string to a maximum bytesize
+ without breaking multibyte characters or grapheme clusters.
+ ([Pull Request](https://github.com/rails/rails/pull/27319))
+
+* Add `private` option to `delegate` method in order to delegate to
+ private methods. This option accepts `true/false` as the value.
+ ([Pull Request](https://github.com/rails/rails/pull/31944))
+
+* Add support for translations through I18n for `ActiveSupport::Inflector#ordinal`
+ and `ActiveSupport::Inflector#ordinalize`.
+ ([Pull Request](https://github.com/rails/rails/pull/32168))
+
+* Add `before?` and `after?` methods to `Date`, `DateTime`,
+ `Time`, and `TimeWithZone`.
+ ([Pull Request](https://github.com/rails/rails/pull/32185))
+
+* Fix bug where `URI.unescape` would fail with mixed Unicode/escaped character
+ input.
+ ([Pull Request](https://github.com/rails/rails/pull/32183))
+
+* Fix bug where `ActiveSupport::Cache` would massively inflate the storage
+ size when compression was enabled.
+ ([Pull Request](https://github.com/rails/rails/pull/32539))
+
+* Redis cache store: `delete_matched` no longer blocks the Redis server.
+ ([Pull Request](https://github.com/rails/rails/pull/32614))
+
+* Fix bug where `ActiveSupport::TimeZone.all` would fail when tzinfo data for
+ any timezone defined in `ActiveSupport::TimeZone::MAPPING` was missing.
+ ([Pull Request](https://github.com/rails/rails/pull/32613))
+
+* Add `Enumerable#index_with` which allows creating a hash from an enumerable
+ with the value from a passed block or a default argument.
+ ([Pull Request](https://github.com/rails/rails/pull/32523))
+
+* Allow `Range#===` and `Range#cover?` methods to work with `Range` argument.
+ ([Pull Request](https://github.com/rails/rails/pull/32938))
+
+* Support key expiry in `increment/decrement` operations of RedisCacheStore.
+ ([Pull Request](https://github.com/rails/rails/pull/33254))
+
+* Add cpu time, idle time, and allocations features to log subscriber events.
+ ([Pull Request](https://github.com/rails/rails/pull/33449))
+
+* Add support for event object to the Active Support notification system.
+ ([Pull Request](https://github.com/rails/rails/pull/33451))
+
+* Add support for not caching `nil` entries by introducing new option `skip_nil`
+ for `ActiveSupport::Cache#fetch`.
+ ([Pull Request](https://github.com/rails/rails/pull/25437))
+
+* Add `Array#extract!` method which removes and returns the elements for which
+ block returns a true value.
+ ([Pull Request](https://github.com/rails/rails/pull/33137))
+
+* Keep an HTML-safe string HTML-safe after slicing.
+ ([Pull Request](https://github.com/rails/rails/pull/33808))
+
+* Add support for tracing constant autoloads via logging.
+ ([Commit](https://github.com/rails/rails/commit/c03bba4f1f03bad7dc034af555b7f2b329cf76f5))
+
+* Define `unfreeze_time` as an alias of `travel_back`.
+ ([Pull Request](https://github.com/rails/rails/pull/33813))
+
+* Change `ActiveSupport::TaggedLogging.new` to return a new logger instance
+ instead of mutating the one received as argument.
+ ([Pull Request](https://github.com/rails/rails/pull/27792))
+
+* Treat `#delete_prefix`, `#delete_suffix` and `#unicode_normalize` methods
+ as non HTML-safe methods.
+ ([Pull Request](https://github.com/rails/rails/pull/33990))
+
+* Fix bug where `#without` for `ActiveSupport::HashWithIndifferentAccess`
+ would fail with symbol arguments.
+ ([Pull Request](https://github.com/rails/rails/pull/34012))
+
+* Rename `Module#parent`, `Module#parents`, and `Module#parent_name` to
+ `module_parent`, `module_parents`, and `module_parent_name`.
+ ([Pull Request](https://github.com/rails/rails/pull/34051))
+
+* Add `ActiveSupport::ParameterFilter`.
+ ([Pull Request](https://github.com/rails/rails/pull/34039))
+
+* Fix issue where duration was being rounded to a full second when a float
+ was added to the duration.
+ ([Pull Request](https://github.com/rails/rails/pull/34135))
+
+* Make `#to_options` an alias for `#symbolize_keys` in
+ `ActiveSupport::HashWithIndifferentAccess`.
+ ([Pull Request](https://github.com/rails/rails/pull/34360))
+
+* Don't raise an exception anymore if the same block is included multiple times
+ for a Concern.
+ ([Pull Request](https://github.com/rails/rails/pull/34553))
+
+* Preserve key order passed to `ActiveSupport::CacheStore#fetch_multi`.
+ ([Pull Request](https://github.com/rails/rails/pull/34700))
+
+* Fix `String#safe_constantize` to not throw a `LoadError` for incorrectly
+ cased constant references.
+ ([Pull Request](https://github.com/rails/rails/pull/34892))
+
+* Add `Hash#deep_transform_values` and `Hash#deep_transform_values!`.
+ ([Commit](https://github.com/rails/rails/commit/b8dc06b8fdc16874160f61dcf58743fcc10e57db))
+
+* Add `ActiveSupport::HashWithIndifferentAccess#assoc`.
+ ([Pull Request](https://github.com/rails/rails/pull/35080))
+
+* Add `before_reset` callback to `CurrentAttributes` and define
+ `after_reset` as an alias of `resets` for symmetry.
+ ([Pull Request](https://github.com/rails/rails/pull/35063))
+
+* Revise `ActiveSupport::Notifications.unsubscribe` to correctly
+ handle Regex or other multiple-pattern subscribers.
+ ([Pull Request](https://github.com/rails/rails/pull/32861))
+
+* Add new autoloading mechanism using Zeitwerk.
+ ([Commit](https://github.com/rails/rails/commit/e53430fa9af239e21e11548499d814f540d421e5))
+
+* Add `Array#including` and `Enumerable#including` to conveniently enlarge
+ a collection.
+ ([Commit](https://github.com/rails/rails/commit/bfaa3091c3c32b5980a614ef0f7b39cbf83f6db3))
+
+* Rename `Array#without` and `Enumerable#without` to `Array#excluding`
+ and `Enumerable#excluding`. Old method names are retained as aliases.
+ ([Commit](https://github.com/rails/rails/commit/bfaa3091c3c32b5980a614ef0f7b39cbf83f6db3))
+
+* Add support for supplying `locale` to `transliterate` and `parameterize`.
+ ([Pull Request](https://github.com/rails/rails/pull/35571))
+
+* Fix `Time#advance` to work with dates before 1001-03-07.
+ ([Pull Request](https://github.com/rails/rails/pull/35659))
+
+* Update `ActiveSupport::Notifications::Instrumenter#instrument` to allow
+ not passing block.
+ ([Pull Request](https://github.com/rails/rails/pull/35705))
+
+* Use weak references in descendants tracker to allow anonymous subclasses to
+ be garbage collected.
+ ([Pull Request](https://github.com/rails/rails/pull/31442))
+
+* Calling test methods with `with_info_handler` method to allow minitest-hooks
+ plugin to work.
+ ([Commit](https://github.com/rails/rails/commit/758ba117a008b6ea2d3b92c53b6a7a8d7ccbca69))
+
+* Preserve `html_safe?` status on `ActiveSupport::SafeBuffer#*`.
+ ([Pull Request](https://github.com/rails/rails/pull/36012))
+
Active Job
----------
diff --git a/guides/source/action_text_overview.md b/guides/source/action_text_overview.md
index 08ec35e52a..07919775e2 100644
--- a/guides/source/action_text_overview.md
+++ b/guides/source/action_text_overview.md
@@ -91,7 +91,7 @@ end
By default, the Action Text editor and content is styled by the Trix defaults.
If you want to change these defaults, you'll want to remove
-the `app/assets/stylesheets/actiontext.css` linker and base your stylings on
+the `app/assets/stylesheets/actiontext.scss` linker and base your stylings on
the [contents of that file](https://raw.githubusercontent.com/basecamp/trix/master/dist/trix.css).
You can also style the HTML used for embedded images and other attachments (known as blobs).
diff --git a/guides/source/active_record_migrations.md b/guides/source/active_record_migrations.md
index 270e4a3bf9..9398244ccf 100644
--- a/guides/source/active_record_migrations.md
+++ b/guides/source/active_record_migrations.md
@@ -225,6 +225,8 @@ class CreateProducts < ActiveRecord::Migration[5.0]
create_table :products do |t|
t.string :name
t.string :part_number
+
+ t.timestamps
end
end
end
diff --git a/guides/source/active_support_instrumentation.md b/guides/source/active_support_instrumentation.md
index 4868b00bbe..9f15e70da6 100644
--- a/guides/source/active_support_instrumentation.md
+++ b/guides/source/active_support_instrumentation.md
@@ -643,7 +643,16 @@ The block receives the following arguments:
```ruby
ActiveSupport::Notifications.subscribe "process_action.action_controller" do |name, started, finished, unique_id, data|
# your own custom stuff
- Rails.logger.info "#{name} Received!"
+ Rails.logger.info "#{name} Received! (started: #{started}, finished: #{finished})" # process_action.action_controller Received (started: 2019-05-05 13:43:57 -0800, finished: 2019-05-05 13:43:58 -0800)
+end
+```
+
+If you are concerned about the accuracy of `started` and `finished` to compute a precise elapsed time then use `ActiveSupport::Notifications.monotonic_subscribe`. The given block would receive the same arguments as above but the `started` and `finished` will have values with an accurate monotonic time instead of wall-clock time.
+
+```ruby
+ActiveSupport::Notifications.monotonic_subscribe "process_action.action_controller" do |name, started, finished, unique_id, data|
+ # your own custom stuff
+ Rails.logger.info "#{name} Received! (started: #{started}, finished: #{finished})" # process_action.action_controller Received (started: 1560978.425334, finished: 1560979.429234)
end
```
diff --git a/guides/source/configuring.md b/guides/source/configuring.md
index 3863323bd2..3adc956ef9 100644
--- a/guides/source/configuring.md
+++ b/guides/source/configuring.md
@@ -145,7 +145,7 @@ defaults to `:debug` for all environments. The available log levels are: `:debug
* `secret_key_base` is used for specifying a key which allows sessions for the application to be verified against a known secure key to prevent tampering. Applications get a random generated key in test and development environments, other environments should set one in `config/credentials.yml.enc`.
-* `config.public_file_server.enabled` configures Rails to serve static files from the public directory. This option defaults to `true`, but in the production environment it is set to `false` because the server software (e.g. NGINX or Apache) used to run the application should serve static files instead. If you are running or testing your app in production mode using WEBrick (it is not recommended to use WEBrick in production) set the option to `true.` Otherwise, you won't be able to use page caching and request for files that exist under the public directory.
+* `config.public_file_server.enabled` configures Rails to serve static files from the public directory. This option defaults to `true`, but in the production environment it is set to `false` because the server software (e.g. NGINX or Apache) used to run the application should serve static files instead. If you are running or testing your app in production mode using WEBrick (it is not recommended to use WEBrick in production) set the option to `true`. Otherwise, you won't be able to use page caching and request for files that exist under the public directory.
* `config.session_store` specifies what class to use to store the session. Possible values are `:cookie_store` which is the default, `:mem_cache_store`, and `:disabled`. The last one tells Rails not to deal with sessions. Defaults to a cookie store with application name as the session key. Custom session stores can also be specified:
diff --git a/guides/source/contributing_to_ruby_on_rails.md b/guides/source/contributing_to_ruby_on_rails.md
index f86589bdf1..a6eb9907a0 100644
--- a/guides/source/contributing_to_ruby_on_rails.md
+++ b/guides/source/contributing_to_ruby_on_rails.md
@@ -340,7 +340,7 @@ $ TEST_DIR=generators bundle exec rake test
You can run the tests for a particular file by using:
```bash
-$ cd actionpack
+$ cd actionview
$ bundle exec ruby -w -Itest test/template/form_helper_test.rb
```
diff --git a/guides/source/testing.md b/guides/source/testing.md
index 18eecf49fa..9540bb2af5 100644
--- a/guides/source/testing.md
+++ b/guides/source/testing.md
@@ -781,7 +781,7 @@ This can be helpful for viewing the browser at the point a test failed, or
to view screenshots later for debugging.
Two methods are provided: `take_screenshot` and `take_failed_screenshot`.
-`take_failed_screenshot` is automatically included in `after_teardown` inside
+`take_failed_screenshot` is automatically included in `before_teardown` inside
Rails.
The `take_screenshot` helper method can be included anywhere in your tests to
diff --git a/guides/source/upgrading_ruby_on_rails.md b/guides/source/upgrading_ruby_on_rails.md
index 7e4152aa51..76ee5c0c48 100644
--- a/guides/source/upgrading_ruby_on_rails.md
+++ b/guides/source/upgrading_ruby_on_rails.md
@@ -133,6 +133,223 @@ Action Cable JavaScript API:
+ ActionCable.logger.enabled = false
```
+### Autoloading
+
+The default configuration for Rails 6
+
+```ruby
+# config/application.rb
+
+config.load_defaults "6.0"
+```
+
+enables `zeitwerk` autoloading mode on CRuby. In that mode, autoloading, reloading, and eager loading are managed by [Zeitwerk](https://github.com/fxn/zeitwerk).
+
+#### Public API
+
+In general, applications do not need to use the API of Zeitwerk directly. Rails sets things up according to the existing contract: `config.autoload_paths`, `config.cache_classes`, etc.
+
+While applications should stick to that interface, the actual Zeitwerk loader object can be accessed as
+
+```ruby
+Rails.autoloaders.main
+```
+
+That may be handy if you need to preload STIs or configure a custom inflector, for example.
+
+#### Project Structure
+
+If the application being upgraded autoloads correctly, the project structure should be already mostly compatible.
+
+However, `classic` mode infers file names from missing constant names (`underscore`), whereas `zeitwerk` mode infers constant names from file names (`camelize`). These helpers are not always inverse of each other, in particular if acronyms are involved. For instance, `"FOO".underscore` is `"foo"`, but `"foo".camelize` is `"Foo"`, not `"FOO"`. Compatibility can be checked by setting `classic` mode first temporarily:
+
+```ruby
+# config/application.rb
+
+config.load_defaults "6.0"
+config.autoloader = :classic
+```
+
+and then running
+
+```
+bin/rails zeitwerk:check
+```
+
+When all is good, you can delete `config.autoloader = :classic`.
+
+#### require_dependency
+
+All known use cases of `require_dependency` have been eliminated, you should grep the project and delete them.
+
+In the case of STIs with a hierarchy of more than two levels, you can preload the leaves of the hierarchy in an initializer:
+
+```ruby
+# config/initializers/preload_stis.rb
+
+# By preloading leaves, the entire hierarchy is loaded upwards following
+# the references to superclasses in the class definitions.
+sti_leaves = %w(
+ app/models/leaf1.rb
+ app/models/leaf2.rb
+ app/models/leaf3.rb
+)
+Rails.autoloaders.main.preload(sti_leaves)
+```
+
+#### Qualified names in class and module definitions
+
+You can now robustly use constant paths in class and module definitions:
+
+```ruby
+# Autoloading in this class' body matches Ruby semantics now.
+class Admin::UsersController < ApplicationController
+ # ...
+end
+```
+
+A gotcha to be aware of is that, depending on the order of execution, the classic autoloader could sometimes be able to autoload `Foo::Wadus` in
+
+```ruby
+class Foo::Bar
+ Wadus
+end
+```
+
+That does not match Ruby semantics because `Foo` is not in the nesting, and won't work at all in `zeitwerk` mode. If you find such corner case you can use the qualified name `Foo::Wadus`:
+
+```ruby
+class Foo::Bar
+ Foo::Wadus
+end
+```
+
+or add `Foo` to the nesting:
+
+```ruby
+module Foo
+ class Bar
+ Wadus
+ end
+end
+```
+
+#### Concerns
+
+You can autoload and eager load from a standard structure like
+
+```
+app/models
+app/models/concerns
+```
+
+In that case, `app/models/concerns` is assumed to be a root directory (because it belongs to the autoload paths), and it is ignored as namespace. So, `app/models/concerns/foo.rb` should define `Foo`, not `Concerns::Foo`.
+
+The `Concerns::` namespace worked with the classic autoloader as a side-effect of the implementation, but it was not really an intended behavior. An application using `Concerns::` needs to rename those classes and modules to be able to run in `zeitwerk` mode.
+
+#### Autoloaded Constants and Explicit Namespaces
+
+If a namespace is defined in a file, as `Hotel` is here:
+
+```
+app/models/hotel.rb # Defines Hotel.
+app/models/hotel/pricing.rb # Defines Hotel::Pricing.
+```
+
+the `Hotel` constant has to be set using the `class` or `module` keywords. For example:
+
+```ruby
+class Hotel
+end
+```
+
+is good.
+
+Alternatives like
+
+```ruby
+Hotel = Class.new
+```
+
+or
+
+```ruby
+Hotel = Struct.new
+```
+
+won't work, child objects like `Hotel::Pricing` won't be found.
+
+This restriction only applies to explicit namespaces. Classes and modules not defining a namespace can be defined using those idioms.
+
+#### Spring and the `test` Environment
+
+Spring reloads the application code if something changes. In the `test` environment you need to enable reloading for that to work:
+
+```ruby
+# config/environments/test.rb
+
+config.cache_classes = false
+```
+
+Otherwise you'll get this error:
+
+```
+reloading is disabled because config.cache_classes is true
+```
+
+#### Bootsnap
+
+Bootsnap should be at least version 1.4.2.
+
+In addition to that, Bootsnap needs to disable the iseq cache due to a bug in the interpreter if running Ruby 2.5. Please make sure to depend on at least Bootsnap 1.4.4 in that case.
+
+#### `config.add_autoload_paths_to_load_path`
+
+The new configuration point
+
+```ruby
+config.add_autoload_paths_to_load_path
+```
+
+is `true` by default for backwards compatibility, but allows you to opt-out from adding the autoload paths to `$LOAD_PATH`.
+
+This makes sense in most applications, since you never should require a file in `app/models`, for example, and Zeitwerk only uses absolute file names internally.
+
+By opting-out you optimize `$LOAD_PATH` lookups (less directories to check), and save Bootsnap work and memory consumption, since it does not need to build an index for these directories.
+
+#### Thread-safety
+
+In classic mode, constant autoloading is not thread-safe, though Rails has locks in place for example to make web requests thread-safe when autoloading is enabled, as it is common in `development` mode.
+
+Constant autoloading is thread-safe in `zeitwerk` mode. For example, you can now autoload in multi-threaded scripts executed by the `runner` command.
+
+#### Globs in config.autoload_paths
+
+Beware of configurations like
+
+```ruby
+config.autoload_paths += Dir["#{config.root}/lib/**/"]
+```
+
+Every element of `config.autoload_paths` should represent the top-level namespace (`Object`) and they cannot be nested in consequence (with the exception of `concerns` directories explained above).
+
+To fix this, just remove the wildcards:
+
+```ruby
+config.autoload_paths << "#{config.root}/lib"
+```
+
+#### How to Use the Classic Autoloader in Rails 6
+
+Applications can load Rails 6 defaults and still use the classic autoloader by setting `config.autoloader` this way:
+
+```ruby
+# config/application.rb
+
+config.load_defaults "6.0"
+config.autoloader = :classic
+```
+
Upgrading from Rails 5.1 to Rails 5.2
-------------------------------------
@@ -1557,7 +1774,7 @@ config.assets.enabled = true
config.assets.version = '1.0'
```
-If your application is using an "/assets" route for a resource you may want change the prefix used for assets to avoid conflicts:
+If your application is using an "/assets" route for a resource you may want to change the prefix used for assets to avoid conflicts:
```ruby
# Defaults to '/assets'
diff --git a/railties/lib/rails/application/bootstrap.rb b/railties/lib/rails/application/bootstrap.rb
index e3c0759f95..50685a4d7a 100644
--- a/railties/lib/rails/application/bootstrap.rb
+++ b/railties/lib/rails/application/bootstrap.rb
@@ -19,14 +19,14 @@ module Rails
initializer :set_eager_load, group: :all do
if config.eager_load.nil?
- warn <<-INFO
-config.eager_load is set to nil. Please update your config/environments/*.rb files accordingly:
+ warn <<~INFO
+ config.eager_load is set to nil. Please update your config/environments/*.rb files accordingly:
- * development - set it to false
- * test - set it to false (unless you use a tool that preloads your test environment)
- * production - set it to true
+ * development - set it to false
+ * test - set it to false (unless you use a tool that preloads your test environment)
+ * production - set it to true
-INFO
+ INFO
config.eager_load = config.cache_classes
end
end
diff --git a/railties/lib/rails/generators/app_base.rb b/railties/lib/rails/generators/app_base.rb
index 7e3560d9d2..8782a85391 100644
--- a/railties/lib/rails/generators/app_base.rb
+++ b/railties/lib/rails/generators/app_base.rb
@@ -307,7 +307,7 @@ module Rails
def assets_gemfile_entry
return [] if options[:skip_sprockets]
- GemfileEntry.version("sass-rails", "~> 5", "Use SCSS for stylesheets")
+ GemfileEntry.version("sass-rails", ">= 5", "Use SCSS for stylesheets")
end
def webpacker_gemfile_entry
diff --git a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt
index 6b216001d2..d3f996188c 100644
--- a/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt
+++ b/railties/lib/rails/generators/erb/scaffold/templates/show.html.erb.tt
@@ -4,7 +4,7 @@
<p>
<strong><%= attribute.human_name %>:</strong>
<% if attribute.attachment? -%>
- <%%= link_to @<%= singular_table_name %>.<%= attribute.column_name %>.filename, @<%= singular_table_name %>.<%= attribute.column_name %> %>
+ <%%= link_to @<%= singular_table_name %>.<%= attribute.column_name %>.filename, @<%= singular_table_name %>.<%= attribute.column_name %> if @<%= singular_table_name %>.<%= attribute.column_name %>.attached? %>
<% elsif attribute.attachments? -%>
<%% @<%= singular_table_name %>.<%= attribute.column_name %>.each do |<%= attribute.singular_name %>| %>
<div><%%= link_to <%= attribute.singular_name %>.filename, <%= attribute.singular_name %> %></div>
diff --git a/railties/lib/rails/generators/named_base.rb b/railties/lib/rails/generators/named_base.rb
index 42e64cd11f..d6732f8ff1 100644
--- a/railties/lib/rails/generators/named_base.rb
+++ b/railties/lib/rails/generators/named_base.rb
@@ -187,7 +187,6 @@ module Rails
def attributes_names # :doc:
@attributes_names ||= attributes.each_with_object([]) do |a, names|
- next if a.attachments?
names << a.column_name
names << "password_confirmation" if a.password_digest?
names << "#{a.name}_type" if a.polymorphic?
diff --git a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
index 8b46eb88ae..e1ca54ec91 100644
--- a/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
+++ b/railties/lib/rails/generators/rails/scaffold_controller/scaffold_controller_generator.rb
@@ -36,9 +36,15 @@ module Rails
private
def permitted_params
- params = attributes_names.map { |name| ":#{name}" }.join(", ")
- params += attributes.select(&:attachments?).map { |a| ", #{a.name}: []" }.join
- params
+ attachments, others = attributes_names.partition { |name| attachments?(name) }
+ params = others.map { |name| ":#{name}" }
+ params += attachments.map { |name| "#{name}: []" }
+ params.join(", ")
+ end
+
+ def attachments?(name)
+ attribute = attributes.find { |attr| attr.name == name }
+ attribute&.attachments?
end
end
end
diff --git a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
index 6df50c3217..26002a0704 100644
--- a/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
+++ b/railties/lib/rails/generators/test_unit/scaffold/scaffold_generator.rb
@@ -49,16 +49,21 @@ module TestUnit # :nodoc:
attributes_names.map do |name|
if %w(password password_confirmation).include?(name) && attributes.any?(&:password_digest?)
["#{name}", "'secret'"]
- else
+ elsif !virtual?(name)
["#{name}", "@#{singular_table_name}.#{name}"]
end
- end.sort.to_h
+ end.compact.sort.to_h
end
def boolean?(name)
attribute = attributes.find { |attr| attr.name == name }
attribute&.type == :boolean
end
+
+ def virtual?(name)
+ attribute = attributes.find { |attr| attr.name == name }
+ attribute&.virtual?
+ end
end
end
end
diff --git a/railties/test/application/middleware/exceptions_test.rb b/railties/test/application/middleware/exceptions_test.rb
index 17df78ed4e..5fae521937 100644
--- a/railties/test/application/middleware/exceptions_test.rb
+++ b/railties/test/application/middleware/exceptions_test.rb
@@ -136,5 +136,21 @@ module ApplicationTests
assert_match(/boooom/, last_response.body)
assert_match(/測試テスト시험/, last_response.body)
end
+
+ test "displays diagnostics message when malformed query parameters are provided" do
+ controller :foo, <<-RUBY
+ class FooController < ActionController::Base
+ def index
+ end
+ end
+ RUBY
+
+ app.config.action_dispatch.show_exceptions = true
+ app.config.consider_all_requests_local = true
+
+ get "/foo?x[y]=1&x[y][][w]=2"
+ assert_equal 400, last_response.status
+ assert_match "Invalid query parameters", last_response.body
+ end
end
end
diff --git a/railties/test/generators/migration_generator_test.rb b/railties/test/generators/migration_generator_test.rb
index e6b614a935..65cde91436 100644
--- a/railties/test/generators/migration_generator_test.rb
+++ b/railties/test/generators/migration_generator_test.rb
@@ -270,6 +270,21 @@ class MigrationGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_create_table_migration_with_timestamps
+ run_generator ["create_books", "title:string", "content:text"]
+ assert_migration "db/migrate/create_books.rb", /t.timestamps/
+ end
+
+ def test_create_table_timestamps_are_skipped
+ run_generator ["create_books", "title:string", "content:text", "--no-timestamps"]
+
+ assert_migration "db/migrate/create_books.rb" do |m|
+ assert_method :change, m do |change|
+ assert_no_match(/t.timestamps/, change)
+ end
+ end
+ end
+
def test_add_uuid_to_create_table_migration
run_generator ["create_books", "--primary_key_type=uuid"]
assert_migration "db/migrate/create_books.rb" do |content|
diff --git a/railties/test/generators/scaffold_controller_generator_test.rb b/railties/test/generators/scaffold_controller_generator_test.rb
index 1348744b0b..8278b72a5a 100644
--- a/railties/test/generators/scaffold_controller_generator_test.rb
+++ b/railties/test/generators/scaffold_controller_generator_test.rb
@@ -89,6 +89,15 @@ class ScaffoldControllerGeneratorTest < Rails::Generators::TestCase
end
end
+ def test_controller_permit_attachments_attributes_only
+ run_generator ["Message", "photos:attachments"]
+
+ assert_file "app/controllers/messages_controller.rb" do |content|
+ assert_match(/def message_params/, content)
+ assert_match(/params\.require\(:message\)\.permit\(photos: \[\]\)/, content)
+ end
+ end
+
def test_helper_are_invoked_with_a_pluralized_name
run_generator
assert_file "app/helpers/users_helper.rb", /module UsersHelper/
diff --git a/railties/test/generators/scaffold_generator_test.rb b/railties/test/generators/scaffold_generator_test.rb
index bfa52a1beb..fa9a42215b 100644
--- a/railties/test/generators/scaffold_generator_test.rb
+++ b/railties/test/generators/scaffold_generator_test.rb
@@ -487,6 +487,36 @@ class ScaffoldGeneratorTest < Rails::Generators::TestCase
assert_match(/^\W{4}<%= form\.file_field :video %>/, content)
assert_match(/^\W{4}<%= form\.file_field :photos, multiple: true %>/, content)
end
+
+ assert_file "app/views/messages/show.html.erb" do |content|
+ assert_match(/^\W{2}<%= link_to @message\.video\.filename, @message\.video if @message\.video\.attached\? %>/, content)
+ assert_match(/^\W{4}<div><%= link_to photo\.filename, photo %>/, content)
+ end
+
+ assert_file "test/system/messages_test.rb" do |content|
+ assert_no_match(/fill_in "Video"/, content)
+ assert_no_match(/fill_in "Photos"/, content)
+ end
+ end
+
+ def test_scaffold_generator_rich_text
+ run_generator ["message", "content:rich_text"]
+
+ assert_file "app/models/message.rb", /rich_text :content/
+
+ assert_file "app/controllers/messages_controller.rb" do |content|
+ assert_instance_method :message_params, content do |m|
+ assert_match(/permit\(:content\)/, m)
+ end
+ end
+
+ assert_file "app/views/messages/_form.html.erb" do |content|
+ assert_match(/^\W{4}<%= form\.rich_text_area :content %>/, content)
+ end
+
+ assert_file "test/system/messages_test.rb" do |content|
+ assert_no_match(/fill_in "Content"/, content)
+ end
end
def test_scaffold_generator_database