render_turbo_stream 2.1.2 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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