render_turbo_stream 2.1.2 → 3.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.
@@ -1,335 +1,15 @@
1
1
  require "render_turbo_stream/version"
2
2
  require "render_turbo_stream/railtie"
3
3
  require 'render_turbo_stream/engine'
4
- require 'render_turbo_stream/test/request_helpers'
5
- require 'render_turbo_stream/test/rspec_request_helpers'
6
- require 'render_turbo_stream/turbo_cable_helpers'
7
- require 'render_turbo_stream/turbo_cable_view_helpers'
8
4
 
9
- module RenderTurboStream
5
+ require 'render_turbo_stream/test/request/channel_helpers'
6
+ require 'render_turbo_stream/test/request/helpers'
7
+ require 'render_turbo_stream/test/request/libs'
10
8
 
11
- def turbo_stream_save(
12
- save_action,
13
- 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
14
- turbo_redirect_on_success_to: nil, # does a full page redirect (break out of all frames by turbo_power redirect)
15
- object: nil, # object used in save_action, example: @customer
16
- id: nil, # if nil: no partial is rendered
17
- partial: nil, # example: 'customers/form' default: "#{controller_path}/#{id}"
18
- action: 'replace', # options: append, prepend
19
- flash_action: action_name, # options: 'update', 'create', otherwise you have to declare a translation in config/locales like "activerecord.success.#{flash_action}" and "activerecord.errors.#{flash_action}"
20
- locals: {}, # locals used by the partial
21
- streams_on_success: [
22
- {
23
- id: nil,
24
- partial: 'form',
25
- locals: {},
26
- action: 'replace'
27
- }
28
- ], # additional partials that should be rendered if save_action succeeded
29
- streams_on_error: [
30
- {
31
- id: nil,
32
- partial: 'form',
33
- locals: {},
34
- action: 'replace'
35
- }
36
- ], # additional partials that should be rendered if save_action failed
37
- add_flash_alerts: [], #=> array of strings
38
- add_flash_notices: [], #=> array of strings
39
- flashes_on_success: [], #=> array of strings
40
- flashes_on_error: [] #=> array of strings
41
- )
9
+ require 'render_turbo_stream/controller_helpers'
10
+ require 'render_turbo_stream/controller_channel_helpers'
42
11
 
43
- unless object
44
- object = eval("@#{controller_name.classify.underscore}")
45
- end
12
+ require 'render_turbo_stream/channel_view_helpers'
46
13
 
47
- #== Streams / Partials
14
+ require 'render_turbo_stream/channel_libs'
48
15
 
