diff --git a/actionmailer/lib/action_mailer.rb b/actionmailer/lib/action_mailer.rb
index 426ca919a1c5f..1dbecb2cbc140 100644
--- a/actionmailer/lib/action_mailer.rb
+++ b/actionmailer/lib/action_mailer.rb
@@ -67,4 +67,5 @@ def self.eager_load!
 ActiveSupport.on_load(:action_view) do
   ActionView::Base.default_formats ||= Mime::SET.symbols
   ActionView::Template::Types.delegate_to Mime
+  ActionView::LookupContext::DetailsKey.clear
 end
diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb
index 83e3e0c37c857..7a34911e9dc5d 100644
--- a/actionpack/lib/action_controller/caching.rb
+++ b/actionpack/lib/action_controller/caching.rb
@@ -22,7 +22,6 @@ module ActionController
   #   config.action_controller.cache_store = :mem_cache_store, Memcached::Rails.new('localhost:11211')
   #   config.action_controller.cache_store = MyOwnStore.new('parameter')
   module Caching
-    extend ActiveSupport::Autoload
     extend ActiveSupport::Concern
 
     included do
diff --git a/actionpack/lib/action_controller/metal/implicit_render.rb b/actionpack/lib/action_controller/metal/implicit_render.rb
index 8365ddca570ab..b2485db5f8b45 100644
--- a/actionpack/lib/action_controller/metal/implicit_render.rb
+++ b/actionpack/lib/action_controller/metal/implicit_render.rb
@@ -22,7 +22,7 @@ module ActionController
   # Third, if we DON'T find a template AND the request is a page load in a web
   # browser (technically, a non-XHR GET request for an HTML response) where
   # you reasonably expect to have rendered a template, then we raise
-  # <tt>ActionView::UnknownFormat</tt> with an explanation.
+  # <tt>ActionController::MissingExactTemplate</tt> with an explanation.
   #
   # Finally, if we DON'T find a template AND the request isn't a browser page
   # load, then we implicitly respond with <tt>204 No Content</tt>.
diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb
index ea931cec5195a..0aa1b4ab46b44 100644
--- a/actionpack/lib/action_controller/metal/strong_parameters.rb
+++ b/actionpack/lib/action_controller/metal/strong_parameters.rb
@@ -252,7 +252,7 @@ class Parameters
     # to change these is to specify `always_permitted_parameters` in your
     # config. For instance:
     #
-    #    config.always_permitted_parameters = %w( controller action format )
+    #    config.action_controller.always_permitted_parameters = %w( controller action format )
     cattr_accessor :always_permitted_parameters, default: %w( controller action )
 
     class << self
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 5d64c61b7706b..ba4b62fb97057 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -116,4 +116,5 @@ module Session
 ActiveSupport.on_load(:action_view) do
   ActionView::Base.default_formats ||= Mime::SET.symbols
   ActionView::Template::Types.delegate_to Mime
+  ActionView::LookupContext::DetailsKey.clear
 end
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 4f7b59c79aa68..d6df5a0fd2fee 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -334,7 +334,7 @@ def raw_post
     # variable is already set, wrap it in a StringIO.
     def body
       if raw_post = get_header("RAW_POST_DATA")
-        raw_post = raw_post.dup.force_encoding(Encoding::BINARY)
+        raw_post = (+raw_post).force_encoding(Encoding::BINARY)
         StringIO.new(raw_post)
       else
         body_stream
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 7464309b1e6a5..fc6a79b1448f0 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -81,7 +81,7 @@ def each(&block)
     CONTENT_TYPE = "Content-Type"
     SET_COOKIE   = "Set-Cookie"
     LOCATION     = "Location"
-    NO_CONTENT_CODES = [100, 101, 102, 204, 205, 304]
+    NO_CONTENT_CODES = [100, 101, 102, 103, 204, 205, 304]
 
     cattr_accessor :default_charset, default: "utf-8"
     cattr_accessor :default_headers
@@ -448,8 +448,8 @@ def parsed_content_type_header
     end
 
     def set_content_type(content_type, charset)
-      type = (content_type || "").dup
-      type << "; charset=#{charset.to_s.downcase}" if charset
+      type = content_type || ""
+      type = "#{type}; charset=#{charset.to_s.downcase}" if charset
       set_header CONTENT_TYPE, type
     end
 
diff --git a/actionpack/test/controller/new_base/bare_metal_test.rb b/actionpack/test/controller/new_base/bare_metal_test.rb
index c320d164dee99..f6146687fc993 100644
--- a/actionpack/test/controller/new_base/bare_metal_test.rb
+++ b/actionpack/test/controller/new_base/bare_metal_test.rb
@@ -81,6 +81,11 @@ def processing
       head 102
     end
 
+    def early_hints
+      self.content_type = "text/html"
+      head 103
+    end
+
     def no_content
       self.content_type = "text/html"
       head 204
@@ -121,6 +126,12 @@ class HeadTest < ActiveSupport::TestCase
       assert_nil headers["Content-Length"]
     end
 
+    test "head :early_hints (103) does not return a content-type header" do
+      headers = HeadController.action(:early_hints).call(Rack::MockRequest.env_for("/")).second
+      assert_nil headers["Content-Type"]
+      assert_nil headers["Content-Length"]
+    end
+
     test "head :no_content (204) does not return a content-type header" do
       headers = HeadController.action(:no_content).call(Rack::MockRequest.env_for("/")).second
       assert_nil headers["Content-Type"]
diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb
index 1877afb1b63b7..153c3c55ca2ee 100644
--- a/actionpack/test/dispatch/response_test.rb
+++ b/actionpack/test/dispatch/response_test.rb
@@ -153,7 +153,7 @@ def test_only_set_charset_still_defaults_to_text_html
   end
 
   test "content length" do
-    [100, 101, 102, 204].each do |c|
+    [100, 101, 102, 103, 204].each do |c|
       @response = ActionDispatch::Response.new
       @response.status = c.to_s
       @response.set_header "Content-Length", "0"
@@ -163,7 +163,7 @@ def test_only_set_charset_still_defaults_to_text_html
   end
 
   test "does not contain a message-body" do
-    [100, 101, 102, 204, 304].each do |c|
+    [100, 101, 102, 103, 204, 304].each do |c|
       @response = ActionDispatch::Response.new
       @response.status = c.to_s
       @response.body = "Body must not be included"
diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index 4197c4c021c0f..50b9e5be44faf 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,12 @@
+*   Ensure cache fragment digests include all relevant template dependencies when
+    fragments are contained in a block passed to the render helper. Remove the
+    virtual_path keyword arguments found in CacheHelper as they no longer possess
+    any function following 1581cab.
+
+    Fixes #38984
+
+    *Aaron Lipman*
+
 *   Deprecate `config.action_view.raise_on_missing_translations` in favor of
     `config.i18n.raise_on_missing_translations`.
 
