render_turbo_stream 3.0.5 → 4.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +37 -95
- 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: caa22ab0fcd032899bc52bb3e92bc576e26bea6bb187acbe0e00b3162e78c054
|
4
|
+
data.tar.gz: 7314b1bb2199fb89fc83df166c5a02749c82b291ab0210ababbf1afffedfb72a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a99d072daf0c5cfb3955790ba17aa63ddbabdd7332ced553f06abb988ad5150329f1a787da4d03b58d34b088398d6ec64b3774fde34b429f46837e13ac11418a
|
7
|
+
data.tar.gz: 6d16c9a72c015961e6597847d664806871f550088b0a54f8a4e4f0c0df186844de4ca1838159fcfe8243f76de0c8ca53fa0513aa688c7e091db5d648491b4d8b
|
data/README.md
CHANGED
@@ -1,19 +1,23 @@
|
|
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
|
+
To say it up front: If you want to have all the benefits of Turbo without disturbing the handy default Rails workflow, you must use WebSockets sometimes, 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
6
|
|
7
|
-
|
7
|
+
Defining templates like `(create|update).turbo_stream.haml` is a heavy mix of logic and view. This gem separates logic and view so that `*.turbo_stream.*` templates are no longer necessary and the logic can stay on the ruby side.
|
8
8
|
|
9
|
-
|
9
|
+
For `:replace` actions, responses must be wrapped inside a `turbo_frame_tag` with a matching `target_id`. The gem will regex the content and wrap it by a `turbo_frame_tag` if necessary but not present. This way the matching `target_id` is only defined in one place. This check only happens on the first call after restarting the application in production and on every call if precompile assets is set to true in configs.
|
10
|
+
|
11
|
+
There are many different ways to handle **redirects** since turbo. The gem brings this into a workflow.
|
12
|
+
|
13
|
+
Execute [turbo_power](https://github.com/marcoroth/turbo_power) commands such as adding a css class to an html element, can be sent directly from the controller.
|
14
|
+
|
15
|
+
Has a testing strategy.
|
10
16
|
|
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
|
-
|
15
|
-
|
16
|
-
Hope it can help you.
|
20
|
+
I am happy if it can help.
|
17
21
|
|
18
22
|
**Chris**
|
19
23
|
|
@@ -49,9 +53,9 @@ Required Configurations for Flash Partial
|
|
49
53
|
|
50
54
|
```ruby
|
51
55
|
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.
|
56
|
+
config.x.render_turbo_stream.flash_target_id = 'flash-box'
|
57
|
+
config.x.render_turbo_stream.flash_turbo_action = 'prepend'
|
58
|
+
config.x.render_turbo_stream.allow_channel_to_me_for_turbo_stream_save = true
|
55
59
|
```
|
56
60
|
|
57
61
|
For the latter you have to setup channels, see below.
|
@@ -95,20 +99,19 @@ The Rails team has integrated `ActionCable` as `Turbo::StreamsChannel` into `Tur
|
|
95
99
|
def update
|
96
100
|
turbo_stream_save(
|
97
101
|
@article.update(article_params),
|
98
|
-
|
99
|
-
|
102
|
+
if_success_turbo_redirect_to: articles_path,
|
103
|
+
target_id: 'customer-form'
|
100
104
|
)
|
101
105
|
end
|
102
106
|
```
|
103
|
-
|
104
|
-
This will set a status, generate a flash message and perform `render_turbo_stream`, which would, if update succeeded result in something like this:
|
107
|
+
This will set a status, generate a flash message, and run `render_turbo_stream`. If it fails, it would result in something like this:
|
105
108
|
|
106
109
|
```ruby
|
107
110
|
render_turbo_stream(
|
108
111
|
[
|
109
112
|
{
|
110
113
|
id: 'customer-form',
|
111
|
-
partial: 'customers/
|
114
|
+
partial: 'customers/customer_form'
|
112
115
|
},
|
113
116
|
{
|
114
117
|
id: 'flash-wrapper',
|
@@ -119,6 +122,8 @@ render_turbo_stream(
|
|
119
122
|
)
|
120
123
|
```
|
121
124
|
|
125
|
+
If the update succeeds, it will do a `redirect_to` action from turbo_power
|
126
|
+
|
122
127
|
The `stream_partial` method is for rendering a partial alone.
|
123
128
|
|
124
129
|
```ruby
|
@@ -154,90 +159,35 @@ render_turbo_stream(
|
|
154
159
|
|
155
160
|
Under the hood, inside a `*.turbo_stream.erb` template, it does the following: `= turbo_stream.send args.first, *(args[1..-1])`
|
156
161
|
|
157
|
-
**
|
158
|
-
|
159
|
-
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).
|
162
|
+
**Config: allow_channel_to_me_for_turbo_stream_save**
|
160
163
|
|
161
|
-
|
164
|
+
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.
|
162
165
|
|
163
|
-
|
164
|
-
config.x.render_turbo_stream.allow_channel_for_turbo_stream_save = true
|
165
|
-
```
|
166
|
-
|
167
|
-
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.
|
168
|
-
|
169
|
-
Another workaround is to use the src attribute of the parent frame. `turbo_frame_set_src` comes from turbo_power. The code might look like this:
|
170
|
-
|
171
|
-
```ruby
|
172
|
-
def update
|
173
|
-
turbo_stream_save(
|
174
|
-
@car.update(car_params),
|
175
|
-
streams_on_success: [
|
176
|
-
[
|
177
|
-
:turbo_frame_set_src,
|
178
|
-
'cars-box',
|
179
|
-
cars_path
|
180
|
-
]
|
181
|
-
]
|
182
|
-
)
|
183
|
-
end
|
184
|
-
|
185
|
-
```
|
186
|
-
|
187
|
-
**Parameters for turbo_stream_save**
|
188
|
-
|
189
|
-
save_action,
|
190
|
-
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
|
191
|
-
turbo_redirect_on_success_to: nil, # does a full page redirect (break out of all frames by turbo_power redirect)
|
192
|
-
object: nil, # object used in save_action, example: @customer
|
193
|
-
id: nil, # if nil: no partial is rendered
|
194
|
-
partial: nil, # example: 'customers/form' default: "#{controller_path}/#{id}"
|
195
|
-
action: 'replace', # options: append, prepend
|
196
|
-
locals: {}, # locals used by the partial
|
197
|
-
streams_on_success: [
|
198
|
-
{
|
199
|
-
id: nil,
|
200
|
-
partial: 'form',
|
201
|
-
locals: {},
|
202
|
-
action: 'replace'
|
203
|
-
}
|
204
|
-
], # additional partials that should be rendered if save_action succeeded
|
205
|
-
streams_on_error: [
|
206
|
-
{
|
207
|
-
id: nil,
|
208
|
-
partial: 'form',
|
209
|
-
locals: {},
|
210
|
-
action: 'replace'
|
211
|
-
}
|
212
|
-
], # additional partials that should be rendered if save_action failed
|
213
|
-
add_flash_alerts: [], #=> array of strings
|
214
|
-
add_flash_notices: [], #=> array of strings
|
215
|
-
flashes_on_success: [], #=> array of strings
|
216
|
-
flashes_on_error: [] #=> array of strings
|
166
|
+
If this config is set to true, Turbo::StreamsChannel is installed and a current user is logged in:
|
217
167
|
|
168
|
+
If an `if_success_redirect_to` argument is provided and the save action was successful, `turbo_stream_save` would send the partials by channel.
|
218
169
|
|
219
170
|
# Testing
|
220
171
|
|
221
|
-
|
222
|
-
|
223
|
-
**request tests cannot test javascript. they only test the ruby side: check if the right content is sent to turbo**.
|
172
|
+
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**.
|
224
173
|
|
225
174
|
If the request format is not `turbo_stream`, which is the case on request specs, the method responds in a special html
|
226
175
|
that contains the medadata that is interesting for our tests and is parsed by included test helpers.
|
227
176
|
|
228
177
|
There is a helper for writing the test: In the debugger, within the test, check the output of `all_turbo_responses`.
|
229
178
|
|
230
|
-
|
179
|
+
**Response Status**
|
231
180
|
|
232
|
-
|
233
|
-
|
234
|
-
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.
|
181
|
+
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.
|
235
182
|
|
236
183
|
**Redirection**
|
184
|
+
|
185
|
+
If you defined the attribute `if_success_turbo_redirect_to` which uses the redirect_to function from `turbo_power` if installed:
|
186
|
+
|
237
187
|
```ruby
|
238
|
-
|
188
|
+
it 'update success' do
|
239
189
|
patch article_path(article, params: valid_params)
|
240
|
-
|
190
|
+
assert_turbo_redirect_to('/articles')
|
241
191
|
end
|
242
192
|
```
|
243
193
|
|
@@ -247,25 +197,17 @@ end
|
|
247
197
|
expect(turbo_targets.length).to eq(2)
|
248
198
|
# Check the total number of targeted html-ids, in most cases it will be one form and one flash.
|
249
199
|
|
200
|
+
assert_stream_action('turbo_frame_set_src'){ |args| args == ["cars-box", "/cars"] }
|
201
|
+
# if the turbo_power gem is installed
|
202
|
+
|
250
203
|
assert_stream_response('form'){|e|e.css('.field_with_errors').inner_html.include?('title')}
|
251
204
|
# make sure that there is one response to the target '#form' and it checks the rendered content
|
252
205
|
# if cero or more responses are expected, add a attribute like 'count: 0'
|
253
206
|
```
|
254
207
|
|
255
|
-
Possible matchers can be found at [Nokogiri](https://nokogiri.org/tutorials/searching_a_xml_html_document.html).
|
256
|
-
|
257
|
-
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.
|
258
|
-
|
259
|
-
```ruby
|
260
|
-
# check actions from turbo_power gem
|
261
|
-
assert_stream_response('colored-element', action: 'remove_css_class') {|args| args.last == 'background-red' }
|
262
|
-
# block (all within {}) is optional
|
263
|
-
```
|
264
|
-
|
265
|
-
**P.S.:**
|
208
|
+
Possible matchers for checking html content can be found at [Nokogiri](https://nokogiri.org/tutorials/searching_a_xml_html_document.html).
|
266
209
|
|
267
|
-
|
268
|
-
includes the plugin and has tests done by rspec/request and capybara.
|
210
|
+
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.
|
269
211
|
|
270
212
|
# More Configs
|
271
213
|
|
@@ -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 = nil #available_instance_variables #=> because if an instance variable was not set on production and the first call, and it was set on a later call, the gem would not know that and not set it.
|
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
|