49
- streams = (id ? [id: id, partial: partial, locals: locals, action: action] : [])
50
-
51
- if save_action
52
- response.status = 200
53
- streams_on_success.each do |s|
54
- if s.is_a?(Array)
55
- streams.push(s)
56
- elsif s.is_a?(Hash) && s[:id].present?
57
- streams.push(s)
58
- end
59
- end
60
- else
61
- response.status = :unprocessable_entity
62
- streams += streams_on_error.select { |s| s[:id].present? }
63
- end
64
-
65
- #== FLASHES
66
-
67
- model_name = object.model_name.human
68
- if save_action
69
- flash_notices = if flashes_on_success.present?
70
- flashes_on_success
71
- elsif flash_action.to_s == 'create'
72
- str = I18n.t(
73
- 'activerecord.success.successfully_created',
74
- default: '%<model_name>s successfully created'
75
- )
76
- [format(str, model_name: model_name)]
77
- elsif flash_action.to_s == 'update'
78
- str = I18n.t(
79
- 'activerecord.success.successfully_updated',
80
- default: '%<model_name>s successfully updated'
81
- )
82
- [format(str, model_name: model_name)]
83
- else
84
- str = I18n.t(
85
- "activerecord.success.#{flash_action}",
86
- default: '%<model_name>s successfully updated'
87
- )
88
- [format(str, model_name: model_name)]
89
- end
90
- flash_alerts = []
91
- else
92
- flash_alerts = if flashes_on_error.present?
93
- flashes_on_error
94
- elsif flash_action.to_s == 'create'
95
- str = I18n.t(
96
- 'activerecord.errors.messages.could_not_create',
97
- default: '%<model_name>s could not be created'
98
- )
99
- [format(str, model_name: model_name)]
100
- elsif flash_action.to_s == 'update'
101
- str = I18n.t(
102
- 'activerecord.errors.messages.could_not_update',
103
- default: '%<model_name>s could not be updated'
104
- )
105
- [format(str, model_name: model_name)]
106
- else
107
- str = I18n.t(
108
- "activerecord.errors.messages.#{flash_action}",
109
- default: '%<model_name>s could not be updated'
110
- )
111
- [format(str, model_name: model_name)]
112
- end
113
- flash_notices = []
114
- end
115
-
116
- flash_notices += add_flash_notices.to_a
117
- flash_alerts += add_flash_alerts.to_a
118
- _flash_id = Rails.configuration.x.render_turbo_stream.flash_id
119
- flash_id = (_flash_id ? _flash_id : "ERROR, MISSING CONFIG => config.x.render_turbo_stream.flash_id")
120
- flash_partial = Rails.configuration.x.render_turbo_stream.flash_partial
121
- flash_action = Rails.configuration.x.render_turbo_stream.flash_action
122
- flash_notices.each do |notice|
123
- next unless notice.present?
124
- # inside the flash partial has to be a loop that handles all theese flashes
125
- flash_stream = {
126
- id: flash_id,
127
- partial: flash_partial,
128
- action: flash_action,
129
- locals: { success: true, message: notice }
130
- }
131
- streams.push(flash_stream)
132
- end
133
- flash_alerts.each do |alert|
134
- next unless alert.present?
135
- # inside the flash partial has to be a loop that handles all theese flashes
136
- flash_stream = {
137
- id: flash_id,
138
- partial: flash_partial,
139
- action: flash_action,
140
- locals: { success: false, message: alert }
141
- }
142
- streams.push(flash_stream)
143
- end
144
-
145
- #== render
146
-
147
- if save_action && turbo_redirect_on_success_to.present?
148
- response.status = 302
149
- flash[:alert] = flash_alerts
150
- flash[:notice] = flash_notices
151
- Rails.logger.debug(" • Set flash[:alert] => #{flash_alerts}") if flash_alerts.present?
152
- Rails.logger.debug(" • Set flash[:notice] => #{flash_notices}") if flash_notices.present?
153
- render_turbo_stream([
154
- [
155
- :redirect_to,
156
- turbo_redirect_on_success_to
157
- ]
158
- ])
159
- elsif save_action && redirect_on_success_to.present?
160
- response.status = 302
161
- if Rails.configuration.x.render_turbo_stream.use_cable_for_turbo_stream_save && helpers.user_signed_in?
162
- streams.each do |s|
163
- next unless s.is_a?(Hash)
164
- Rails.logger.debug(" • Send by Cable => «#{s}»")
165
- partial_to_me(
166
- s[:partial],
167
- s[:id],
168
- action: flash_action,
169
- locals: s[:locals]
170
- )
171
- end
172
-
173
- else
174
- flash[:alert] = flash_alerts
175
- flash[:notice] = flash_notices
176
- Rails.logger.debug(" • Set flash[:alert] => #{flash_alerts}") if flash_alerts.present?
177
- Rails.logger.debug(" • Set flash[:notice] => #{flash_notices}") if flash_notices.present?
178
- end
179
- redirect_to redirect_on_success_to
180
-
181
- else
182
- flash.now[:alert] = flash_alerts
183
- flash.now[:notice] = flash_notices
184
- render_turbo_stream(streams)
185
- end
186
- end
187
-
188
- def render_turbo_stream(array)
189
-
190
- ary = []
191
- array.each do |pr|
192
- if !pr.present?
193
- Rails.logger.warn " WARNING render_turbo_stream: Empty element inside attributes: «#{array}»"
194
- elsif pr.is_a?(Hash)
195
- props = pr.symbolize_keys
196
- raise "missing attribute :id in #{props}" if !props[:id].present?
197
- part = (props[:partial].present? ? props[:partial] : props[:id]).gsub('-', '_')
198
- partial = (part.to_s.include?('/') ? part : [controller_path, part].join('/'))
199
- r = props
200
- r[:action] = (props[:action].present? ? props[:action] : :replace)
201
- r[:partial] = partial
202
- ary.push(r)
203
- elsif pr.is_a?(Array)
204
- raise "array has to contain at least one element: #{pr}" unless pr.first.present?
205
- ary.push(pr)
206
- else
207
- raise "ERROR render_turbo_stream invalid type: Only hash or array allowed"
208
- end
209
- end
210
-
211
- if request.format.to_sym == :turbo_stream
212
- render template: 'render_turbo_stream', locals: { streams: ary }, layout: false, formats: :turbo_stream
213
- else
214
- Rails.logger.debug(" • Render Turbo Stream RENDERING AS HTML because request.format => #{request.format}")
215
- render template: 'render_turbo_stream', locals: { streams: ary }, layout: false, formats: :html
216
- end
217
- end
218
-
219
- def stream_partial(
220
- id,
221
- partial: nil, #=> default: id
222
- action: :replace,
223
- locals: {}
224
- )
225
- render_turbo_stream(
226
- [
227
- {
228
- id: id,
229
- partial: partial,
230
- action: action,
231
- locals: locals
232
- }
233
- ]
234
- )
235
- end
236
-
237
- class Libs
238
- def self.all_responses(response)
239
- e = Nokogiri::HTML(response.body).search('#rendered-partials').first
240
- res = (e.present? ? JSON.parse(e.inner_html) : [])
241
- response.headers.each do |k, v|
242
- next unless k.match(/^test-turbo-cable-[\d]+$/)
243
- h = JSON.parse(v)
244
- res.push(h)
245
- end
246
- res
247
- end
248
-
249
- # if partial is one word, its checked against the last behind the slash, example: 'articles/form' matches 'form'
250
-
251
- def self.select_responses(response, id)
252
- all = all_responses(response)
253
-
254
- all.select do |a|
255
- if id.present?
256
- a['target'] == "##{id}"
257
- else
258
- false
259
- end
260
- end
261
-
262
- end
263
-
264
- def self.stream_targets(response)
265
- responses = all_responses(response)
266
- targets = []
267
- responses.each do |r|
268
- if r['target'].is_a?(Array)
269
- r['target'].each do |t|
270
- targets.push(t) unless targets.include?(t)
271
- end
272
- else
273
- targets.push(r['target']) unless targets.include?(r['target'])
274
- end
275
- end
276
-
277
- targets
278
- end
279
-
280
- def self.stream_target_response_count(response, id, total, &block)
281
- responses = select_responses(response, id)
282
-
283
- if total && responses.count != total
284
- false
285
- elsif block_given?
286
- res = responses.select do |r|
287
- parsed = Nokogiri::HTML(r['html_response'])
288
- r = yield parsed
289
- r.present?
290
- end.length
291
- (res == 0 ? nil : res)
292
- else
293
- responses.length
294
- end
295
-
296
- end
297
-
298
- # on most methods the first attribute is the target.
299
- # This method checks for that
300
- # it includes the methods from turbo-power
301
- # used for test helpers
302
- def self.first_arg_is_html_id(method)
303
- config = Rails.configuration.x.render_turbo_stream.first_argument_is_html_id
304
- default = [
305
- :graft,
306
- :morph,
307
- :inner_html,
308
- :insert_adjacent_text,
309
- :outer_html,
310
- :text_content,
311
- :add_css_class,
312
- :remove_attribute,
313
- :remove_css_class,
314
- :set_attribute,
315
- :set_dataset_attribute,
316
- :set_property,
317
- :set_style,
318
- :set_styles,
319
- :set_value,
320
- :dispatch_event,
321
- :reset_form,
322
- :clear_storage,
323
- :scroll_into_view,
324
- :set_focus,
325
- :turbo_frame_reload,
326
- :turbo_frame_set_src,
327
- :replace,
328
- :append,
329
- :prepend
330
- ]
331
- (config.present? ? config : default).map { |m| m.to_sym }.include?(method.to_sym)
332
- end
333
-
334
- end
335
- end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: render_turbo_stream
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.2
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - christian
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-03 00:00:00.000000000 Z
11
+ date: 2023-05-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -39,18 +39,20 @@ files:
39
39
  - Rakefile
