Skip to content

Commit 80bb910

Browse files
authored
turbo_stream tag builder: support :partial with block (#701)
The Background --- Consider an application with a shared `application/flash` partial that accepts its message as a partial-local assignment: ```erb <%# app/views/application/_flash.html.erb %> <p role="alert"><%= message %></p> ``` Also consider an `application/layout.turbo_stream.erb` template for appending the `Flash` messages to a element with `[id="flashes"]`: ```erb <%# app/views/layouts/application.turbo_stream.erb %> <% flash.each do |name, message| %> <%= turbo_stream.append "flashes", partial: "application/flash", locals: { message: message } %> <% end %> ``` This works as you'd expect, since the `:partial` and `:locals` keyword arguments are forwarded along to the underlying `render` call. The Scenario --- Now consider that the `application/flash` message changed its interface to expect the `message` as block content yielded to the partial instead of an assignment to the `:locals` options: ```diff <%# app/views/application/_flash.html.erb %> -<p role="alert"><%= message %></p> +<p role="alert"><%= yield %></p> ``` The `layouts/application.turbo_stream.erb` template would need to change as well: ```diff <%# app/views/layouts/application.turbo_stream.erb %> <% flash.each do |name, message| %> - <%= turbo_stream.append "flashes", partial: "application/flash", locals: { message: message } %> + <%= turbo_stream.append "flashes", partial: "application/flash" do %> + <span style="color: red"><%= message %></span> + <%= end %> <% end %> ``` The Problem --- This style of invocation of `turbo_stream.append` does not work the same as if it were passed a block of content generated by calling `render` with the same keywords. The presence of a `&block` argument triggers an entirely separate code path than the presence of the `**rendering` keywords. To work around this issue, you'd have to capture the rendering separately: ```diff <%# app/views/layouts/application.turbo_stream.erb %> <% flash.each do |name, message| %> - <%= turbo_stream.append "flashes", partial: "application/flash", locals: { message: message } %> + <% content = capture do %> + <%= render partial: "application/flash" do %> + <span style="color: red"><%= message %></span> + <% end %> + <% end %> + + <%= turbo_stream.append "flashes", content %> <% end %> ``` The Proposal --- This commit alters the tag builder's decision making process to incorporate a check for a combination of both a `&block` and `:partial` or `:layout` keyword arguments.
1 parent 3fcec46 commit 80bb910

File tree

3 files changed

+45
-0
lines changed

3 files changed

+45
-0
lines changed

app/models/turbo/streams/tag_builder.rb

+2
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,8 @@ def render_template(target, content = nil, allow_inferred_rendering: true, **ren
267267
content.render_in(@view_context, &block)
268268
when content
269269
allow_inferred_rendering ? (render_record(content) || content) : content
270+
when block_given? && (rendering.key?(:partial) || rendering.key?(:layout))
271+
@view_context.render(formats: [ :html ], layout: rendering[:partial], **rendering, &block)
270272
when block_given?
271273
@view_context.capture(&block)
272274
when rendering.any?
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
<p><%= yield %></p>

test/streams/streams_helper_test.rb

+42
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,48 @@ def to_key
1919

2020
attr_accessor :formats
2121

22+
test "turbo_stream builder captures block when called without :partial keyword" do
23+
rendered = turbo_stream.update "target_id" do
24+
tag.span "Hello, world"
25+
end
26+
27+
assert_dom_equal <<~HTML.strip, rendered
28+
<turbo-stream action="update" target="target_id">
29+
<template>
30+
<span>Hello, world</span>
31+
</template>
32+
</turbo-stream>
33+
HTML
34+
end
35+
36+
test "turbo_stream builder forwards block to partial when called with :partial keyword" do
37+
rendered = turbo_stream.update "target_id", partial: "application/partial_with_block" do
38+
"Hello, from application/partial_with_block partial"
39+
end
40+
41+
assert_dom_equal <<~HTML.strip, rendered
42+
<turbo-stream action="update" target="target_id">
43+
<template>
44+
<p>Hello, from application/partial_with_block partial</p>
45+
</template>
46+
</turbo-stream>
47+
HTML
48+
end
49+
50+
test "turbo_stream builder forwards block to partial when called with :layout keyword" do
51+
rendered = turbo_stream.update "target_id", layout: "application/partial_with_block" do
52+
"Hello, from application/partial_with_block partial"
53+
end
54+
55+
assert_dom_equal <<~HTML.strip, rendered
56+
<turbo-stream action="update" target="target_id">
57+
<template>
58+
<p>Hello, from application/partial_with_block partial</p>
59+
</template>
60+
</turbo-stream>
61+
HTML
62+
end
63+
2264
test "supports valid :renderable option object with nil content" do
2365
component = Component.new(id: 1, content: "Hello, world")
2466

0 commit comments

Comments
 (0)