diff --git a/actionview/CHANGELOG.md b/actionview/CHANGELOG.md
index a1e94384bd..c663ec3bbc 100644
--- a/actionview/CHANGELOG.md
+++ b/actionview/CHANGELOG.md
@@ -1,3 +1,7 @@
+* `RenderingHelper` supports rendering objects that `respond_to?` `:render_in`
+
+ *Joel Hawksley*, *Natasha Umer*, *Aaron Patterson*, *Shawn Allen*, *Emily Plummer*, *Diana Mounter*, *John Hawthorn*, *Nathan Herald*, *Zaid Zawaideh*, *Zach Ahn*
+
* Fix `select_tag` so that it doesn't change `options` when `include_blank` is present.
*Younes SERRAJ*
diff --git a/actionview/lib/action_view/helpers/rendering_helper.rb b/actionview/lib/action_view/helpers/rendering_helper.rb
index 7ead691113..4be23b68f3 100644
--- a/actionview/lib/action_view/helpers/rendering_helper.rb
+++ b/actionview/lib/action_view/helpers/rendering_helper.rb
@@ -35,7 +35,11 @@ module ActionView
end
end
else
- view_renderer.render_partial(self, partial: options, locals: locals, &block)
+ if options.respond_to?(:render_in)
+ options.render_in(self, &block)
+ else
+ view_renderer.render_partial(self, partial: options, locals: locals, &block)
+ end
end
end
diff --git a/actionview/test/lib/test_component.rb b/actionview/test/lib/test_component.rb
new file mode 100644
index 0000000000..493b9487b1
--- /dev/null
+++ b/actionview/test/lib/test_component.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+class TestComponent < ActionView::Base
+ include ActiveModel::Validations
+
+ validates :content, :title, presence: true
+ delegate :render, to: :view_context
+
+ def initialize(title:)
+ @title = title
+ end
+
+ # Entrypoint for rendering. Called by ActionView::RenderingHelper#render.
+ #
+ # Returns ActionView::OutputBuffer.
+ def render_in(view_context, &block)
+ self.class.compile
+ @view_context = view_context
+ @content = view_context.capture(&block) if block_given?
+ validate!
+ rendered_template
+ end
+
+ def self.template
+ <<~'erb'
+ <%= content %> (<%= render(plain: "Inline render") %>)
+ erb
+ end
+
+ def self.compile
+ @compiled ||= nil
+ return if @compiled
+
+ class_eval(
+ "def rendered_template; @output_buffer = ActionView::OutputBuffer.new; " +
+ ActionView::Template::Handlers::ERB.erb_implementation.new(template, trim: true).src +
+ "; end"
+ )
+
+ @compiled = true
+ end
+
+private
+
+ attr_reader :content, :title, :view_context
+end
diff --git a/actionview/test/template/render_test.rb b/actionview/test/template/render_test.rb
index 2235a7816f..c82264a170 100644
--- a/actionview/test/template/render_test.rb
+++ b/actionview/test/template/render_test.rb
@@ -2,6 +2,8 @@
require "abstract_unit"
require "controller/fake_models"
+require "test_component"
+require "active_model/validations"
class TestController < ActionController::Base
end
@@ -670,6 +672,21 @@ module RenderTestCases
def test_render_throws_exception_when_no_extensions_passed_to_register_template_handler_function_call
assert_raises(ArgumentError) { ActionView::Template.register_template_handler CustomHandler }
end
+
+ def test_render_component
+ assert_equal(
+ %(Hello, World! (Inline render)),
+ @view.render(TestComponent.new(title: "my title")) { "Hello, World!" }.strip
+ )
+ end
+
+ def test_render_component_with_validation_error
+ error = assert_raises(ActiveModel::ValidationError) do
+ @view.render(TestComponent.new(title: "my title")).strip
+ end
+
+ assert_match "Content can't be blank", error.message
+ end
end
class CachedViewRenderTest < ActiveSupport::TestCase