render_turbo_stream 3.0.5 → 4.0.0

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: 525e9a802269d71e60be6cbc44fe62ebbe677a89fd49e048b1dbc11c6ae4d2b0
4
+ data.tar.gz: 5b47c7b9a7dcc49491e4ab20eb2637654b62e971afe0abdf481ec9210e97f145
5
5
  SHA512:
6
- metadata.gz: 5983031570c67f03c85ae84341d51bfd6cbb76f2f1e5b1543f2434cc6d133edbcdbd25205c6162b55bdacaa7531c79a411f397c65695598b76096e1395c54182
7
- data.tar.gz: e672f2427971215dada8b1b3b6047a6331e0c0e39f7c1b5166f2fe2992e35cbc03dc94fb7d39abcc2f1091d20f650d3b0ad6b2720fa4f66b4476810b2865785f
6
+ metadata.gz: c532c3c9a5307547f260ff1feefea8e43b3cb09e42c43eacad1771c8a72b16a9843703024e1a58891a0498592c53dd296f2a6d7ef0dac72554c838c7c42d3915
7
+ data.tar.gz: f1c5cf8193d9c3214d60c492d73e7ebaf844b848dfa88a602ee235af9c0fbe90d757584938cf3b58d661d507b119791d4fe544968580654406c63abdfe4c1864
data/README.md CHANGED
@@ -1,8 +1,14 @@
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
+ 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.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
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
- turbo_redirect_on_success_to: (params['no_redirection'] == 'true' ? nil : articles_path),
99
- id: 'form'
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/form'
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
- **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).
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
- ```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
- ```
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
- **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
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
- 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**.
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
- Test scenarios are:
231
-
232
- **The fastest**
181
+ **Response Status**
233
182
 
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.
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
- it 'update success' do
190
+ it 'update success' do
239
191
  patch article_path(article, params: valid_params)
240
- expect(turbo_redirect_to).to eq(articles_path)
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
- 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.
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
 
@@ -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 = 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