40
40
  - app/controllers/render_turbo_stream/application_controller.rb
41
41
  - app/controllers/render_turbo_stream/render_turbo_stream_render_controller.rb
42
- - app/models/render_turbo_stream/cable_stream.rb
43
- - app/views/render_turbo_stream.html.erb
44
42
  - app/views/render_turbo_stream.turbo_stream.erb
45
- - db/migrate/20230428115511_cable_streams.rb
46
- - db/migrate/20230428115724_create_render_turbo_stream_cable_streams.rb
43
+ - app/views/render_turbo_stream_command.html.erb
44
+ - app/views/render_turbo_stream_empty_template.html.erb
45
+ - app/views/render_turbo_stream_request_test.html.erb
47
46
  - lib/render_turbo_stream.rb
47
+ - lib/render_turbo_stream/channel_libs.rb
48
+ - lib/render_turbo_stream/channel_view_helpers.rb
49
+ - lib/render_turbo_stream/controller_channel_helpers.rb
50
+ - lib/render_turbo_stream/controller_helpers.rb
48
51
  - lib/render_turbo_stream/engine.rb
49
52
  - lib/render_turbo_stream/railtie.rb
50
- - lib/render_turbo_stream/test/request_helpers.rb
51
- - lib/render_turbo_stream/test/rspec_request_helpers.rb
52
- - lib/render_turbo_stream/turbo_cable_helpers.rb
53
- - lib/render_turbo_stream/turbo_cable_view_helpers.rb
53
+ - lib/render_turbo_stream/test/request/channel_helpers.rb
54
+ - lib/render_turbo_stream/test/request/helpers.rb
55
+ - lib/render_turbo_stream/test/request/libs.rb
54
56
  - lib/render_turbo_stream/version.rb
