render_turbo_stream 1.3.3 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +78 -73
- data/app/views/_render_turbo_stream.turbo_stream.erb +49 -0
- data/app/views/render_turbo_stream.html.erb +1 -0
- data/lib/render_turbo_stream/test/request_helpers.rb +11 -15
- data/lib/render_turbo_stream/version.rb +1 -1
- data/lib/render_turbo_stream.rb +29 -37
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cf65f7ac19c9387a4ee6a42f9e2227082ec7996aef74e672cc45aa04f75becf7
|
4
|
+
data.tar.gz: 558ecbee422b8eab39c292f82ae0df3df54cc5b439b0e8a7b83008dc863e00ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 11549d8a9b8145ca04e7e025516a74edbeeb726bfa9d3bfca0fa819c2d8bf76da7d354c860ec01ec461b4d50b80e0fb197aaf34b30502b1d29d72718982cf149
|
7
|
+
data.tar.gz: 8405143aeae7793da5011b56e5993843dac2f0f97c5d0cce712d5fac0eca5a1e4045dd7680b72db76f6c5c761dd35f7f3fa24fb71e029dbde6bda8b49b140281
|
data/README.md
CHANGED
@@ -6,6 +6,8 @@ Working consistently with turbo_stream means shooting lots of partials from the
|
|
6
6
|
|
7
7
|
And build it dynamically: Most [turbo_power](https://github.com/marcoroth/turbo_power) commands, such as adding a css class to an html element or pushing a state to the browser history, work directly from the controller. Since turbo allows it, custom javascript functions are also possible.
|
8
8
|
|
9
|
+
ATTENTION: This plugin is in a early state.
|
10
|
+
|
9
11
|
An overview of how we design a rails-7 application with turbo
|
10
12
|
is [published on dev.to](https://dev.to/chmich/rails-7-vite-wrapping-up-1pia).
|
11
13
|
|
@@ -34,11 +36,6 @@ RSpec.configure do |config|
|
|
34
36
|
end
|
35
37
|
```
|
36
38
|
|
37
|
-
**Redirection and more**
|
38
|
-
|
39
|
-
For redirection to work, you must follow the installation steps
|
40
|
-
from [turbo_power](https://github.com/marcoroth/turbo_power).
|
41
|
-
|
42
39
|
**Flash**
|
43
40
|
|
44
41
|
Required Configurations for Flash Partial
|
@@ -51,16 +48,37 @@ config.x.render_turbo_stream.flash_action = 'prepend'
|
|
51
48
|
|
52
49
|
The corresponding partials for flashes could look [like this](https://gitlab.com/sedl/renderturbostream/-/wikis/flashes)
|
53
50
|
|
51
|
+
**Translations**
|
52
|
+
|
53
|
+
```
|
54
|
+
activerecord.success.successfully_created
|
55
|
+
activerecord.success.successfully_updated
|
56
|
+
activerecord.errors.messages.could_not_create
|
57
|
+
activerecord.errors.messages.could_not_update
|
58
|
+
```
|
59
|
+
|
60
|
+
example value: `"%<model_name>s successfully created"`
|
61
|
+
|
62
|
+
Model name translations, see: Rails Docs.
|
63
|
+
|
64
|
+
**Turbo power**
|
65
|
+
|
66
|
+
To get redirection and many other options working, you need to follow the installation steps from [turbo_power](https://github.com/marcoroth/turbo_power).
|
67
|
+
|
68
|
+
**Turbo itself**
|
69
|
+
|
70
|
+
A comprehensive tutorial on turbo and how to check that it is working properly can be found at [hotrails.dev](https://www.hotrails.dev/turbo-rails).
|
71
|
+
|
54
72
|
## Usage
|
55
73
|
|
56
|
-
`turbo_stream_save` is a special method for streamlining `update
|
74
|
+
`turbo_stream_save` is a special method for streamlining `update` or `create` functions with `turbo_stream`. A controller action for update might look like this:
|
57
75
|
|
58
76
|
```ruby
|
59
77
|
|
60
78
|
def update
|
61
79
|
turbo_stream_save(
|
62
80
|
@customer.update(customer_params),
|
63
|
-
redirect_on_success_to: edit_customer_path(@customer)
|
81
|
+
redirect_on_success_to: edit_customer_path(@customer),
|
64
82
|
)
|
65
83
|
end
|
66
84
|
```
|
@@ -72,7 +90,7 @@ render_turbo_stream(
|
|
72
90
|
[
|
73
91
|
{
|
74
92
|
id: 'form',
|
75
|
-
partial: 'form'
|
93
|
+
partial: 'customers/form'
|
76
94
|
},
|
77
95
|
{
|
78
96
|
id: 'flash-wrapper',
|
@@ -96,7 +114,7 @@ stream_partial(
|
|
96
114
|
|
97
115
|
**locals**: The hash for locals goes through a `symbolize_keys`, so you need to use locals in used partials like this: `locals[:message]`.
|
98
116
|
|
99
|
-
|
117
|
+
**More options**
|
100
118
|
|
101
119
|
`render_turbo_stream` interprets a hash as a partial to be sent by `turbo_stream` and an array as a command to be sent. This allows you to perform most actions from e.g. [turbo_power](https://github.com/marcoroth/turbo_power). An example of adding a css class to an html element and updating the browser history would look like this:
|
102
120
|
|
@@ -111,12 +129,45 @@ render_turbo_stream(
|
|
111
129
|
:add_css_class,
|
112
130
|
'#colored-element',
|
113
131
|
'red'
|
132
|
+
],
|
133
|
+
[
|
134
|
+
|
114
135
|
]
|
115
136
|
]
|
116
137
|
)
|
117
138
|
```
|
118
139
|
|
119
|
-
|
140
|
+
Under the hood, inside a `*.turbo_stream.erb` template, it does the following: `= turbo_stream.send args.first, *(args[1..-1])`
|
141
|
+
|
142
|
+
**Parameters for turbo_stream_save**
|
143
|
+
|
144
|
+
save_action,
|
145
|
+
redirect_on_success_to: nil,
|
146
|
+
object: nil, # object used in save_action, example: @customer
|
147
|
+
id: 'form', # if nil: no partial is rendered
|
148
|
+
partial: nil, # example: 'customers/form' default: "#{controller_path}/#{id}"
|
149
|
+
action: 'replace', # options: append, prepend
|
150
|
+
locals: {}, # locals used by the partial
|
151
|
+
streams_on_success: [
|
152
|
+
{
|
153
|
+
id: nil,
|
154
|
+
partial: 'form',
|
155
|
+
locals: {},
|
156
|
+
action: 'replace'
|
157
|
+
}
|
158
|
+
], # additional partials that should be rendered if save_action succeeded
|
159
|
+
streams_on_error: [
|
160
|
+
{
|
161
|
+
id: nil,
|
162
|
+
partial: 'form',
|
163
|
+
locals: {},
|
164
|
+
action: 'replace'
|
165
|
+
}
|
166
|
+
], # additional partials that should be rendered if save_action failed
|
167
|
+
add_flash_alerts: [], #=> array of strings
|
168
|
+
add_flash_notices: [], #=> array of strings
|
169
|
+
flashes_on_success: [], #=> array of strings
|
170
|
+
flashes_on_error: [] #=> array of strings
|
120
171
|
|
121
172
|
|
122
173
|
## Testing Scenarios
|
@@ -126,7 +177,7 @@ For system testing there is Capybara. Its the only way to check if frontend and
|
|
126
177
|
If the request format is not `turbo_stream`, which is the case on request specs, the method responds in a special html
|
127
178
|
that contains the medadata that is interesting for our tests and is parsed by included test helpers.
|
128
179
|
|
129
|
-
There is a helper for writing the test: In the debugger, within the test, check the output of `
|
180
|
+
There is a helper for writing the test: In the debugger, within the test, check the output of `streams_log`.
|
130
181
|
|
131
182
|
Test scenarios are:
|
132
183
|
|
@@ -141,10 +192,10 @@ For rspec there is a special helper, for a successful save action:
|
|
141
192
|
```ruby
|
142
193
|
it 'update failed' do
|
143
194
|
patch article_path(article, params: valid_params)
|
144
|
-
expect_successful_saved('form', 'flash-box')
|
195
|
+
expect_successful_saved('customer-form', 'flash-box')
|
145
196
|
# expects response.status 200
|
146
197
|
# Make sure that the responses point to exactly these 2 IDs ('form' and 'flash-box').
|
147
|
-
# Make sure that each ID is responded to exactly once.
|
198
|
+
# Make sure that to each ID is responded to exactly once.
|
148
199
|
end
|
149
200
|
```
|
150
201
|
|
@@ -160,14 +211,14 @@ end
|
|
160
211
|
|
161
212
|
For checking a little more inside the partial responses, but with a simple syntax that checks for a one-time response from a specific partial and may be sufficient for most cases:
|
162
213
|
```ruby
|
163
|
-
expect(
|
164
|
-
# Check the total number of
|
214
|
+
expect(stream_targets_count).to eq(2)
|
215
|
+
# Check the total number of targeted html-ids, in most cases it will be one form and one flash.
|
165
216
|
|
166
|
-
expect(
|
167
|
-
# Make sure that the form
|
217
|
+
expect(stream_response('customer-form')).to eq(true)
|
218
|
+
# Make sure that the id «customer-form» is targeted exactly once.
|
168
219
|
|
169
|
-
expect(
|
170
|
-
#
|
220
|
+
expect(stream_response('customer-form', css: '.field_with_errors', include_string: 'Title')).to eq(true)
|
221
|
+
# Verify that the response targeted to "customer-form" contains the specified html.
|
171
222
|
```
|
172
223
|
|
173
224
|
**More detailed**
|
@@ -175,77 +226,31 @@ expect(partial_response('articles/form', id: 'form', css: '.field_with_errors',
|
|
175
226
|
Consider a controller action that should respond in 2 flashes:
|
176
227
|
|
177
228
|
```ruby
|
178
|
-
expect(
|
229
|
+
expect(stream_targets_count).to eq(1)
|
179
230
|
|
180
231
|
expect(
|
181
|
-
|
232
|
+
stream_target_response_count('flash-box', total: 2) do |p|
|
182
233
|
p.css('.callout.success').inner_html.include?('All perfect')
|
183
234
|
end
|
184
235
|
).to eq(1)
|
185
236
|
|
186
237
|
expect(
|
187
|
-
|
238
|
+
stream_target_response_count('flash-box', total: 2) do |p|
|
188
239
|
p.css('.callout.alert').inner_html.include?('Something went wrong')
|
189
240
|
end
|
190
241
|
).to eq(1)
|
191
242
|
```
|
192
|
-
`
|
243
|
+
`stream_target_response_count` returns the number of matched responses.
|
244
|
+
|
245
|
+
Possible matchers can be found at [Nokogiri](https://nokogiri.org/tutorials/searching_a_xml_html_document.html).
|
193
246
|
|
194
|
-
|
247
|
+
For more detailed testing of partials, there are view tests.
|
195
248
|
|
196
|
-
P.S
|
249
|
+
**P.S.:**
|
197
250
|
|
198
251
|
Testing the plugin itself: There is a [quick-and-dirty app](https://gitlab.com/sedl/renderturbostream_railsapp) which
|
199
252
|
includes the plugin and has tests done by rspec/request and capybara.
|
200
253
|
|
201
|
-
## Parameters for turbo_stream_save
|
202
|
-
|
203
|
-
save_action,
|
204
|
-
redirect_on_success_to: nil,
|
205
|
-
object: nil, # object used in save_action, example: @customer
|
206
|
-
id: 'form', # if nil: no partial is rendered
|
207
|
-
partial: nil, # example: 'customers/form' default: "#{controller_path}/#{id}"
|
208
|
-
action: 'replace', # options: append, prepend
|
209
|
-
locals: {}, # locals used by the partial
|
210
|
-
streams_on_success: [
|
211
|
-
{
|
212
|
-
id: nil,
|
213
|
-
partial: 'form',
|
214
|
-
locals: {},
|
215
|
-
action: 'replace'
|
216
|
-
}
|
217
|
-
], # additional partials that should be rendered if save_action succeeded
|
218
|
-
streams_on_error: [
|
219
|
-
{
|
220
|
-
id: nil,
|
221
|
-
partial: 'form',
|
222
|
-
locals: {},
|
223
|
-
action: 'replace'
|
224
|
-
}
|
225
|
-
], # additional partials that should be rendered if save_action failed
|
226
|
-
add_flash_alerts: [], #=> array of strings
|
227
|
-
add_flash_notices: [], #=> array of strings
|
228
|
-
flashes_on_success: [], #=> array of strings
|
229
|
-
flashes_on_error: [] #=> array of strings
|
230
|
-
|
231
|
-
## Requirements
|
232
|
-
|
233
|
-
gem `turbo_power` (is included, used for redirection)
|
234
|
-
|
235
|
-
**Translations**
|
236
|
-
|
237
|
-
`activerecord.success.successfully_created`
|
238
|
-
|
239
|
-
`activerecord.success.successfully_updated`
|
240
|
-
|
241
|
-
`activerecord.errors.messages.could_not_create`
|
242
|
-
|
243
|
-
`activerecord.errors.messages.could_not_update`
|
244
|
-
|
245
|
-
example value: `"%<model_name>s successfully created"`
|
246
|
-
|
247
|
-
.. and Model Name Translations
|
248
|
-
|
249
254
|
## Contributing
|
250
255
|
|
251
256
|
Contribution welcome.
|
@@ -0,0 +1,49 @@
|
|
1
|
+
<% error = false %>
|
2
|
+
<% control = [] %>
|
3
|
+
<% streams = local_assigns[:streams] %>
|
4
|
+
<% streams.each do |args| %>
|
5
|
+
|
6
|
+
|
7
|
+
<% if args.is_a?(Array) %>
|
8
|
+
|
9
|
+
<% ctl = "turbo_stream.#{args.first}, #{args[1..-1].join(', ')}" %>
|
10
|
+
|
11
|
+
<% if args.last.is_a?(Proc) %>
|
12
|
+
<% Rails.logger.error(" RENDER TURBO STREAM BLOCK => #{ctl}") %>
|
13
|
+
<%#= turbo_stream.replace 'form' do %>
|
14
|
+
<%#= render partial: 'articles/form' %>
|
15
|
+
<%# end %>
|
16
|
+
<%= turbo_stream.send args.first, *(args[1..-2]) { args.last.call } %>
|
17
|
+
<% else %>
|
18
|
+
<% Rails.logger.error(" RENDER TURBO POWER => #{ctl}") %>
|
19
|
+
<%= turbo_stream.send args.first, *(args[1..-1]) %>
|
20
|
+
<% end %>
|
21
|
+
|
22
|
+
<% else %>
|
23
|
+
<% ctl = { partial: args[:partial], id: args[:id], action: args[:action] } %>
|
24
|
+
<% info = { id: args[:id], partial: args[:partial], locals: args[:locals] } %>
|
25
|
+
<% if !args[:action].present? %>
|
26
|
+
<% Rails.logger.error(" ERROR RENDER TURBO STREAM => MISSING ACTION => #{args}") %>
|
27
|
+
<% error = true %>
|
28
|
+
|
29
|
+
<% elsif args[:action].present? %>
|
30
|
+
<% Rails.logger.debug(" • render-turbo-stream #{args[:action].upcase} => #{info}") %>
|
31
|
+
<%= turbo_stream.send args[:action].to_sym, args[:id] do %>
|
32
|
+
<%= render args[:partial], locals: args[:locals]&.symbolize_keys %>
|
33
|
+
<% end %>
|
34
|
+
|
35
|
+
<% else %>
|
36
|
+
<% Rails.logger.error(" ERROR RENDER TURBO STREAM => NOTHING DONE! => #{args}") %>
|
37
|
+
<% end %>
|
38
|
+
<% end %>
|
39
|
+
|
40
|
+
<% control.push(ctl) %>
|
41
|
+
|
42
|
+
<% end %>
|
43
|
+
|
44
|
+
<% if error %>
|
45
|
+
<% Rails.logger.error(" RENDER TURBO STREAM HAD ERRORS, REPEATING WHOLE ARRAY: ") %>
|
46
|
+
<% control.each do |c| %>
|
47
|
+
<% Rails.logger.error(" #{c}") %>
|
48
|
+
<% end %>
|
49
|
+
<% end %>
|
@@ -6,6 +6,7 @@
|
|
6
6
|
<% rendered_partials.push({ command: "turbo_stream.#{s.first}, #{s[1..-1].join(', ')}", method: s.first, attributes: s[1..-1] }) %>
|
7
7
|
<% else %>
|
8
8
|
<% html = (render s[:partial], locals: s[:locals]&.symbolize_keys, formats: [:html]) %>
|
9
|
+
<% s.delete(:partial) %>
|
9
10
|
<% rendered_partials.push({ html_response: html }.merge(s)) %>
|
10
11
|
<% end %>
|
11
12
|
|
@@ -2,16 +2,8 @@ module RenderTurboStream
|
|
2
2
|
module Test
|
3
3
|
module RequestHelpers
|
4
4
|
|
5
|
-
# count of rendered partials
|
6
|
-
# count rendering different partials (example: «customer/form»)
|
7
|
-
# doesnt check how often a specific partial is rendered
|
8
|
-
|
9
|
-
def partials_count
|
10
|
-
RenderTurboStream::Libs.partials_count(response)
|
11
|
-
end
|
12
|
-
|
13
5
|
# log as helper for the developer to see which flash is set and which partials are rendered to wich ids
|
14
|
-
def
|
6
|
+
def streams_log
|
15
7
|
all_responses = RenderTurboStream::Libs.all_responses(response)
|
16
8
|
r = []
|
17
9
|
if response.status == 302
|
@@ -48,12 +40,12 @@ module RenderTurboStream
|
|
48
40
|
url
|
49
41
|
end
|
50
42
|
|
51
|
-
def
|
52
|
-
RenderTurboStream::Libs.
|
43
|
+
def stream_target_response_count(id, total: 1, &block)
|
44
|
+
RenderTurboStream::Libs.stream_target_response_count(response, id, total, &block)
|
53
45
|
end
|
54
46
|
|
55
|
-
def
|
56
|
-
c =
|
47
|
+
def stream_response(id, css: nil, include_string: nil)
|
48
|
+
c = stream_target_response_count(id, total: 1) do |r|
|
57
49
|
if css && include_string
|
58
50
|
r.css(css).inner_html.include?(include_string)
|
59
51
|
elsif include_string
|
@@ -67,8 +59,12 @@ module RenderTurboStream
|
|
67
59
|
c == 1
|
68
60
|
end
|
69
61
|
|
70
|
-
def
|
71
|
-
RenderTurboStream::Libs.
|
62
|
+
def stream_targets
|
63
|
+
RenderTurboStream::Libs.stream_targets(response)
|
64
|
+
end
|
65
|
+
|
66
|
+
def stream_targets_count
|
67
|
+
stream_targets.length
|
72
68
|
end
|
73
69
|
end
|
74
70
|
end
|
data/lib/render_turbo_stream.rb
CHANGED
@@ -8,7 +8,8 @@ module RenderTurboStream
|
|
8
8
|
|
9
9
|
def turbo_stream_save(
|
10
10
|
save_action,
|
11
|
-
redirect_on_success_to: nil,
|
11
|
+
redirect_on_success_to: nil, # does a regular redirect. Works if you are inside a turbo_frame and just want to redirect inside that frame
|
12
|
+
turbo_redirect_on_success_to: nil, # does a full page redirect (break out of all frames by turbo_power redirect)
|
12
13
|
object: nil, # object used in save_action, example: @customer
|
13
14
|
id: 'form', # if nil: no partial is rendered
|
14
15
|
partial: nil, # example: 'customers/form' default: "#{controller_path}/#{id}"
|
@@ -122,16 +123,27 @@ module RenderTurboStream
|
|
122
123
|
|
123
124
|
#== render
|
124
125
|
|
125
|
-
if save_action &&
|
126
|
+
if save_action && turbo_redirect_on_success_to.present?
|
126
127
|
response.status = 302
|
127
128
|
flash[:alert] = flash_alerts
|
128
129
|
flash[:notice] = flash_notices
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
130
|
+
render_turbo_stream([
|
131
|
+
[
|
132
|
+
:redirect_to,
|
133
|
+
turbo_redirect_on_success_to
|
134
|
+
]
|
135
|
+
])
|
136
|
+
elsif save_action && redirect_on_success_to.present?
|
137
|
+
response.status = 302
|
138
|
+
flash.now[:alert] = flash_alerts
|
139
|
+
flash.now[:notice] = flash_notices
|
140
|
+
render_turbo_stream([
|
141
|
+
[
|
142
|
+
:redirect_to,
|
143
|
+
turbo_redirect_on_success_to
|
144
|
+
]
|
145
|
+
])
|
146
|
+
|
135
147
|
else
|
136
148
|
flash.now[:alert] = flash_alerts
|
137
149
|
flash.now[:notice] = flash_notices
|
@@ -191,40 +203,20 @@ module RenderTurboStream
|
|
191
203
|
|
192
204
|
# if partial is one word, its checked against the last behind the slash, example: 'articles/form' matches 'form'
|
193
205
|
|
194
|
-
def self.select_responses(response,
|
206
|
+
def self.select_responses(response, id)
|
195
207
|
all = all_responses(response)
|
196
208
|
|
197
209
|
all.select do |a|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
else
|
203
|
-
a['partial'].split('/').last == partial
|
204
|
-
end
|
205
|
-
id_matched = if !id.present?
|
206
|
-
false
|
207
|
-
else
|
208
|
-
a['id'] == id
|
209
|
-
end
|
210
|
-
partial_matched && (id.present? ? id_matched : true)
|
211
|
-
end
|
212
|
-
|
213
|
-
end
|
214
|
-
|
215
|
-
def self.partials_count(response)
|
216
|
-
all = all_responses(response)
|
217
|
-
part = []
|
218
|
-
all.each do |p|
|
219
|
-
_p = p['partial']
|
220
|
-
unless part.include?(_p)
|
221
|
-
part.push(_p)
|
210
|
+
if id.present?
|
211
|
+
a['id'] == id
|
212
|
+
else
|
213
|
+
false
|
222
214
|
end
|
223
215
|
end
|
224
|
-
|
216
|
+
|
225
217
|
end
|
226
218
|
|
227
|
-
def self.
|
219
|
+
def self.stream_targets(response)
|
228
220
|
all = all_responses(response)
|
229
221
|
ids = []
|
230
222
|
all.each do |p|
|
@@ -236,8 +228,8 @@ module RenderTurboStream
|
|
236
228
|
ids
|
237
229
|
end
|
238
230
|
|
239
|
-
def self.
|
240
|
-
responses = select_responses(response,
|
231
|
+
def self.stream_target_response_count(response, id, total, &block)
|
232
|
+
responses = select_responses(response, id)
|
241
233
|
|
242
234
|
if total && responses.count != total
|
243
235
|
false
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: render_turbo_stream
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.4.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- christian
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-04-
|
11
|
+
date: 2023-04-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -38,6 +38,7 @@ files:
|
|
38
38
|
- README.md
|
39
39
|
- Rakefile
|
40
40
|
- app/controllers/render_turbo_stream/application_controller.rb
|
41
|
+
- app/views/_render_turbo_stream.turbo_stream.erb
|
41
42
|
- app/views/render_turbo_stream.html.erb
|
42
43
|
- app/views/render_turbo_stream.turbo_stream.erb
|
43
44
|
- lib/render_turbo_stream.rb
|