render_turbo_stream 1.3.3 → 1.4.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|