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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c984de11f7923f03531f0b53b0a4224bd3c56520f75bc4e92e16292c9e2a9e86
4
- data.tar.gz: 184129722f704f61599a022f3ef6bb7306c3b7d1f74a5770a666cac9835e5839
3
+ metadata.gz: caa22ab0fcd032899bc52bb3e92bc576e26bea6bb187acbe0e00b3162e78c054
4
+ data.tar.gz: 7314b1bb2199fb89fc83df166c5a02749c82b291ab0210ababbf1afffedfb72a
5
5
  SHA512:
6
- metadata.gz: 5983031570c67f03c85ae84341d51bfd6cbb76f2f1e5b1543f2434cc6d133edbcdbd25205c6162b55bdacaa7531c79a411f397c65695598b76096e1395c54182
7
- data.tar.gz: e672f2427971215dada8b1b3b6047a6331e0c0e39f7c1b5166f2fe2992e35cbc03dc94fb7d39abcc2f1091d20f650d3b0ad6b2720fa4f66b4476810b2865785f
6
+ metadata.gz: a99d072daf0c5cfb3955790ba17aa63ddbabdd7332ced553f06abb988ad5150329f1a787da4d03b58d34b088398d6ec64b3774fde34b429f46837e13ac11418a
7
+ data.tar.gz: 6d16c9a72c015961e6597847d664806871f550088b0a54f8a4e4f0c0df186844de4ca1838159fcfe8243f76de0c8ca53fa0513aa688c7e091db5d648491b4d8b
data/README.md CHANGED
@@ -1,19 +1,23 @@
1
1
  # RenderTurboStream
2
2
 
3
- DHH made a milestone in web development with [Rails 7, "Fulfilling a Vision"](https://rubyonrails.org/2021/12/15/Rails-7-fulfilling-a-vision). This gem is my contribution to that bold new step. The goal is to have a complete rails like workflow.
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
- 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. And there are many ways and tedious details to handle redirects since Turbo! This can all be streamlined.
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
- 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`.
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
- Logic to Ruby, views to views, and the space in between for the gem! And all this together with a testing strategy for the Ruby side.
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
- A quick and dirty application with all the features, including built in tests is [here](https://gitlab.com/sedl/renderturbostream_railsapp).
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.flash_id = 'flash-box'
53
- config.x.render_turbo_stream.flash_action = 'prepend'
54
- config.x.render_turbo_stream.allow_channel_for_turbo_stream_save = true
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
- turbo_redirect_on_success_to: (params['no_redirection'] == 'true' ? nil : articles_path),
99
- id: 'form'
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/form'
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
- **WORKAROUNDS for redirects inside frame**
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
- There are two workarounds:
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
- ```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:
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
- For system testing there is Capybara. Its the only way to check if frontend and backend work together. But its a good practice to break tests into smaller pieces. The much larger number of tests we will write on the much faster request tests, examples here in rspec.
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
- Test scenarios are:
179
+ **Response Status**
231
180
 
232
- **The fastest**
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
- it 'update success' do
188
+ it 'update success' do
239
189
  patch article_path(article, params: valid_params)
240
- expect(turbo_redirect_to).to eq(articles_path)
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
- Testing the plugin itself: There is a [quick-and-dirty app](https://gitlab.com/sedl/renderturbostream_railsapp) which
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
 
@@ -0,0 +1,6 @@
1
+ <%= turbo_frame_tag local_assigns[:render_turbo_stream_target_id] do %>
2
+ <%# locs = { value: 'hi' } %>
3
+ <%# locs2 = local_assigns.to_h %>
4
+ <%#= render local_assigns[:render_turbo_stream_partial], locals: { article: 'hi' } %>
5
+ <%= yield %>
6
+ <% end %>
@@ -12,12 +12,21 @@
12
12
 
13
13
 
14
14
  <% else %>
15
- <% ctl = { partial: args[:partial], id: args[:id], action: args[:action] } %>
16
- <% info = { id: args[:id], partial: args[:partial], locals: args[:locals] } %>
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[:id] do %>
20
- <%= render args[:partial], locals: args[:locals]&.symbolize_keys %>
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
- <% html = (render s[:partial], locals: s[:locals]&.symbolize_keys, formats: [:html]) %>
13
- <% s.delete(:partial) %>
14
- <% s[:target] = "##{s[:id]}" %>
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 self.render_to_channel(response, channel, target_id, action, partial: nil, template: nil, locals: nil)
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: locals) if partial
11
- html = RenderTurboStreamRenderController.render(template: template, locals: locals) if template
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: "##{target_id}",
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: locals&.symbolize_keys,
44
- layout: false
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: false
85
+ layout: _layout
54
86
  )
55
87
  end
56
88
  end
57
89
 
58
- def self.action_to_channel(response, channel, command, arguments)
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] = "##{target_id}"
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
@@ -32,5 +32,13 @@ module RenderTurboStream
32
32
  end
33
33
  end
34
34
 
35
+ def fetch_instance_variables(locals)
36
+ locals.each do |k,v|
37
+ if k[0..38] == 'render_turbo_stream_instance_variable_@'
38
+ eval "#{k[38..-1]} = v"
39
+ end
40
+ end
41
+ end
42
+
35
43
  end
36
44
  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