55
57
  - lib/tasks/render_turbo_stream_tasks.rake
56
58
  homepage: https://gitlab.com/sedl/renderturbostream
@@ -78,6 +80,6 @@ requirements: []
78
80
  rubygems_version: 3.4.12
79
81
  signing_key:
80
82
  specification_version: 4
81
- summary: Run javascripts and render partials directly from the controller. With test
82
- helpers.
83
+ summary: Complete rails-like workflow for turbo streams and channels. With testing
84
+ strategy.
83
85
  test_files: []
@@ -1,4 +0,0 @@
1
- module RenderTurboStream
2
- class CableStream < ApplicationRecord
3
- end
4
- end
@@ -1,4 +0,0 @@
1
- class CableStreams < ActiveRecord::Migration[7.0]
2
- def change
3
- end
4
- end
@@ -1,11 +0,0 @@
1
- class CreateRenderTurboStreamCableStreams < ActiveRecord::Migration[7.0]
2
- def change
3
- create_table :render_turbo_stream_cable_streams do |t|
4
- t.string :request_id, limit: 80
5
- t.text :locals
6
- t.string :action, limit: 80
7
- t.string :arguments
8
- t.timestamps
9
- end
10
- end
11
- end
@@ -1,100 +0,0 @@
1
- module RenderTurboStream
2
- module Test
3
- module RequestHelpers
4
-
5
- # log as helper for the developer to see which flash is set and which partials are rendered to wich ids
6
- def streams_log
7
- RenderTurboStream::Libs.all_responses(response)
8
- end
9
-
10
- # Returns the path to which turbo_stream.redirect_to would be applied.
11
- def turbo_redirect_to
12
- resps = RenderTurboStream::Libs.all_responses(response)
13
- url = nil
14
- resps.each do |r|
15
- if r['type'] == 'command'
16
- if r['attributes'].first == 'redirect_to'
17
- if url
18
- url = 'ERROR: REDIRECT CALLED MORE THAN ONCE'
19
- else
20
- url = r['attributes'].second
21
- end
22
- end
23
- end
24
- end
25
- url
26
- end
27
-
28
- def stream_target_response_count(id, total: 1, &block)
29
- RenderTurboStream::Libs.stream_target_response_count(response, id, total, &block)
30
- end
31
-
32
- def stream_response(id, css: nil, include_string: nil)
33
- c = stream_target_response_count(id, total: 1) do |r|
34
- if css && include_string
35
- r.css(css).inner_html.include?(include_string)
36
- elsif include_string
37
- r.inner_html.include?(include_string)
38
- elsif css
39
- r.css(css)
40
- else
41
- r.inner_html
42
- end
43
- end
44
- c == 1
45
- end
46
-
47
- def stream_targets
48
- RenderTurboStream::Libs.stream_targets(response)
49
- end
50
-
51
- def stream_targets_count
52
- stream_targets.length
53
- end
54
-
55
- # Returns false if a given action such as "replace" (for: turbo_stream.replace) is not performed exactly once on a given target id. Otherwise, it returns an array of attributes that were used to call the function.
56
- # For stream actions, this only works if the given action has the HTML-ID as its first element.
57
- def stream_action_once(action, id)
58
- all_responses = RenderTurboStream::Libs.all_responses(response)
59
- counts = {}
60
- res = nil
61
- all_responses.each do |r|
62
-
63
- if r['type'] == 'command'
64
- act = r['attributes'].first
65
- is_id = RenderTurboStream::Libs.first_arg_is_html_id(act)
66
- if action.to_s == act && is_id && r['attributes'].second == id.to_s
67
- k = [act, r['attributes'].second].join('-')
68
- counts[k] ||= 0
69
- counts[k] += 1
70
- if counts[k] == 1
71
- res = r['attributes']
72
- else
73
- res = false
74
- end
75
- end
76
-
77
- elsif r['type'] == 'partial'
78
-
79
- act = r['action']
80
- is_id = r['target'][0] == '#'
81
- _id = r['target'][1..-1]
82
- if action.to_s == act && is_id && _id == id.to_s
83
- k = [act, _id].join('-')
84
- counts[k] ||= 0
85
- counts[k] += 1
86
- if counts[k] == 1
87
- res = [act, id, { locals: r['locals'] }]
88
- else
89
- res = false
90
- end
91
- end
92
-
93
- end
94
- end
95
- res
96
- end
97
-
98
- end
99
- end
100
- end
@@ -1,34 +0,0 @@
1
- module RenderTurboStream
2
- module Test
3
- module RspecRequestHelpers
4
-
5
- # expect status 200 and a each one time response to given ids
6
-
7
- def expect_successful_saved(*ids)
8
- responses = RenderTurboStream::Libs.all_responses(response)
9
- id_counts = {}
10
- responses.each do |r|
11
- if r['target'].is_a?(Array)
12
- r['target'].each do |t|
13
- id = (t[0] == '#' ? t[1..-1] : t)
14
- id_counts[t.to_s] ||= 0
15
- id_counts[t.to_s] += 1
16
- end
17
- else
18
- id = (r['target'][0] == '#' ? r['target'][1..-1] : r['target'])
19
- id_counts[id] ||= 0
20
- id_counts[id] += 1
21
- end
22
- end
23
-
24
- expect(response.status).to eq(200)
25
- expect(id_counts.keys.length).to eq(ids.length)
26
- ids.each do |id|
27
- expect(id_counts.key?(id)).to be_truthy
28
- expect(id_counts[id]).to eq(1)
29
- end
30
- end
31
-
32
- end
33
- end
34
- end
@@ -1,75 +0,0 @@
1
- module RenderTurboStream
2
- module TurboCableHelpers
3
-
4
- def partial_to_all_authenticated_users
5
- raise "Function partial_to_all_authenticated_users is not yet implemented"
6
- if user_signed_in?
7
-
8
- end
9
- end
10
-
11
- def partial_to_channel(action, channel, partial, id, locals: nil)
12
-
13
- # add headers for test
14
- if Rails.env.test?
15
- html = RenderTurboStreamRenderController.render(partial: partial, locals: locals)
16
- props = {
17
- target: "##{id}",
18
- action: action,
19
- type: 'cable-partial',
20
- partial: partial,
21
- locals: locals,
22
- html_response: html.to_s
23
- }
24
- h = response.headers.to_h
25
- i = 1
26
- loop do
27
- k = "test-turbo-cable-#{i}"
28
- unless h.keys.include?(k)
29
- response.headers[k] = props.to_json
30
- break
31
- end
32
- i += 1
33
- end
34
- end
35
-
36
- # send
37
-
38
- Turbo::StreamsChannel.send(
39
- "broadcast_#{action}_to",
40
- channel.to_s,
41
- target: id,
42
- partial: partial,
43
- locals: locals&.symbolize_keys,
44
- layout: false
45
- )
46
- end
47
-
48
- def partial_to_all(action, partial, id, locals: nil)
49
- partial_to_channel(action, 'all', partial, id, locals: locals)
50
- end
51
-
52
- def partial_to_me(partial, id, action: :replace, locals: nil)
53
- begin
54
- u_id = helpers.current_user&.id
55
- a=1
56
- unless u_id.present?
57
- Rails.logger.debug(' • SKIP RenderTurboStream.partial_to_me because current_user is nil')
58
- return
59
- end
60
- rescue
61
- Rails.logger.debug(' • ERROR RenderTurboStream.partial_to_me because current_user is not available')
62
- return
63
- end
64
-
65
- partial_to_channel(
66
- action,
67
- "authenticated-user-#{helpers.current_user.id}",
68
- partial,
69
- id,
70
- locals: locals
71
- )
72
- end
73
-
74
- end
75
- end
@@ -1,36 +0,0 @@
1
- module RenderTurboStream
2
- module TurboCableViewHelpers
3
-
4
- def cable_from_me
5
- if current_user
6
- turbo_stream_from "authenticated-user-#{current_user.id}"
7
- else
8
- Rails.logger.debug(" • SKIP CABLE_FROM_ME because not authenticated")
9
- nil
10
- end
11
- end
12
-
13
- def cable_from_all_authenticated_users
14
- raise "Function cable_from_all_authenticated_users is not yet implemented"
15
- if user_signed_in?
16
- turbo_stream_from "all-authenticated-users"
17
- else
18
- Rails.logger.debug(" • SKIP CABLE_FROM_ALL_AUTHENTICATED_USERS because not authenticated")
19
- nil
20
- end
21
- end
22
-
23
- def cable_from_all
24
- turbo_stream_from "all"
25
- end
26
-
27
- def local_model(object)
28
- if (object.is_a?(String) && object[0..5] == 'gid://') || object.is_a?(GlobalID)
29
- GlobalID::Locator.locate(object)
30
- else
31
- object
32
- end
33
- end
34
-
35
- end
36
- end