diff --git a/actionview/lib/action_view/base.rb b/actionview/lib/action_view/base.rb
index e9002e9b8b399..3840ac0caf175 100644
--- a/actionview/lib/action_view/base.rb
+++ b/actionview/lib/action_view/base.rb
@@ -269,13 +269,13 @@ def initialize(lookup_context = nil, assigns = {}, controller = nil, formats = N
       _prepare_context
     end
 
-    def _run(method, template, locals, buffer, &block)
-      _old_output_buffer, _old_virtual_path, _old_template = @output_buffer, @virtual_path, @current_template
-      @current_template = template
+    def _run(method, template, locals, buffer, add_to_stack: true, &block)
+      _old_output_buffer, _old_template = @output_buffer, @current_template
+      @current_template = template if add_to_stack
       @output_buffer = buffer
       send(method, locals, buffer, &block)
     ensure
-      @output_buffer, @virtual_path, @current_template = _old_output_buffer, _old_virtual_path, _old_template
+      @output_buffer, @current_template = _old_output_buffer, _old_template
     end
 
     def compiled_method_container
diff --git a/actionview/lib/action_view/context.rb b/actionview/lib/action_view/context.rb
index 2b22c30a3accb..a9320640858a6 100644
--- a/actionview/lib/action_view/context.rb
+++ b/actionview/lib/action_view/context.rb
@@ -18,7 +18,6 @@ module Context
     def _prepare_context
       @view_flow     = OutputFlow.new
       @output_buffer = nil
-      @virtual_path  = nil
     end
 
     # Encapsulates the interaction with the view flow so it
diff --git a/actionview/lib/action_view/helpers/cache_helper.rb b/actionview/lib/action_view/helpers/cache_helper.rb
index 3f396da288a81..3f87bcf9e1c8c 100644
--- a/actionview/lib/action_view/helpers/cache_helper.rb
+++ b/actionview/lib/action_view/helpers/cache_helper.rb
@@ -165,7 +165,7 @@ module CacheHelper
       # expire the cache.
       def cache(name = {}, options = {}, &block)
         if controller.respond_to?(:perform_caching) && controller.perform_caching
-          name_options = options.slice(:skip_digest, :virtual_path)
+          name_options = options.slice(:skip_digest)
           safe_concat(fragment_for(cache_fragment_name(name, **name_options), options, &block))
         else
           yield
@@ -205,14 +205,11 @@ def cache_unless(condition, name = {}, options = {}, &block)
       # fragments can be manually bypassed. This is useful when cache fragments
       # cannot be manually expired unless you know the exact key which is the
       # case when using memcached.
-      #
-      # The digest will be generated using +virtual_path:+ if it is provided.
-      #
-      def cache_fragment_name(name = {}, skip_digest: nil, virtual_path: nil, digest_path: nil)
+      def cache_fragment_name(name = {}, skip_digest: nil, digest_path: nil)
         if skip_digest
           name
         else
-          fragment_name_with_digest(name, virtual_path, digest_path)
+          fragment_name_with_digest(name, digest_path)
         end
       end
 
@@ -227,14 +224,11 @@ def digest_path_from_template(template) # :nodoc:
       end
 
     private
-      def fragment_name_with_digest(name, virtual_path, digest_path)
-        virtual_path ||= @virtual_path
-
-        if virtual_path || digest_path
-          name = controller.url_for(name).split("://").last if name.is_a?(Hash)
+      def fragment_name_with_digest(name, digest_path)
+        name = controller.url_for(name).split("://").last if name.is_a?(Hash)
 
+        if @current_template&.virtual_path || digest_path
           digest_path ||= digest_path_from_template(@current_template)
-
           [ digest_path, name ]
         else
           name
@@ -243,10 +237,10 @@ def fragment_name_with_digest(name, virtual_path, digest_path)
 
       def fragment_for(name = {}, options = nil, &block)
         if content = read_fragment_for(name, options)
-          @view_renderer.cache_hits[@virtual_path] = :hit if defined?(@view_renderer)
+          @view_renderer.cache_hits[@current_template&.virtual_path] = :hit if defined?(@view_renderer)
           content
         else
-          @view_renderer.cache_hits[@virtual_path] = :miss if defined?(@view_renderer)
+          @view_renderer.cache_hits[@current_template&.virtual_path] = :miss if defined?(@view_renderer)
           write_fragment_for(name, options, &block)
         end
       end
diff --git a/actionview/lib/action_view/helpers/form_helper.rb b/actionview/lib/action_view/helpers/form_helper.rb
index acca4df74cbaa..c794d2b67f8e6 100644
--- a/actionview/lib/action_view/helpers/form_helper.rb
+++ b/actionview/lib/action_view/helpers/form_helper.rb
@@ -1793,7 +1793,7 @@ def initialize(object_name, object, template, options)
       # Wraps ActionView::Helpers::FormHelper#time_field for form builders:
       #
       #   <%= form_with model: @user do |f| %>
-      #     <%= f.time_field :borned_at %>
+      #     <%= f.time_field :born_at %>
       #   <% end %>
       #
       # Please refer to the documentation of the base helper for details.
diff --git a/actionview/lib/action_view/helpers/translation_helper.rb b/actionview/lib/action_view/helpers/translation_helper.rb
index 49e7e6d3be1e7..028ffc2f8747b 100644
--- a/actionview/lib/action_view/helpers/translation_helper.rb
+++ b/actionview/lib/action_view/helpers/translation_helper.rb
@@ -124,10 +124,10 @@ def localize(object, **options)
         def scope_key_by_partial(key)
           stringified_key = key.to_s
           if stringified_key.start_with?(".")
-            if @virtual_path
+            if @current_template&.virtual_path
               @_scope_key_by_partial_cache ||= {}
-              @_scope_key_by_partial_cache[@virtual_path] ||= @virtual_path.gsub(%r{/_?}, ".")
-              "#{@_scope_key_by_partial_cache[@virtual_path]}#{stringified_key}"
+              @_scope_key_by_partial_cache[@current_template.virtual_path] ||= @current_template.virtual_path.gsub(%r{/_?}, ".")
+              "#{@_scope_key_by_partial_cache[@current_template.virtual_path]}#{stringified_key}"
             else
               raise "Cannot use t(#{key.inspect}) shortcut because path is not available"
             end
diff --git a/actionview/lib/action_view/renderer/partial_renderer.rb b/actionview/lib/action_view/renderer/partial_renderer.rb
index 1503ba734d442..39d24bd4f878c 100644
--- a/actionview/lib/action_view/renderer/partial_renderer.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer.rb
@@ -282,7 +282,7 @@ def render_partial_template(view, locals, template, layout, block)
           identifier: template.identifier,
           layout: layout && layout.virtual_path
         ) do |payload|
-          content = template.render(view, locals) do |*name|
+          content = template.render(view, locals, add_to_stack: !block) do |*name|
             view._layout_for(*name, &block)
           end
 
diff --git a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
index 788d171a90391..ea02ffcae9de1 100644
--- a/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
+++ b/actionview/lib/action_view/renderer/partial_renderer/collection_caching.rb
@@ -68,7 +68,7 @@ def collection_by_cache_keys(view, template, collection)
       end
 
       def expanded_cache_key(key, view, template, digest_path)
