render_turbo_stream 3.0.5 → 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 +36 -92
- 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,9 +55,9 @@ 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
|
|
57
63
|
For the latter you have to setup channels, see below.
|
@@ -95,20 +101,19 @@ The Rails team has integrated `ActionCable` as `Turbo::StreamsChannel` into `Tur
|
|
95
101
|
def update
|
96
102
|
turbo_stream_save(
|
97
103
|
@article.update(article_params),
|
98
|
-
|
99
|
-
|
104
|
+
if_success_turbo_redirect_to: articles_path,
|
105
|
+
target_id: 'customer-form'
|
100
106
|
)
|
101
107
|
end
|
102
108
|
```
|
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:
|
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:
|
105
110
|
|
106
111
|
```ruby
|
107
112
|
render_turbo_stream(
|
108
113
|
[
|
109
114
|
{
|
110
115
|
id: 'customer-form',
|
111
|
-
partial: 'customers/
|
116
|
+
partial: 'customers/customer_form'
|
112
117
|
},
|
113
118
|
{
|
114
119
|
id: 'flash-wrapper',
|
@@ -119,6 +124,8 @@ render_turbo_stream(
|
|
119
124
|
)
|
120
125
|
```
|
121
126
|
|
127
|
+
If the update succeeds, it will do a `redirect_to` action from turbo_power
|
128
|
+
|
122
129
|
The `stream_partial` method is for rendering a partial alone.
|
123
130
|
|
124
131
|
```ruby
|
@@ -154,90 +161,35 @@ render_turbo_stream(
|
|
154
161
|
|
155
162
|
Under the hood, inside a `*.turbo_stream.erb` template, it does the following: `= turbo_stream.send args.first, *(args[1..-1])`
|
156
163
|
|
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).
|
160
|
-
|
161
|
-
There are two workarounds:
|
162
|
-
|
163
|
-
```ruby
|
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:
|
164
|
+
**Config: allow_channel_to_me_for_turbo_stream_save**
|
170
165
|
|
171
|
-
|
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
|
-
```
|
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.
|
186
167
|
|
187
|
-
|
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
|
168
|
+
If this config is set to true, Turbo::StreamsChannel is installed and a current user is logged in:
|
217
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.
|
218
171
|
|
219
172
|
# Testing
|
220
173
|
|
221
|
-
|
222
|
-
|
223
|
-
**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**.
|
224
175
|
|
225
176
|
If the request format is not `turbo_stream`, which is the case on request specs, the method responds in a special html
|
226
177
|
that contains the medadata that is interesting for our tests and is parsed by included test helpers.
|
227
178
|
|
228
179
|
There is a helper for writing the test: In the debugger, within the test, check the output of `all_turbo_responses`.
|
229
180
|
|
230
|
-
|
231
|
-
|
232
|
-
**The fastest**
|
181
|
+
**Response Status**
|
233
182
|
|
234
|
-
|
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.
|
235
184
|
|
236
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
|
+
|
237
189
|
```ruby
|
238
|
-
|
190
|
+
it 'update success' do
|
239
191
|
patch article_path(article, params: valid_params)
|
240
|
-
|
192
|
+
assert_turbo_redirect_to('/articles')
|
241
193
|
end
|
242
194
|
```
|
243
195
|
|
@@ -247,25 +199,17 @@ end
|
|
247
199
|
expect(turbo_targets.length).to eq(2)
|
248
200
|
# Check the total number of targeted html-ids, in most cases it will be one form and one flash.
|
249
201
|
|
202
|
+
assert_stream_action('turbo_frame_set_src'){ |args| args == ["cars-box", "/cars"] }
|
203
|
+
# if the turbo_power gem is installed
|
204
|
+
|
250
205
|
assert_stream_response('form'){|e|e.css('.field_with_errors').inner_html.include?('title')}
|
251
206
|
# make sure that there is one response to the target '#form' and it checks the rendered content
|
252
207
|
# if cero or more responses are expected, add a attribute like 'count: 0'
|
253
208
|
```
|
254
209
|
|
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.:**
|
210
|
+
Possible matchers for checking html content can be found at [Nokogiri](https://nokogiri.org/tutorials/searching_a_xml_html_document.html).
|
266
211
|
|
267
|
-
|
268
|
-
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.
|
269
213
|
|
270
214
|
# More Configs
|
271
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
|