render_turbo_stream 3.0.4 → 4.0.0
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 +47 -98
- data/app/views/layouts/_add_turbo_frame_tag.html.erb +6 -0
- data/app/views/render_turbo_stream.turbo_stream.erb +13 -4
- data/app/views/render_turbo_stream_request_test.html.erb +13 -5
- data/lib/render_turbo_stream/channel_libs.rb +58 -14
- data/lib/render_turbo_stream/channel_view_helpers.rb +8 -0
- data/lib/render_turbo_stream/check_template.rb +122 -0
- data/lib/render_turbo_stream/controller_channel_helpers.rb +102 -10
- data/lib/render_turbo_stream/controller_helpers.rb +79 -153
- data/lib/render_turbo_stream/controller_libs.rb +162 -0
- data/lib/render_turbo_stream/libs.rb +35 -0
- data/lib/render_turbo_stream/test/request/channel_helpers.rb +50 -10
- data/lib/render_turbo_stream/test/request/helpers.rb +22 -10
- data/lib/render_turbo_stream/test/request/libs.rb +97 -42
- data/lib/render_turbo_stream/version.rb +1 -1
- data/lib/render_turbo_stream.rb +3 -0
- metadata +13 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 525e9a802269d71e60be6cbc44fe62ebbe677a89fd49e048b1dbc11c6ae4d2b0
|
4
|
+
data.tar.gz: 5b47c7b9a7dcc49491e4ab20eb2637654b62e971afe0abdf481ec9210e97f145
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c532c3c9a5307547f260ff1feefea8e43b3cb09e42c43eacad1771c8a72b16a9843703024e1a58891a0498592c53dd296f2a6d7ef0dac72554c838c7c42d3915
|
7
|
+
data.tar.gz: f1c5cf8193d9c3214d60c492d73e7ebaf844b848dfa88a602ee235af9c0fbe90d757584938cf3b58d661d507b119791d4fe544968580654406c63abdfe4c1864
|
data/README.md
CHANGED
@@ -1,8 +1,14 @@
|
|
1
1
|
# RenderTurboStream
|
2
2
|
|
3
|
-
DHH
|
3
|
+
2021 DHH annouced a great milestone in web development with [Rails 7, "Fulfilling a Vision"](https://rubyonrails.org/2021/12/15/Rails-7-fulfilling-a-vision).
|
4
4
|
|
5
|
-
|
5
|
+
TTo say it up front: If you want to have all the benefits of Turbo without disturbing the handy default Rails workflow, you have to use WebSockets, which are well integrated in Turbo, see [README Turbo::StreamsChannel](https://gitlab.com/sedl/renderturbostream/-/blob/main/README-channels.md). But the starting point is here. And I recommend that you download my [Quick-and-dirty test project](https://gitlab.com/sedl/renderturbostream_railsapp), set it up, see all the tests succeed, and then read through this README.
|
6
|
+
|
7
|
+
Defining templates like `(create|update).turbo_stream.haml` annoyed me. Because it is a heavy mix of logic and view. Working consistently with turbo_stream or Turbo Streams Channel means shooting a lot of partials from the backend to the frontend. This gem checks if a partial has the necessary frames, wraps it, and includes a helper to make instance variables work for Turbo Streams as well. This way, a simple as-usual partial is compatible for withh and without turbo, and can be sent in any way.
|
8
|
+
|
9
|
+
It has a testing strategy!
|
10
|
+
|
11
|
+
A lot of details for handling many different ways of redirect, that are possible and necessary since Turbo, are handled.
|
6
12
|
|
7
13
|
Execute [turbo_power](https://github.com/marcoroth/turbo_power) commands such as adding a css class to an html element, pushing a state to the browser history, or running custom javascript actions through Turbo Stream can be written in pure ruby code. No need for embeds like `.html.erb`.
|
8
14
|
|
@@ -11,10 +17,10 @@ Logic to Ruby, views to views, and the space in between for the gem! And all thi
|
|
11
17
|
An overview of how we design a rails-7 application with turbo
|
12
18
|
is [published on dev.to](https://dev.to/chmich/rails-7-vite-wrapping-up-1pia).
|
13
19
|
|
14
|
-
A quick and dirty application with all the features, including built in tests is [here](https://gitlab.com/sedl/renderturbostream_railsapp).
|
15
|
-
|
16
20
|
Hope it can help you.
|
17
21
|
|
22
|
+
April 2023 first released this gem is in a very early state. I am happy if others contribute for increase the quality of this new kind of working with rails 7.
|
23
|
+
|
18
24
|
**Chris**
|
19
25
|
|
20
26
|
## Installation
|
@@ -49,24 +55,29 @@ Required Configurations for Flash Partial
|
|
49
55
|
|
50
56
|
```ruby
|
51
57
|
config.x.render_turbo_stream.flash_partial = 'layouts/flash'
|
52
|
-
config.x.render_turbo_stream.
|
53
|
-
config.x.render_turbo_stream.
|
54
|
-
config.x.render_turbo_stream.
|
58
|
+
config.x.render_turbo_stream.flash_target_id = 'flash-box'
|
59
|
+
config.x.render_turbo_stream.flash_turbo_action = 'prepend'
|
60
|
+
config.x.render_turbo_stream.allow_channel_to_me_for_turbo_stream_save = true
|
55
61
|
```
|
56
62
|
|
63
|
+
For the latter you have to setup channels, see below.
|
64
|
+
|
57
65
|
The corresponding partials for flashes could look [like this](https://gitlab.com/sedl/renderturbostream/-/wikis/Flashes-example)
|
58
66
|
|
59
67
|
**Translations**
|
60
68
|
|
61
69
|
```
|
62
|
-
|
63
|
-
activerecord
|
64
|
-
|
65
|
-
|
70
|
+
en:
|
71
|
+
activerecord:
|
72
|
+
success:
|
73
|
+
successfully_created: '%<model_name>s successfully created'
|
74
|
+
successfully_updated: '%<model_name>s successfully updated'
|
75
|
+
errors:
|
76
|
+
messages:
|
77
|
+
could_not_create: '%<model_name>s could not be created'
|
78
|
+
could_not_update: '%<model_name>s could not be updated'
|
66
79
|
```
|
67
80
|
|
68
|
-
example value: `"%<model_name>s successfully created"`
|
69
|
-
|
70
81
|
Model name translations, see: Rails Docs.
|
71
82
|
|
72
83
|
**Turbo power**
|
@@ -90,20 +101,19 @@ The Rails team has integrated `ActionCable` as `Turbo::StreamsChannel` into `Tur
|
|
90
101
|
def update
|
91
102
|
turbo_stream_save(
|
92
103
|
@article.update(article_params),
|
93
|
-
|
94
|
-
|
104
|
+
if_success_turbo_redirect_to: articles_path,
|
105
|
+
target_id: 'customer-form'
|
95
106
|
)
|
96
107
|
end
|
97
108
|
```
|
98
|
-
|
99
|
-
This will set a status, generate a flash message and perform `render_turbo_stream`, which would, if update succeeded result in something like this:
|
109
|
+
This will set a status, generate a flash message, and run `render_turbo_stream`. If it fails, it would result in something like this:
|
100
110
|
|
101
111
|
```ruby
|
102
112
|
render_turbo_stream(
|
103
113
|
[
|
104
114
|
{
|
105
115
|
id: 'customer-form',
|
106
|
-
partial: 'customers/
|
116
|
+
partial: 'customers/customer_form'
|
107
117
|
},
|
108
118
|
{
|
109
119
|
id: 'flash-wrapper',
|
@@ -114,6 +124,8 @@ render_turbo_stream(
|
|
114
124
|
)
|
115
125
|
```
|
116
126
|
|
127
|
+
If the update succeeds, it will do a `redirect_to` action from turbo_power
|
128
|
+
|
117
129
|
The `stream_partial` method is for rendering a partial alone.
|
118
130
|
|
119
131
|
```ruby
|
@@ -149,90 +161,35 @@ render_turbo_stream(
|
|
149
161
|
|
150
162
|
Under the hood, inside a `*.turbo_stream.erb` template, it does the following: `= turbo_stream.send args.first, *(args[1..-1])`
|
151
163
|
|
152
|
-
**
|
153
|
-
|
154
|
-
Suppose you have a CRUD controller that should do all its actions inside a turbo frame. Classic redirect_to, together with a turbo_stream action on the same response, would raise «Render and/or redirect were called multiple times in this action». See [issue](https://gitlab.com/sedl/renderturbostream/-/issues/3).
|
155
|
-
|
156
|
-
There are two workarounds:
|
157
|
-
|
158
|
-
```ruby
|
159
|
-
config.x.render_turbo_stream.use_channel_for_turbo_stream_save = true
|
160
|
-
```
|
161
|
-
|
162
|
-
With this config, the `turbo_stream_save` method will send flash messages through `Turbo::StreamsChannel` (if installed as described above) to a channel to the currently logged in user in parallel with the redirect.
|
164
|
+
**Config: allow_channel_to_me_for_turbo_stream_save**
|
163
165
|
|
164
|
-
|
166
|
+
Turbo streams rely on the controller response, which can only be executed once for a request. Other than Turbo::StreamsChannel, they cannot run in parallel with the standard responses like redirect_to.
|
165
167
|
|
166
|
-
|
167
|
-
def update
|
168
|
-
turbo_stream_save(
|
169
|
-
@car.update(car_params),
|
170
|
-
streams_on_success: [
|
171
|
-
[
|
172
|
-
:turbo_frame_set_src,
|
173
|
-
'cars-box',
|
174
|
-
cars_path
|
175
|
-
]
|
176
|
-
]
|
177
|
-
)
|
178
|
-
end
|
179
|
-
|
180
|
-
```
|
181
|
-
|
182
|
-
**Parameters for turbo_stream_save**
|
183
|
-
|
184
|
-
save_action,
|
185
|
-
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 BUT CANNOT STREAM OTHERS ACTIONS ON THE SAME RESPONSE https://github.com/rails/rails/issues/48056
|
186
|
-
turbo_redirect_on_success_to: nil, # does a full page redirect (break out of all frames by turbo_power redirect)
|
187
|
-
object: nil, # object used in save_action, example: @customer
|
188
|
-
id: nil, # if nil: no partial is rendered
|
189
|
-
partial: nil, # example: 'customers/form' default: "#{controller_path}/#{id}"
|
190
|
-
action: 'replace', # options: append, prepend
|
191
|
-
locals: {}, # locals used by the partial
|
192
|
-
streams_on_success: [
|
193
|
-
{
|
194
|
-
id: nil,
|
195
|
-
partial: 'form',
|
196
|
-
locals: {},
|
197
|
-
action: 'replace'
|
198
|
-
}
|
199
|
-
], # additional partials that should be rendered if save_action succeeded
|
200
|
-
streams_on_error: [
|
201
|
-
{
|
202
|
-
id: nil,
|
203
|
-
partial: 'form',
|
204
|
-
locals: {},
|
205
|
-
action: 'replace'
|
206
|
-
}
|
207
|
-
], # additional partials that should be rendered if save_action failed
|
208
|
-
add_flash_alerts: [], #=> array of strings
|
209
|
-
add_flash_notices: [], #=> array of strings
|
210
|
-
flashes_on_success: [], #=> array of strings
|
211
|
-
flashes_on_error: [] #=> array of strings
|
168
|
+
If this config is set to true, Turbo::StreamsChannel is installed and a current user is logged in:
|
212
169
|
|
170
|
+
If an `if_success_redirect_to` argument is provided and the save action was successful, `turbo_stream_save` would send the partials by channel.
|
213
171
|
|
214
172
|
# Testing
|
215
173
|
|
216
|
-
|
217
|
-
|
218
|
-
**request tests cannot test javascript. they only test the ruby side: check if the right content is sent to turbo**.
|
174
|
+
To test if the whole system works together, including javascript actions, so that finally a part reaches the surface, there is **Capybara** system testing. But it is a good practice to **break tests into smaller pieces**. So, there are helpers for **enabling the much faster request tests that are much easier to maintain**.
|
219
175
|
|
220
176
|
If the request format is not `turbo_stream`, which is the case on request specs, the method responds in a special html
|
221
177
|
that contains the medadata that is interesting for our tests and is parsed by included test helpers.
|
222
178
|
|
223
179
|
There is a helper for writing the test: In the debugger, within the test, check the output of `all_turbo_responses`.
|
224
180
|
|
225
|
-
|
181
|
+
**Response Status**
|
226
182
|
|
227
|
-
|
228
|
-
|
229
|
-
If you want to check if a controller action succeeded, just check for `response.status`. The `turbo_stream_save` method returns three statuses: `200` if `save_action` is true, otherwise `422` and `302` for redirect. If one of the declared partials does not exist or breaks, the server will respond with exception anyway.
|
183
|
+
The `turbo_stream_save` method sets three statuses to the response: `200` if `save_action` is true, otherwise `422` and `302` for redirect. If one of the declared partials does not exist or breaks, the server will respond with exception anyway.
|
230
184
|
|
231
185
|
**Redirection**
|
186
|
+
|
187
|
+
If you defined the attribute `if_success_turbo_redirect_to` which uses the redirect_to function from `turbo_power` if installed:
|
188
|
+
|
232
189
|
```ruby
|
233
|
-
|
190
|
+
it 'update success' do
|
234
191
|
patch article_path(article, params: valid_params)
|
235
|
-
|
192
|
+
assert_turbo_redirect_to('/articles')
|
236
193
|
end
|
237
194
|
```
|
238
195
|
|
@@ -242,25 +199,17 @@ end
|
|
242
199
|
expect(turbo_targets.length).to eq(2)
|
243
200
|
# Check the total number of targeted html-ids, in most cases it will be one form and one flash.
|
244
201
|
|
202
|
+
assert_stream_action('turbo_frame_set_src'){ |args| args == ["cars-box", "/cars"] }
|
203
|
+
# if the turbo_power gem is installed
|
204
|
+
|
245
205
|
assert_stream_response('form'){|e|e.css('.field_with_errors').inner_html.include?('title')}
|
246
206
|
# make sure that there is one response to the target '#form' and it checks the rendered content
|
247
207
|
# if cero or more responses are expected, add a attribute like 'count: 0'
|
248
208
|
```
|
249
209
|
|
250
|
-
Possible matchers can be found at [Nokogiri](https://nokogiri.org/tutorials/searching_a_xml_html_document.html).
|
251
|
-
|
252
|
-
The `assert_stream_response`, like the other matchers, checks by default that the same target is not affected multiple times by the `:replace` action within the same response. This is defined in the underlying `RenderTurboStream::Test::Request::Libs.select_responses` method, which is also used for the `assert_channel_to_*` methods.
|
253
|
-
|
254
|
-
```ruby
|
255
|
-
# check actions from turbo_power gem
|
256
|
-
assert_stream_response('colored-element', action: 'remove_css_class') {|args| args.last == 'background-red' }
|
257
|
-
# block (all within {}) is optional
|
258
|
-
```
|
259
|
-
|
260
|
-
**P.S.:**
|
210
|
+
Possible matchers for checking html content can be found at [Nokogiri](https://nokogiri.org/tutorials/searching_a_xml_html_document.html).
|
261
211
|
|
262
|
-
|
263
|
-
includes the plugin and has tests done by rspec/request and capybara.
|
212
|
+
The `assert_stream_response` checks by default that the same target is not affected multiple times by the `:replace` action within the same response. This is defined in the underlying `RenderTurboStream::Test::Request::Libs.select_responses` method, which is also used for the `assert_channel_to_*` methods.
|
264
213
|
|
265
214
|
# More Configs
|
266
215
|
|
@@ -12,12 +12,21 @@
|
|
12
12
|
|
13
13
|
|
14
14
|
<% else %>
|
15
|
-
<% ctl = { partial: args[:partial],
|
16
|
-
<% info = {
|
15
|
+
<% ctl = { partial: args[:partial], target_id: args[:target_id], action: args[:action] } %>
|
16
|
+
<% info = { target_id: args[:target_id], partial: args[:partial], locals: args[:locals] } %>
|
17
|
+
|
18
|
+
|
19
|
+
|
17
20
|
<% if args[:action].present? %>
|
18
21
|
<% Rails.logger.debug(" • render-turbo-stream #{args[:action].upcase} => #{info}") %>
|
19
|
-
<%= turbo_stream.send args[:action].to_sym, args[:
|
20
|
-
|
22
|
+
<%= turbo_stream.send args[:action].to_sym, RenderTurboStream::Libs.target_to_target_id(args[:target]) do %>
|
23
|
+
<% if RenderTurboStream::CheckTemplate.new(partial: args[:partial], action: args[:action]).add_turbo_frame_tag? %>
|
24
|
+
<%= turbo_frame_tag RenderTurboStream::Libs.target_to_target_id(args[:target]) do %>
|
25
|
+
<%= render args[:partial], locals: args[:locals]&.symbolize_keys %>
|
26
|
+
<% end %>
|
27
|
+
<% else %>
|
28
|
+
<%= render args[:partial], locals: args[:locals]&.symbolize_keys %>
|
29
|
+
<% end %>
|
21
30
|
<% end %>
|
22
31
|
|
23
32
|
<% else %>
|
@@ -6,14 +6,22 @@
|
|
6
6
|
<% if s.is_a?(Array) %>
|
7
7
|
<% attr_id = RenderTurboStream::Test::Request::Libs.first_arg_is_html_id(s.first) %>
|
8
8
|
<% h = { array: s, type: 'stream-command', action: s.first } %>
|
9
|
-
<% h[:target] = "##{s.second[0]=='#' ? s.second[1..-1] : s.second}" if attr_id %>
|
9
|
+
<% h[:target] = "##{s.second[0] == '#' ? s.second[1..-1] : s.second}" if attr_id %>
|
10
10
|
<% rendered_partials.push(h) %>
|
11
11
|
<% else %>
|
12
|
-
|
13
|
-
<%
|
14
|
-
|
12
|
+
|
13
|
+
<% wrap = RenderTurboStream::CheckTemplate.new(partial: s[:partial], action: s[:action]).add_turbo_frame_tag? %>
|
14
|
+
|
15
|
+
<% if wrap %>
|
16
|
+
<% html = turbo_frame_tag RenderTurboStream::Libs.target_to_target_id(s[:target]) do %>
|
17
|
+
<%= (render s[:partial], locals: s[:locals]&.symbolize_keys, formats: [:html]) %>
|
18
|
+
<% end %>
|
19
|
+
<% else %>
|
20
|
+
<% html = (render s[:partial], locals: s[:locals]&.symbolize_keys, formats: [:html]) %>
|
21
|
+
<% end %>
|
22
|
+
<% s[:target] = s[:target] %>
|
15
23
|
<% s.delete(:id) %>
|
16
|
-
<% rendered_partials.push({ html_response: html, type: 'partial' }.merge(s)) %>
|
24
|
+
<% rendered_partials.push({ html_response: html, type: 'stream-partial' }.merge(s)) %>
|
17
25
|
<% end %>
|
18
26
|
|
19
27
|
|
@@ -1,16 +1,48 @@
|
|
1
1
|
module RenderTurboStream
|
2
2
|
class ChannelLibs
|
3
3
|
|
4
|
-
def
|
4
|
+
def initialize(response)
|
5
|
+
@response = response
|
6
|
+
end
|
7
|
+
|
8
|
+
def render_to_channel(channel, target, action, controller_self, partial: nil, template: nil, locals: nil)
|
9
|
+
|
10
|
+
# instance variables
|
11
|
+
|
12
|
+
target_id = RenderTurboStream::Libs.target_to_target_id(target)
|
13
|
+
check_template = RenderTurboStream::CheckTemplate.new(
|
14
|
+
partial: partial,
|
15
|
+
template: template,
|
16
|
+
available_instance_variables: controller_self.instance_variables,
|
17
|
+
action: action
|
18
|
+
)
|
19
|
+
|
20
|
+
# add instance_variables to locals
|
5
21
|
|
6
22
|
locals = {} unless locals # prevent error missing keys for nil
|
23
|
+
if check_template.add_turbo_frame_tag?
|
24
|
+
_layout = 'layouts/add_turbo_frame_tag'
|
25
|
+
_locals = locals.merge(
|
26
|
+
{
|
27
|
+
render_turbo_stream_partial: partial,
|
28
|
+
render_turbo_stream_target_id: target_id
|
29
|
+
}
|
30
|
+
)
|
31
|
+
else
|
32
|
+
_locals = locals
|
33
|
+
_layout = false
|
34
|
+
end
|
35
|
+
check_template.templates_instance_variables.each do |v|
|
36
|
+
_locals[:"render_turbo_stream_instance_variable_#{v}"] = controller_self.instance_variable_get(v.to_s)
|
37
|
+
end
|
7
38
|
|
8
39
|
# add headers for test
|
40
|
+
|
9
41
|
if Rails.env.test?
|
10
|
-
html = RenderTurboStreamRenderController.render(partial: partial, locals:
|
11
|
-
html = RenderTurboStreamRenderController.render(template: template, locals:
|
42
|
+
html = RenderTurboStreamRenderController.render(partial: partial, locals: _locals, layout: _layout) if partial
|
43
|
+
html = RenderTurboStreamRenderController.render(template: template, locals: _locals, layout: _layout) if template
|
12
44
|
props = {
|
13
|
-
target:
|
45
|
+
target: target,
|
14
46
|
action: action,
|
15
47
|
type: 'channel-partial',
|
16
48
|
locals: locals,
|
@@ -20,12 +52,12 @@ module RenderTurboStream
|
|
20
52
|
|
21
53
|
props[:partial] = partial if partial
|
22
54
|
props[:template] = template if template
|
23
|
-
h = response.headers.to_h
|
55
|
+
h = @response.headers.to_h
|
24
56
|
i = 1
|
25
57
|
loop do
|
26
58
|
k = "test-turbo-channel-#{i}"
|
27
59
|
unless h.keys.include?(k)
|
28
|
-
response.headers[k] = props.to_json
|
60
|
+
@response.headers[k] = props.to_json
|
29
61
|
break
|
30
62
|
end
|
31
63
|
i += 1
|
@@ -40,8 +72,8 @@ module RenderTurboStream
|
|
40
72
|
channel.to_s,
|
41
73
|
target: target_id,
|
42
74
|
partial: partial,
|
43
|
-
locals:
|
44
|
-
layout:
|
75
|
+
locals: _locals&.symbolize_keys,
|
76
|
+
layout: _layout
|
45
77
|
)
|
46
78
|
elsif template
|
47
79
|
Turbo::StreamsChannel.send(
|
@@ -50,12 +82,12 @@ module RenderTurboStream
|
|
50
82
|
target: target_id,
|
51
83
|
template: template,
|
52
84
|
locals: locals&.symbolize_keys,
|
53
|
-
layout:
|
85
|
+
layout: _layout
|
54
86
|
)
|
55
87
|
end
|
56
88
|
end
|
57
89
|
|
58
|
-
def
|
90
|
+
def action_to_channel(channel, command, arguments)
|
59
91
|
|
60
92
|
if Rails.env.test?
|
61
93
|
|
@@ -67,15 +99,15 @@ module RenderTurboStream
|
|
67
99
|
}
|
68
100
|
if RenderTurboStream::Test::Request::Libs.first_arg_is_html_id(command)
|
69
101
|
target_id = (arguments.first[0..0] == '#' ? arguments.first[1..-1] : arguments.first)
|
70
|
-
props[:target] =
|
102
|
+
props[:target] = RenderTurboStream::Libs.target_id_to_target(target_id)
|
71
103
|
end
|
72
|
-
|
73
|
-
h = response.headers.to_h
|
104
|
+
|
105
|
+
h = @response.headers.to_h
|
74
106
|
i = 1
|
75
107
|
loop do
|
76
108
|
k = "test-turbo-channel-#{i}"
|
77
109
|
unless h.keys.include?(k)
|
78
|
-
response.headers[k] = props.to_json
|
110
|
+
@response.headers[k] = props.to_json
|
79
111
|
break
|
80
112
|
end
|
81
113
|
i += 1
|
@@ -90,5 +122,17 @@ module RenderTurboStream
|
|
90
122
|
)
|
91
123
|
end
|
92
124
|
|
125
|
+
def send_actions_to_channel(channel, actions, instance_variables)
|
126
|
+
actions.each do |a|
|
127
|
+
if a.is_a?(Array)
|
128
|
+
action_to_channel(channel, a.first, a[1..-1])
|
129
|
+
else
|
130
|
+
render_to_channel(channel, a[:target], a[:action], instance_variables, partial: a[:partial], template: a[:template], locals: a[:locals])
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def self.fetch_partials_variables(relative_path) end
|
136
|
+
|
93
137
|
end
|
94
138
|
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module RenderTurboStream
|
2
|
+
class CheckTemplate
|
3
|
+
|
4
|
+
AUTO_ADD_TURBO_FRAME_TAG_ON_ACTION = [:replace]
|
5
|
+
|
6
|
+
def initialize(partial: nil, template: nil, available_instance_variables: nil, action: nil)
|
7
|
+
if !partial && !template
|
8
|
+
raise 'missing attribute partial xor template'
|
9
|
+
end
|
10
|
+
unless action
|
11
|
+
raise 'missing required attribute: action'
|
12
|
+
end
|
13
|
+
@action = action
|
14
|
+
@partial_path = partial
|
15
|
+
@template_path = template
|
16
|
+
@available_instance_variables = available_instance_variables
|
17
|
+
prt = (partial ? partial : template).split('/')
|
18
|
+
if prt.length < 2
|
19
|
+
raise 'Partial or template path must always be specified with the controller path, for example «articles/partial_name».'
|
20
|
+
end
|
21
|
+
@controller_path = prt[0..-2].join('/')
|
22
|
+
@key = relative_path(partial: partial, template: template)
|
23
|
+
if production?
|
24
|
+
$render_turbo_stream_check_templates ||= {}
|
25
|
+
if $render_turbo_stream_check_templates[@key]
|
26
|
+
@result = $render_turbo_stream_check_templates[@key]
|
27
|
+
else
|
28
|
+
@result = { instance_variables: [], add_turbo_frame_tag: false }
|
29
|
+
fetch_nested_partials(1, partial: @partial_path, template: @template_path)
|
30
|
+
$render_turbo_stream_check_templates[@key] ||= @result
|
31
|
+
end
|
32
|
+
else
|
33
|
+
@result = { instance_variables: [], add_turbo_frame_tag: false }
|
34
|
+
fetch_nested_partials(1, partial: @partial_path, template: @template_path)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def templates_instance_variables
|
39
|
+
@result[:instance_variables]
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_turbo_frame_tag?
|
43
|
+
@result[:add_turbo_frame_tag]
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def production?
|
49
|
+
# in production this config should be set to true
|
50
|
+
!Rails.application.config.assets.compile
|
51
|
+
end
|
52
|
+
|
53
|
+
def fetch_nested_partials(stack_level, partial: nil, template: nil)
|
54
|
+
|
55
|
+
@nested_partials_done ||= []
|
56
|
+
if @nested_partials_done.include?(partial || template)
|
57
|
+
return # prevent endless loops
|
58
|
+
end
|
59
|
+
@nested_partials_done.push(partial || template)
|
60
|
+
absolute_path = absolute_path(partial: partial, template: template)
|
61
|
+
code = File.read(absolute_path)
|
62
|
+
|
63
|
+
_code = code.dup
|
64
|
+
_code.scan(/@[a-z_]+/).each do |var|
|
65
|
+
if !@available_instance_variables || (@available_instance_variables && @available_instance_variables.include?(var.to_sym))
|
66
|
+
unless @result[:instance_variables].include?(var.to_sym)
|
67
|
+
@result[:instance_variables].push(var.to_sym)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
if stack_level == 1 && AUTO_ADD_TURBO_FRAME_TAG_ON_ACTION.include?(@action.to_sym)
|
73
|
+
unless _code.match(/(turbo_frame_tag|turbo-frame)/)
|
74
|
+
@result[:add_turbo_frame_tag] = true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
loop do
|
79
|
+
_matched_partial = _code.match((/\brender[ ](partial:|())(|[ ])("|')[a-z_\/]+("|')/))
|
80
|
+
_code = _code[(_code.index(_matched_partial.to_s) + 5)..-1]
|
81
|
+
if _matched_partial
|
82
|
+
|
83
|
+
found_partial = _matched_partial.to_s.split(/("|')/)[-2]
|
84
|
+
absolute_path = absolute_path(partial: found_partial)
|
85
|
+
|
86
|
+
@nested_partials ||= []
|
87
|
+
@nested_partials.push(absolute_path)
|
88
|
+
fetch_nested_partials(stack_level += 1, partial: found_partial)
|
89
|
+
|
90
|
+
else
|
91
|
+
break
|
92
|
+
end
|
93
|
+
end
|
94
|
+
renders = code.match((/\brender[ ](partial:|())(|[ ])("|')[a-z_\/]+("|')/)) # scan function didnt work here, so built a loop
|
95
|
+
end
|
96
|
+
|
97
|
+
def absolute_path(partial: nil, template: nil)
|
98
|
+
divided = relative_path(partial: partial, template: template).split('/')
|
99
|
+
view_folder = divided[0..-2].join('/')
|
100
|
+
folder = Rails.root.join('app', 'views', view_folder)
|
101
|
+
items = Dir.glob "#{folder.to_s}/*"
|
102
|
+
item = items.select { |i| i.split('/').last.match(/^#{divided.last}/) }.first
|
103
|
+
unless item.present?
|
104
|
+
raise "#{partial ? 'Partial' : 'Template'} not found => #{relative_path(partial: partial, template: template)}"
|
105
|
+
end
|
106
|
+
item
|
107
|
+
end
|
108
|
+
|
109
|
+
def relative_path(partial: nil, template: nil)
|
110
|
+
divided = (partial ? partial : template).split('/')
|
111
|
+
if divided.length <= 1
|
112
|
+
_divided = [@controller_path] + divided
|
113
|
+
else
|
114
|
+
_divided = divided
|
115
|
+
end
|
116
|
+
view_folder = _divided[0..-2].join('/')
|
117
|
+
file = "#{partial ? '_' : ''}#{divided.last}"
|
118
|
+
[view_folder, file].join('/')
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|