-        key = view.combined_fragment_cache_key(view.cache_fragment_name(key, virtual_path: template.virtual_path, digest_path: digest_path))
+        key = view.combined_fragment_cache_key(view.cache_fragment_name(key, digest_path: digest_path))
         key.frozen? ? key.dup : key # #read_multi & #write may require mutability, Dalli 2.6.0.
       end
 
diff --git a/actionview/lib/action_view/template.rb b/actionview/lib/action_view/template.rb
index cf0ef749593bb..d2d24299fe14f 100644
--- a/actionview/lib/action_view/template.rb
+++ b/actionview/lib/action_view/template.rb
@@ -176,10 +176,10 @@ def supports_streaming?
     # This method is instrumented as "!render_template.action_view". Notice that
     # we use a bang in this instrumentation because you don't want to
     # consume this in production. This is only slow if it's being listened to.
-    def render(view, locals, buffer = ActionView::OutputBuffer.new, &block)
+    def render(view, locals, buffer = ActionView::OutputBuffer.new, add_to_stack: true, &block)
       instrument_render_template do
         compile!(view)
-        view._run(method_name, self, locals, buffer, &block)
+        view._run(method_name, self, locals, buffer, add_to_stack: add_to_stack, &block)
       end
     rescue => e
       handle_render_error(view, e)
diff --git a/actionview/lib/action_view/template/resolver.rb b/actionview/lib/action_view/template/resolver.rb
index 0f79f6febe995..b04a7cab6670e 100644
--- a/actionview/lib/action_view/template/resolver.rb
+++ b/actionview/lib/action_view/template/resolver.rb
@@ -35,6 +35,44 @@ def to_str
       alias :to_s :to_str
     end
 
