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