render_turbo_stream 1.3.3 → 1.4.1
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 +72 -69
- 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 +15 -35
- 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: 395968d08aa5420fbd50bb63bccb641098503d33002a36a1a9f753b52caaae2b
|
4
|
+
data.tar.gz: 537a3ed1c128c26df63083c545c7ba53c02167c30a38a69adf08261bbb27e069
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a793feeaa175a29b27bd2811f6248de5e5e913224f455547b6fdb5cd1707e36f76d3e2b23824cd3c4a2c7197a5c25e50f99800c355ed0b55c76bae78f127586a
|
7
|
+
data.tar.gz: 0d0294aed7dc3c57e580d34a939145c3ccd423a434da2bfad40d8daf7a769b3e4efece554d814a4f533f0eb21fb1efcdba4d223a6fc701106434635bd2e58a32
|
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,7 +192,7 @@ 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
198
|
# Make sure that each ID is responded to exactly once.
|
@@ -160,13 +211,13 @@ 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(
|
220
|
+
expect(stream_response('customer-form', css: '.field_with_errors', include_string: 'Title')).to eq(true)
|
170
221
|
# Check the content inside at least one in any of the elements that match the given css and check the id that turbo has pointed to..
|
171
222
|
```
|
172
223
|
|
@@ -175,21 +226,21 @@ 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` always returns the number of matched responses.
|
193
244
|
|
194
245
|
Possible matchers can be found at [Nokogiri](https://nokogiri.org/tutorials/searching_a_xml_html_document.html). That should be enough for this level. For more detailed testing of partials, there are view tests. And many of the partials are regularly accessed in two ways, for example a `form': it is pushed by turbo_stream as well as by calling edit_customer_path.
|
195
246
|
|
@@ -198,54 +249,6 @@ P.S.:
|
|
198
249
|
Testing the plugin itself: There is a [quick-and-dirty app](https://gitlab.com/sedl/renderturbostream_railsapp) which
|
199
250
|
includes the plugin and has tests done by rspec/request and capybara.
|
200
251
|
|
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
252
|
## Contributing
|
250
253
|
|
251
254
|
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
@@ -126,12 +126,12 @@ module RenderTurboStream
|
|
126
126
|
response.status = 302
|
127
127
|
flash[:alert] = flash_alerts
|
128
128
|
flash[:notice] = flash_notices
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
129
|
+
render_turbo_stream([
|
130
|
+
[
|
131
|
+
:redirect_to,
|
132
|
+
redirect_on_success_to
|
133
|
+
]
|
134
|
+
])
|
135
135
|
else
|
136
136
|
flash.now[:alert] = flash_alerts
|
137
137
|
flash.now[:notice] = flash_notices
|
@@ -191,40 +191,20 @@ module RenderTurboStream
|
|
191
191
|
|
192
192
|
# if partial is one word, its checked against the last behind the slash, example: 'articles/form' matches 'form'
|
193
193
|
|
194
|
-
def self.select_responses(response,
|
194
|
+
def self.select_responses(response, id)
|
195
195
|
all = all_responses(response)
|
196
196
|
|
197
197
|
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)
|
198
|
+
if id.present?
|
199
|
+
a['id'] == id
|
200
|
+
else
|
201
|
+
false
|
222
202
|
end
|
223
203
|
end
|
224
|
-
|
204
|
+
|
225
205
|
end
|
226
206
|
|
227
|
-
def self.
|
207
|
+
def self.stream_targets(response)
|
228
208
|
all = all_responses(response)
|
229
209
|
ids = []
|
230
210
|
all.each do |p|
|
@@ -236,8 +216,8 @@ module RenderTurboStream
|
|
236
216
|
ids
|
237
217
|
end
|
238
218
|
|
239
|
-
def self.
|
240
|
-
responses = select_responses(response,
|
219
|
+
def self.stream_target_response_count(response, id, total, &block)
|
220
|
+
responses = select_responses(response, id)
|
241
221
|
|
242
222
|
if total && responses.count != total
|
243
223
|
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.1
|
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
|