+    class PathParser # :nodoc:
+      def initialize
+        @regex = build_path_regex
+      end
+
+      def build_path_regex
+        handlers = Template::Handlers.extensions.map { |x| Regexp.escape(x) }.join("|")
+        formats = Template::Types.symbols.map { |x| Regexp.escape(x) }.join("|")
+        locales = "[a-z]{2}(?:-[A-Z]{2})?"
+        variants = "[^.]*"
+
+        %r{
+          \A
+          (?:(?<prefix>.*)/)?
+          (?<partial>_)?
+          (?<action>.*?)
+          (?:\.(?<locale>#{locales}))??
+          (?:\.(?<format>#{formats}))??
+          (?:\+(?<variant>#{variants}))??
+          (?:\.(?<handler>#{handlers}))?
+          \z
+        }x
+      end
+
+      def parse(path)
+        match = @regex.match(path)
+        {
+          prefix: match[:prefix] || "",
+          action: match[:action],
+          partial: !!match[:partial],
+          locale: match[:locale]&.to_sym,
+          handler: match[:handler]&.to_sym,
+          format: match[:format]&.to_sym,
+          variant: match[:variant]
+        }
+      end
+    end
+
     # Threadsafe template cache
     class Cache #:nodoc:
       class SmallCache < Concurrent::Map
@@ -172,11 +210,13 @@ def initialize(pattern = nil)
         @pattern = DEFAULT_PATTERN
       end
       @unbound_templates = Concurrent::Map.new
+      @path_parser = PathParser.new
       super()
     end
 
     def clear_cache
       @unbound_templates.clear
+      @path_parser = PathParser.new
       super()
     end
 
@@ -278,18 +318,11 @@ def escape_entry(entry)
       # from the path, or the handler, we should return the array of formats given
       # to the resolver.
       def extract_handler_and_format_and_variant(path)
-        pieces = File.basename(path).split(".")
-        pieces.shift
+        details = @path_parser.parse(path)
 
-        extension = pieces.pop
-
-        handler = Template.handler_for_extension(extension)
-        format, variant = pieces.last.split(EXTENSIONS[:variants], 2) if pieces.last
-        format = if format
-          Template::Types[format]&.ref
-        elsif handler.respond_to?(:default_format) # default_format can return nil
-          handler.default_format
-        end
+        handler = Template.handler_for_extension(details[:handler])
+        format = details[:format] || handler.try(:default_format)
+        variant = details[:variant]
 
         # Template::Types[format] and handler.default_format can return nil
         [handler, format, variant]
diff --git a/actionview/test/abstract_unit.rb b/actionview/test/abstract_unit.rb
index 36c4bc85670c6..ddd34da7bda52 100644
--- a/actionview/test/abstract_unit.rb
+++ b/actionview/test/abstract_unit.rb
@@ -59,8 +59,6 @@ def view
   end
 
   def render_erb(string)
-    @virtual_path = nil
-
     template = ActionView::Template.new(
       string.strip,
       "test template",
diff --git a/actionview/test/actionpack/controller/layout_test.rb b/actionview/test/actionpack/controller/layout_test.rb
index f946e4360d8e9..0b2627c29007c 100644
--- a/actionview/test/actionpack/controller/layout_test.rb
+++ b/actionview/test/actionpack/controller/layout_test.rb
@@ -18,9 +18,13 @@ def self._implied_layout_name; to_s.underscore.gsub(/_controller$/, "") ; end
 module TemplateHandlerHelper
   def with_template_handler(*extensions, handler)
     ActionView::Template.register_template_handler(*extensions, handler)
+    ActionController::Base.view_paths.paths.each(&:clear_cache)
+    ActionView::LookupContext::DetailsKey.clear
     yield
   ensure
     ActionView::Template.unregister_template_handler(*extensions)
+    ActionController::Base.view_paths.paths.each(&:clear_cache)
+    ActionView::LookupContext::DetailsKey.clear
   end
 end
 
diff --git a/actionview/test/activerecord/relation_cache_test.rb b/actionview/test/activerecord/relation_cache_test.rb
index 26f3bfbcbc84f..2a5cc67a66735 100644
--- a/actionview/test/activerecord/relation_cache_test.rb
+++ b/actionview/test/activerecord/relation_cache_test.rb
@@ -10,7 +10,6 @@ def setup
     view_paths     = ActionController::Base.view_paths
     lookup_context = ActionView::LookupContext.new(view_paths, {}, ["test"])
     @view_renderer = ActionView::Renderer.new(lookup_context)
-    @virtual_path  = "path"
     @current_template = lookup_context.find "test/hello_world"
 
     controller.cache_store = ActiveSupport::Cache::MemoryStore.new
diff --git a/actionview/test/fixtures/test/cache_fragment_inside_render_layout_block_1.html.erb b/actionview/test/fixtures/test/cache_fragment_inside_render_layout_block_1.html.erb
new file mode 100644
index 0000000000000..60ec279ff480e
--- /dev/null
+++ b/actionview/test/fixtures/test/cache_fragment_inside_render_layout_block_1.html.erb
@@ -0,0 +1 @@
+<%= render layout: "layouts/yield_only" do %><%= cache "foo" do %>CAT<% end %><% end %>
\ No newline at end of file
diff --git a/actionview/test/fixtures/test/cache_fragment_inside_render_layout_block_2.html.erb b/actionview/test/fixtures/test/cache_fragment_inside_render_layout_block_2.html.erb
new file mode 100644
index 0000000000000..49df88f56cc48
--- /dev/null
+++ b/actionview/test/fixtures/test/cache_fragment_inside_render_layout_block_2.html.erb
@@ -0,0 +1 @@
+<%= render layout: "layouts/yield_only" do %><%= cache "foo" do %>DOG<% end %><% end %>
\ No newline at end of file
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index e315d6e4e67e1..56f2674eb0cb0 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -22,6 +22,7 @@ def combined_fragment_cache_key(key)
 
     controller = TestController.new
     controller.perform_caching = true
+    controller.cache_store = :memory_store
     @view.controller = controller
 
     @controller_view = controller.view_context_class.with_empty_template_cache.new(
@@ -705,6 +706,13 @@ def setup
     assert_equal ActionView::OptimizedFileSystemResolver, view_paths.first.class
     setup_view(view_paths)
   end
+
+  def test_cache_fragments_inside_render_layout_call_with_block
+    cat = @view.render(template: "test/cache_fragment_inside_render_layout_block_1")
+    dog = @view.render(template: "test/cache_fragment_inside_render_layout_block_2")
+
+    assert_not_equal cat, dog
+  end
 end
 
 class LazyViewRenderTest < ActiveSupport::TestCase
diff --git a/actionview/test/template/resolver_shared_tests.rb b/actionview/test/template/resolver_shared_tests.rb
index 332eb30a58f0a..f513cca0bb78b 100644
--- a/actionview/test/template/resolver_shared_tests.rb
+++ b/actionview/test/template/resolver_shared_tests.rb
@@ -148,22 +148,83 @@ def test_templates_with_optional_locale_shares_common_object
 
   def test_templates_sort_by_formats_json_first
     with_file "test/hello_world.html.erb", "Hello HTML!"
-    with_file "test/hello_world.json.jbuilder", "Hello JSON!"
+    with_file "test/hello_world.json.builder", "Hello JSON!"
 
-    templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:json, :html], variants: :any, handlers: [:erb, :jbuilder])
+    templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:json, :html], variants: :any, handlers: [:erb, :builder])
 
     assert_equal 2, templates.size
     assert_equal "Hello JSON!", templates[0].source
+    assert_equal :json, templates[0].format
+    assert_equal "Hello HTML!", templates[1].source
+    assert_equal :html, templates[1].format
   end
 
   def test_templates_sort_by_formats_html_first
     with_file "test/hello_world.html.erb", "Hello HTML!"
-    with_file "test/hello_world.json.jbuilder", "Hello JSON!"
+    with_file "test/hello_world.json.builder", "Hello JSON!"
 
-    templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :jbuilder])
+    templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :builder])
 
     assert_equal 2, templates.size
     assert_equal "Hello HTML!", templates[0].source
+    assert_equal :html, templates[0].format
+    assert_equal "Hello JSON!", templates[1].source
+    assert_equal :json, templates[1].format
+  end
+
+  def test_templates_with_variant
+    with_file "test/hello_world.html+mobile.erb", "Hello HTML!"
+
+    templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :builder])
+
+    assert_equal 1, templates.size
+    assert_equal "Hello HTML!", templates[0].source
+    assert_kind_of ActionView::Template::Handlers::ERB, templates[0].handler
+    assert_equal :html, templates[0].format
+    assert_equal "mobile", templates[0].variant
+  end
+
+  def test_finds_variants_in_order
+    with_file "test/hello_world.html+tricorder.erb", "Hello Spock!"
+    with_file "test/hello_world.html+lcars.erb", "Hello Geordi!"
+
+    tricorder = context.find("hello_world", "test", false, [], { variants: [:tricorder] })
+    lcars = context.find("hello_world", "test", false, [], { variants: [:lcars] })
+
+    assert_equal "Hello Spock!", tricorder.source
+    assert_equal "tricorder", tricorder.variant
+    assert_equal "Hello Geordi!", lcars.source
+    assert_equal "lcars", lcars.variant
+
+    templates = context.find_all("hello_world", "test", false, [], { variants: [:tricorder, :lcars] })
+    assert_equal [tricorder, lcars], templates
+
+    templates = context.find_all("hello_world", "test", false, [], { variants: [:lcars, :tricorder] })
+    assert_equal [lcars, tricorder], templates
+  end
+
+  def test_templates_no_format_with_variant
+    with_file "test/hello_world+mobile.erb", "Hello HTML!"
+
+    templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :builder])
+
+    assert_equal 1, templates.size
+    assert_equal "Hello HTML!", templates[0].source
+    assert_kind_of ActionView::Template::Handlers::ERB, templates[0].handler
+    assert_nil templates[0].format
+    assert_equal "mobile", templates[0].variant
+  end
+
+  def test_templates_no_format_or_handler_with_variant
+    with_file "test/hello_world+mobile", "Hello HTML!"
+
+    templates = resolver.find_all("hello_world", "test", false, locale: [], formats: [:html, :json], variants: :any, handlers: [:erb, :builder])
+
+    assert_equal 1, templates.size
+    assert_equal "Hello HTML!", templates[0].source
+    assert_kind_of ActionView::Template::Handlers::Raw, templates[0].handler
+    assert_nil templates[0].format
+    assert_equal "mobile", templates[0].variant
   end
 
   def test_virtual_path_is_preserved_with_dot
diff --git a/actionview/test/template/template_test.rb b/actionview/test/template/template_test.rb
index d8dcd33d691a1..e4ec4ee024ef2 100644
--- a/actionview/test/template/template_test.rb
+++ b/actionview/test/template/template_test.rb
@@ -22,7 +22,6 @@ class Context < ActionView::Base
     def initialize(*)
       super
       @output_buffer = "original"
-      @virtual_path = nil
     end
 
     def hello
@@ -35,7 +34,7 @@ def apostrophe
 
     def partial
       ActionView::Template.new(
-        "<%= @virtual_path %>",
+        "<%= @current_template.virtual_path %>",
         "partial",
         ERBHandler,
         virtual_path: "partial",
@@ -115,9 +114,9 @@ def test_restores_buffer
   end
 
   def test_virtual_path
-    @template = new_template("<%= @virtual_path %>" \
+    @template = new_template("<%= @current_template.virtual_path %>" \
                              "<%= partial.render(self, {}) %>" \
-                             "<%= @virtual_path %>")
+                             "<%= @current_template.virtual_path %>")
     assert_equal "hellopartialhello", render
   end
 
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
index 5fa9abad45558..5ea9f908efefa 100644
--- a/activerecord/lib/active_record/associations/preloader/association.rb
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
@@ -133,7 +133,7 @@ def scope
 
           def reflection_scope
             @reflection_scope ||= begin
-              reflection.join_scopes(klass.arel_table, klass.predicate_builder).inject(klass.unscoped, &:merge!)
+              reflection.join_scopes(klass.arel_table, klass.predicate_builder, klass).inject(&:merge!) || klass.unscoped
             end
           end
 
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
index de0b50504bc11..1192cbc58b37f 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb
@@ -612,6 +612,12 @@ def add_column(table_name, column_name, type, **options)
         execute schema_creation.accept at
       end
 
+      def add_columns(table_name, *column_names, type:, **options) # :nodoc:
+        column_names.each do |column_name|
+          add_column(table_name, column_name, type, **options)
+        end
+      end
+
       # Removes the given columns from the table definition.
       #
       #   remove_columns(:suppliers, :qualification, :experience)
@@ -619,9 +625,11 @@ def add_column(table_name, column_name, type, **options)
       # +type+ and other column options can be passed to make migration reversible.
       #
       #    remove_columns(:suppliers, :qualification, :experience, type: :string, null: false)
-      def remove_columns(table_name, *column_names, **options)
-        raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)") if column_names.empty?
-        type = options.delete(:type)
+      def remove_columns(table_name, *column_names, type: nil, **options)
+        if column_names.empty?
+          raise ArgumentError.new("You must specify at least one column name. Example: remove_columns(:people, :first_name)")
+        end
+
         column_names.each do |column_name|
           remove_column(table_name, column_name, type, **options)
         end
diff --git a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
index 3435e2884ec80..697c6e5de78fa 100644
--- a/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
+++ b/activerecord/lib/active_record/connection_adapters/abstract_mysql_adapter.rb
@@ -565,17 +565,8 @@ def initialize_type_map(m = type_map)
           m.alias_type %r(year)i,          "integer"
           m.alias_type %r(bit)i,           "binary"
 
-          m.register_type(%r(enum)i) do |sql_type|
-            limit = sql_type[/^enum\s*\((.+)\)/i, 1]
-              .split(",").map { |enum| enum.strip.length - 2 }.max
-            MysqlString.new(limit: limit)
-          end
-
-          m.register_type(%r(^set)i) do |sql_type|
-            limit = sql_type[/^set\s*\((.+)\)/i, 1]
-              .split(",").map { |set| set.strip.length - 1 }.sum - 1
-            MysqlString.new(limit: limit)
-          end
+          m.register_type %r(^enum)i, MysqlString.new
+          m.register_type %r(^set)i,  MysqlString.new
         end
 
         def register_integer_type(mapping, key, **options)
diff --git a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
index bcd300f3dbd60..f66bc84a4993c 100644
--- a/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
+++ b/activerecord/lib/active_record/connection_adapters/mysql/schema_dumper.rb
@@ -49,7 +49,7 @@ def schema_type(column)
           end
 
           def schema_limit(column)
-            super unless /\A(?:enum|set|(?:tiny|medium|long)?(?:text|blob))\b/.match?(column.sql_type)
+            super unless /\A(?:tiny|medium|long)?(?:text|blob)\b/.match?(column.sql_type)
           end
 
           def schema_precision(column)
diff --git a/activerecord/lib/active_record/migration/command_recorder.rb b/activerecord/lib/active_record/migration/command_recorder.rb
index b4dfc5f04c1b7..f71f77883fe19 100644
--- a/activerecord/lib/active_record/migration/command_recorder.rb
+++ b/activerecord/lib/active_record/migration/command_recorder.rb
@@ -74,12 +74,7 @@ def revert
       #   recorder.record(:method_name, [:arg1, :arg2])
       def record(*command, &block)
         if @reverting
-          inverse_command = inverse_of(*command, &block)
-          if inverse_command[0].is_a?(Array)
-            @commands = @commands.concat(inverse_command)
-          else
-            @commands << inverse_command
-          end
+          @commands << inverse_of(*command, &block)
         else
           @commands << (command << block)
         end
@@ -186,11 +181,7 @@ def invert_remove_columns(args)
             raise ActiveRecord::IrreversibleMigration, "remove_columns is only reversible if given a type."
           end
 
-          column_options = args.extract_options!
-          type = column_options.delete(:type)
-          args[1..-1].map do |arg|
-            [:add_column, [args[0], arg, type, column_options]]
-          end
+          [:add_columns, args]
         end
 
         def invert_rename_index(args)
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
index 934e231eee55a..60444a86d52dd 100644
--- a/activerecord/lib/active_record/reflection.rb
+++ b/activerecord/lib/active_record/reflection.rb
@@ -201,9 +201,9 @@ def join_scope(table, foreign_table, foreign_klass)
         klass_scope
       end
 
-      def join_scopes(table, predicate_builder) # :nodoc:
+      def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
         if scope
-          [scope_for(build_scope(table, predicate_builder))]
+          [scope_for(build_scope(table, predicate_builder, klass))]
         else
           []
         end
@@ -292,7 +292,7 @@ def get_join_keys(association_klass)
         JoinKeys.new(join_primary_key(association_klass), join_foreign_key)
       end
 
-      def build_scope(table, predicate_builder = predicate_builder(table))
+      def build_scope(table, predicate_builder = predicate_builder(table), klass = self.klass)
         Relation.create(
           klass,
           table: table,
@@ -853,8 +853,8 @@ def scopes
         source_reflection.scopes + super
       end
 
-      def join_scopes(table, predicate_builder) # :nodoc:
-        source_reflection.join_scopes(table, predicate_builder) + super
+      def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
+        source_reflection.join_scopes(table, predicate_builder, klass) + super
       end
 
       def has_scope?
@@ -1017,9 +1017,9 @@ def initialize(reflection, previous_reflection)
         @previous_reflection = previous_reflection
       end
 
-      def join_scopes(table, predicate_builder) # :nodoc:
+      def join_scopes(table, predicate_builder, klass = self.klass) # :nodoc:
         scopes = @previous_reflection.join_scopes(table, predicate_builder) + super
-        scopes << build_scope(table, predicate_builder).instance_exec(nil, &source_type_scope)
+        scopes << build_scope(table, predicate_builder, klass).instance_exec(nil, &source_type_scope)
       end
 
       def constraints
diff --git a/activerecord/lib/active_record/relation/query_composer.rb b/activerecord/lib/active_record/relation/query_composer.rb
new file mode 100644
index 0000000000000..15d7dcd3fa9ea
--- /dev/null
+++ b/activerecord/lib/active_record/relation/query_composer.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module ActiveRecord
+  class QueryComposer
+    delegate :[], to: :arel_table
+
+    def initialize(model)
+      @model = model
+      @arel_table = model.arel_table
+      @reflections = model._reflections
+      define_attribute_accessors
+    end
+
+    def method_missing(name, *_args)
+      if reflections.key?(name.to_s)
+        self.class.new(reflections[name.to_s].klass)
+      else
+        super
+      end
+    end
+
+    private
+      attr_reader :model, :arel_table, :reflections
+
+      def define_attribute_accessors
+        model.attribute_names.each do |attr|
+          define_singleton_method attr do
+            arel_table[attr]
+          end
+        end
+      end
+  end
+end
diff --git a/activerecord/lib/active_record/relation/query_methods.rb b/activerecord/lib/active_record/relation/query_methods.rb
index 4ca72b0cf9ccf..12bcaa10a9911 100644
--- a/activerecord/lib/active_record/relation/query_methods.rb
+++ b/activerecord/lib/active_record/relation/query_methods.rb
@@ -2,6 +2,7 @@
 
 require "active_record/relation/from_clause"
 require "active_record/relation/query_attribute"
+require "active_record/relation/query_composer"
 require "active_record/relation/where_clause"
 require "active_record/relation/where_clause_factory"
 require "active_model/forbidden_attributes_protection"
@@ -662,14 +663,24 @@ def left_outer_joins!(*args) # :nodoc:
     #
     # If the condition is any blank-ish object, then #where is a no-op and returns
     # the current relation.
-    def where(opts = :chain, *rest)
-      if :chain == opts
+    def where(opts = :chain, *rest, &block)
+      new_scope = if :chain == opts && block_given?
+        self
+      elsif :chain == opts
         WhereChain.new(spawn)
       elsif opts.blank?
         self
       else
         spawn.where!(opts, *rest)
       end
+
+      if block_given?
+        composer = QueryComposer.new(model)
+        constraints = composer.instance_exec(composer, &block)
+        new_scope.where!(constraints)
+      else
+        new_scope
+      end
     end
 
     def where!(opts, *rest) # :nodoc:
@@ -1399,7 +1410,7 @@ def order_column(field)
       def resolve_arel_attributes(attrs)
         attrs.flat_map do |attr|
           case attr
-          when Arel::Attributes::Attribute
+          when Arel::Predications
             attr
           when Hash
             attr.flat_map do |table, columns|
diff --git a/activerecord/lib/active_record/relation/where_clause.rb b/activerecord/lib/active_record/relation/where_clause.rb
index 3a706a02797ec..dd92beb136290 100644
--- a/activerecord/lib/active_record/relation/where_clause.rb
+++ b/activerecord/lib/active_record/relation/where_clause.rb
@@ -1,5 +1,7 @@
 # frozen_string_literal: true
 
+require "active_support/core_ext/array/extract"
+
 module ActiveRecord
   class Relation
     class WhereClause # :nodoc:
@@ -101,7 +103,9 @@ def contradiction?
       def extract_attributes
         attrs = []
         predicates.each do |node|
-          Arel.fetch_attribute(node) { |attr| attrs << attr }
+          Arel.fetch_attribute(node) { |attr| attrs << attr } || begin
+            attrs << node.left if node.equality? && node.left.is_a?(Arel::Predications)
+          end
         end
         attrs
       end
@@ -153,15 +157,14 @@ def invert_predicate(node)
         end
 
         def except_predicates(columns)
+          attrs = columns.extract! { |node| node.is_a?(Arel::Attribute) }
+          non_attrs = columns.extract! { |node| node.is_a?(Arel::Predications) }
+
           predicates.reject do |node|
-            Arel.fetch_attribute(node) do |attr|
-              columns.any? do |column|
-                if column.is_a?(Arel::Attributes::Attribute)
-                  attr == column
-                else
-                  attr.name.to_s == column.to_s
-                end
-              end
+            if !non_attrs.empty? && node.equality? && node.left.is_a?(Arel::Predications)
+              non_attrs.include?(node.left)
+            end || Arel.fetch_attribute(node) do |attr|
+              attrs.include?(attr) || columns.include?(attr.name.to_s)
             end
           end
         end
diff --git a/activerecord/lib/active_record/signed_id.rb b/activerecord/lib/active_record/signed_id.rb
index abd7ddf37e9ab..e9f1eba3e8f6e 100644
--- a/activerecord/lib/active_record/signed_id.rb
+++ b/activerecord/lib/active_record/signed_id.rb
@@ -40,7 +40,7 @@ module ClassMethods
       #   travel_back
       #   User.find_signed signed_id, purpose: :password_reset # => User.first
       def find_signed(signed_id, purpose: nil)
-        raise UnknownPrimaryKey.new(@klass) if primary_key.nil?
+        raise UnknownPrimaryKey.new(self) if primary_key.nil?
 
         if id = signed_id_verifier.verified(signed_id, purpose: combine_signed_id_purposes(purpose))
           find_by primary_key => id
diff --git a/activerecord/test/cases/adapters/mysql2/enum_test.rb b/activerecord/test/cases/adapters/mysql2/enum_test.rb
index 1168b3677ebd2..50ee9191636b5 100644
--- a/activerecord/test/cases/adapters/mysql2/enum_test.rb
+++ b/activerecord/test/cases/adapters/mysql2/enum_test.rb
@@ -15,11 +15,6 @@ def setup
     end
   end
 
-  def test_enum_limit
-    column = EnumTest.columns_hash["enum_column"]
-    assert_equal 8, column.limit
-  end
-
   def test_should_not_be_unsigned
     column = EnumTest.columns_hash["enum_column"]
     assert_not_predicate column, :unsigned?
diff --git a/activerecord/test/cases/associations/belongs_to_associations_test.rb b/activerecord/test/cases/associations/belongs_to_associations_test.rb
index 9436abdb58b1f..4cdcba3bb68f3 100644
--- a/activerecord/test/cases/associations/belongs_to_associations_test.rb
+++ b/activerecord/test/cases/associations/belongs_to_associations_test.rb
@@ -465,7 +465,14 @@ def test_polymorphic_association_class
   def test_with_polymorphic_and_condition
     sponsor = Sponsor.create
     member = Member.create name: "Bert"
+
     sponsor.sponsorable = member
+    sponsor.save!
+
+    assert_equal member, sponsor.sponsorable
+    assert_nil sponsor.sponsorable_with_conditions
+
+    sponsor = Sponsor.preload(:sponsorable, :sponsorable_with_conditions).last
 
     assert_equal member, sponsor.sponsorable
     assert_nil sponsor.sponsorable_with_conditions
diff --git a/activerecord/test/cases/calculations_test.rb b/activerecord/test/cases/calculations_test.rb
index 5fae3aaefa674..34fab5a159c24 100644
--- a/activerecord/test/cases/calculations_test.rb
+++ b/activerecord/test/cases/calculations_test.rb
@@ -724,8 +724,7 @@ def test_pluck
   end
 
   def test_pluck_with_empty_in
-    Topic.send(:load_schema)
-    assert_no_queries do
+    assert_queries(0) do
       assert_equal [], Topic.where(id: []).pluck(:id)
     end
   end
diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb
index 769ee22985bae..82ba22714f96e 100644
--- a/activerecord/test/cases/finder_test.rb
+++ b/activerecord/test/cases/finder_test.rb
@@ -397,22 +397,19 @@ def test_find_by_ids_with_limit_and_offset
   end
 
   def test_find_with_large_number
-    Topic.send(:load_schema)
-    assert_no_queries do
+    assert_queries(0) do
       assert_raises(ActiveRecord::RecordNotFound) { Topic.find("9999999999999999999999999999999") }
     end
   end
 
   def test_find_by_with_large_number
-    Topic.send(:load_schema)
-    assert_no_queries do
+    assert_queries(0) do
       assert_nil Topic.find_by(id: "9999999999999999999999999999999")
     end
   end
 
   def test_find_by_id_with_large_number
-    Topic.send(:load_schema)
-    assert_no_queries do
+    assert_queries(0) do
       assert_nil Topic.find_by_id("9999999999999999999999999999999")
     end
   end
diff --git a/activerecord/test/cases/null_relation_test.rb b/activerecord/test/cases/null_relation_test.rb
index ee96ea1af6579..407aa232383c8 100644
--- a/activerecord/test/cases/null_relation_test.rb
+++ b/activerecord/test/cases/null_relation_test.rb
@@ -17,8 +17,7 @@ def test_none
   end
 
   def test_none_chainable
-    Developer.send(:load_schema)
-    assert_no_queries do
+    assert_queries(0) do
       assert_equal [], Developer.none.where(name: "David")
     end
   end
diff --git a/activerecord/test/cases/relation/merging_test.rb b/activerecord/test/cases/relation/merging_test.rb
index 803d0e316e83e..fc7b4de0c00e1 100644
--- a/activerecord/test/cases/relation/merging_test.rb
+++ b/activerecord/test/cases/relation/merging_test.rb
@@ -130,21 +130,29 @@ def test_relation_to_sql
   end
 
   def test_relation_merging_with_arel_equalities_keeps_last_equality
-    devs = Developer.where(Developer.arel_table[:salary].eq(80000)).merge(
-      Developer.where(Developer.arel_table[:salary].eq(9000))
-    )
+    salary_attr = Developer.arel_table[:salary]
+
+    devs = Developer.where(salary_attr.eq(80000)).merge(Developer.where(salary_attr.eq(9000)))
+    assert_equal [developers(:poor_jamis)], devs.to_a
+
+    devs = Developer.where(salary_attr.eq(80000)).merge(Developer.where(salary_attr.eq(9000)), rewhere: true)
+    assert_equal [developers(:poor_jamis)], devs.to_a
+
+    devs = Developer.where(salary_attr.eq(80000)).rewhere(salary_attr.eq(9000))
     assert_equal [developers(:poor_jamis)], devs.to_a
   end
 
   def test_relation_merging_with_arel_equalities_keeps_last_equality_with_non_attribute_left_hand
     salary_attr = Developer.arel_table[:salary]
-    devs = Developer.where(
-      Arel::Nodes::NamedFunction.new("abs", [salary_attr]).eq(80000)
-    ).merge(
-      Developer.where(
-        Arel::Nodes::NamedFunction.new("abs", [salary_attr]).eq(9000)
-      )
-    )
+    abs_salary = Arel::Nodes::NamedFunction.new("abs", [salary_attr])
+
+    devs = Developer.where(abs_salary.eq(80000)).merge(Developer.where(abs_salary.eq(9000)))
+    assert_equal [developers(:poor_jamis)], devs.to_a
+
+    devs = Developer.where(abs_salary.eq(80000)).merge(Developer.where(abs_salary.eq(9000)), rewhere: true)
+    assert_equal [developers(:poor_jamis)], devs.to_a
+
+    devs = Developer.where(abs_salary.eq(80000)).rewhere(abs_salary.eq(9000))
     assert_equal [developers(:poor_jamis)], devs.to_a
   end
 
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 2b5f6d942c834..d09eb1b0cb8fa 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -419,15 +419,13 @@ def test_skip_preloading_after_arel_has_been_generated
     end
 
     test "no queries on empty IN" do
-      Post.send(:load_schema)
-      assert_no_queries do
+      assert_queries(0) do
         Post.where(id: []).load
       end
     end
 
     test "can unscope empty IN" do
-      Post.send(:load_schema)
-      assert_queries 1 do
+      assert_queries(1) do
         Post.where(id: []).unscope(where: :id).load
       end
     end
diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb
index 078e6a188ae09..3dccc4070e557 100644
--- a/activerecord/test/cases/relations_test.rb
+++ b/activerecord/test/cases/relations_test.rb
@@ -63,6 +63,26 @@ def test_multivalue_where
     assert_equal 1, posts.to_a.size
   end
 
+  def test_block_where
+    posts = Post.where { |post| post[:author_id].eq(1).and(post[:id].eq(1)) }
+    assert_equal 1, posts.to_a.size
+  end
+
+  def test_block_where_can_chain
+    posts = Post.where { |post| post[:author_id].eq(1) }.where { |post| post[:id].eq(1) }
+    assert_equal 1, posts.to_a.size
+  end
+
+  def test_block_method_where
+    posts = Post.where { |post| post.author_id.eq(1).and(post.id.eq(1)) }
+    assert_equal 1, posts.to_a.size
+  end
+
+  def test_block_instance_method_where
+    posts = Post.where { author_id.eq(1).and(id.eq(1)) }
+    assert_equal 1, posts.to_a.size
+  end
+
   def test_scoped
     topics = Topic.all
     assert_kind_of ActiveRecord::Relation, topics
@@ -504,6 +524,24 @@ def test_finding_with_hash_conditions_on_joined_table
     assert_equal companies(:rails_core), firms.first
   end
 
+  def tests_finding_with_block_on_joined_table
+    firms = DependentFirm.joins(:account).where { |firm| firm[:name].eq("RailsCore").and(firm.accounts[:credit_limit].eq(55..60)) }.to_a
+    assert_equal 1, firms.size
+    assert_equal companies(:rails_core), firms.first
+  end
+
+  def tests_finding_with_block_and_methods_on_joined_table
+    firms = DependentFirm.joins(:account).where { |firm| firm.name.eq("RailsCore").and(firm.accounts.credit_limit.eq(55..60)) }.to_a
+    assert_equal 1, firms.size
+    assert_equal companies(:rails_core), firms.first
+  end
+
+  def tests_finding_with_block_and_instance_methods_on_joined_table
+    firms = DependentFirm.joins(:account).where { name.eq("RailsCore").and(accounts.credit_limit.eq(55..60)) }.to_a
+    assert_equal 1, firms.size
+    assert_equal companies(:rails_core), firms.first
+  end
+
   def test_find_all_with_join
     developers_on_project_one = Developer.joins("LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id").
       where("project_id=1").to_a
diff --git a/activerecord/test/cases/scoping/relation_scoping_test.rb b/activerecord/test/cases/scoping/relation_scoping_test.rb
index 2cb28506bc9ec..6b4290c737ce7 100644
--- a/activerecord/test/cases/scoping/relation_scoping_test.rb
+++ b/activerecord/test/cases/scoping/relation_scoping_test.rb
@@ -367,10 +367,10 @@ def test_three_level_nested_exclusive_scoped_find
     Developer.where("name = 'Jamis'").scoping do
       assert_equal "Jamis", Developer.first.name
 
-      Developer.unscoped.where("name = 'David'") do
+      Developer.unscoped.where("name = 'David'").scoping do
         assert_equal "David", Developer.first.name
 
-        Developer.unscoped.where("name = 'Maiha'") do
+        Developer.unscoped.where("name = 'Maiha'").scoping do
           assert_nil Developer.first
         end
 
diff --git a/activerecord/test/cases/signed_id_test.rb b/activerecord/test/cases/signed_id_test.rb
index 9933401ebe83d..d42a4d02f2b0c 100644
--- a/activerecord/test/cases/signed_id_test.rb
+++ b/activerecord/test/cases/signed_id_test.rb
@@ -27,9 +27,10 @@ class SignedIdTest < ActiveRecord::TestCase
   end
 
   test "raise UnknownPrimaryKey when model have no primary key" do
-    assert_raises(ActiveRecord::UnknownPrimaryKey) do
+    error = assert_raises(ActiveRecord::UnknownPrimaryKey) do
       Matey.find_signed("this will not be even verified")
     end
+    assert_equal "Unknown primary key for table mateys in model Matey.", error.message
   end
 
   test "find signed record with a bang" do
diff --git a/activestorage/lib/active_storage/attached/model.rb b/activestorage/lib/active_storage/attached/model.rb
index 027d52c459bee..2c600fae01d7f 100644
--- a/activestorage/lib/active_storage/attached/model.rb
+++ b/activestorage/lib/active_storage/attached/model.rb
@@ -46,7 +46,8 @@ def has_one_attached(name, dependent: :purge_later, service: nil)
         generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
           # frozen_string_literal: true
           def #{name}
-            @active_storage_attached_#{name} ||= ActiveStorage::Attached::One.new("#{name}", self)
+            @active_storage_attached ||= {}
+            @active_storage_attached[:#{name}] ||= ActiveStorage::Attached::One.new("#{name}", self)
           end
 
           def #{name}=(attachable)
@@ -116,7 +117,8 @@ def has_many_attached(name, dependent: :purge_later, service: nil)
         generated_association_methods.class_eval <<-CODE, __FILE__, __LINE__ + 1
           # frozen_string_literal: true
           def #{name}
-            @active_storage_attached_#{name} ||= ActiveStorage::Attached::Many.new("#{name}", self)
+            @active_storage_attached ||= {}
+            @active_storage_attached[:#{name}] ||= ActiveStorage::Attached::Many.new("#{name}", self)
           end
 
           def #{name}=(attachables)
@@ -183,6 +185,12 @@ def changed_for_autosave? #:nodoc:
       super || attachment_changes.any?
     end
 
+    def initialize_dup(*) #:nodoc:
+      super
+      @active_storage_attached = nil
+      @attachment_changes = nil
+    end
+
     def reload(*) #:nodoc:
       super.tap { @attachment_changes = nil }
     end
diff --git a/activestorage/test/models/attached/many_test.rb b/activestorage/test/models/attached/many_test.rb
index 6be89a6afa799..62620f9f84b04 100644
--- a/activestorage/test/models/attached/many_test.rb
+++ b/activestorage/test/models/attached/many_test.rb
@@ -552,6 +552,20 @@ class ActiveStorage::ManyAttachedTest < ActiveSupport::TestCase
     end
   end
 
+  test "duped record does not share attachments" do
+    @user.highlights.attach [ create_blob(filename: "funky.jpg") ]
+
+    assert_not_equal @user.highlights.first, @user.dup.highlights.first
+  end
+
+  test "duped record does not share attachment changes" do
+    @user.highlights.attach [ create_blob(filename: "funky.jpg") ]
+    assert_not_predicate @user, :changed_for_autosave?
+
+    @user.dup.highlights.attach [ create_blob(filename: "town.mp4") ]
+    assert_not_predicate @user, :changed_for_autosave?
+  end
+
   test "clearing change on reload" do
     @user.highlights = [ create_blob(filename: "funky.jpg"), create_blob(filename: "town.jpg") ]
     assert @user.highlights.attached?
diff --git a/activestorage/test/models/attached/one_test.rb b/activestorage/test/models/attached/one_test.rb
index 3d5a7dc3602c6..f3c5ead528a14 100644
--- a/activestorage/test/models/attached/one_test.rb
+++ b/activestorage/test/models/attached/one_test.rb
@@ -529,6 +529,20 @@ def upload.open
     end
   end
 
+  test "duped record does not share attachments" do
+    @user.avatar.attach create_blob(filename: "funky.jpg")
+
+    assert_not_equal @user.avatar.attachment, @user.dup.avatar.attachment
+  end
+
+  test "duped record does not share attachment changes" do
+    @user.avatar.attach create_blob(filename: "funky.jpg")
+    assert_not_predicate @user, :changed_for_autosave?
+
+    @user.dup.avatar.attach create_blob(filename: "town.jpg")
+    assert_not_predicate @user, :changed_for_autosave?
+  end
+
   test "clearing change on reload" do
     @user.avatar = create_blob(filename: "funky.jpg")
     assert @user.avatar.attached?
diff --git a/railties/test/application/per_request_digest_cache_test.rb b/railties/test/application/per_request_digest_cache_test.rb
index ab055c7648f7f..84f0dd7a0d824 100644
--- a/railties/test/application/per_request_digest_cache_test.rb
+++ b/railties/test/application/per_request_digest_cache_test.rb
@@ -62,7 +62,7 @@ def index
   end
 
   test "template digests are cleared before a request" do
-    assert_called(ActionView::LookupContext::DetailsKey, :clear) do
+    assert_called(ActionView::LookupContext::DetailsKey, :clear, times: 3) do
       get "/customers"
       assert_equal 200, last_response.status
     end
diff --git a/railties/test/application/rendering_test.rb b/railties/test/application/rendering_test.rb
index ab1591f38801f..bf2382695f513 100644
--- a/railties/test/application/rendering_test.rb
+++ b/railties/test/application/rendering_test.rb
@@ -42,5 +42,47 @@ def show
       get "/pages/foo.bar"
       assert_equal 200, last_response.status
     end
+
+    test "New formats and handlers are detected from initializers" do
+      app_file "config/routes.rb", <<-RUBY
+        Rails.application.routes.draw do
+          root to: 'pages#show'
+        end
+      RUBY
+
+      app_file "app/controllers/pages_controller.rb", <<-RUBY
+        class PagesController < ApplicationController
+          layout false
+
+          def show
+            render :show, formats: [:awesome], handlers: [:rubby]
+          end
+        end
+      RUBY
+
+      app_file "app/views/pages/show.awesome.rubby", <<-RUBY
+        {
+          format: @current_template.format,
+          handler: @current_template.handler
+        }.inspect
+      RUBY
+
+      app_file "config/initializers/mime_types.rb", <<-RUBY
+        Mime::Type.register "text/awesome", :awesome
+      RUBY
+
+      app_file "config/initializers/template_handlers.rb", <<-RUBY
+        module RubbyHandler
+          def self.call(_, source)
+            source
+          end
+        end
+        ActionView::Template.register_template_handler(:rubby, RubbyHandler)
+      RUBY
+
+      get "/"
+      assert_equal 200, last_response.status
+      assert_equal "{:format=>:awesome, :handler=>RubbyHandler}", last_response.body
+    end
   end
 end