turbo-rails 2.0.6 → 2.0.13

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.
@@ -6,7 +6,7 @@ module Turbo::Streams::Broadcasts
6
6
  include Turbo::Streams::ActionHelper
7
7
 
8
8
  def broadcast_remove_to(*streamables, **opts)
9
- broadcast_action_to(*streamables, action: :remove, **opts)
9
+ broadcast_action_to(*streamables, action: :remove, render: false, **opts)
10
10
  end
11
11
 
12
12
  def broadcast_replace_to(*streamables, **opts)
@@ -38,10 +38,9 @@ module Turbo::Streams::Broadcasts
38
38
  end
39
39
 
40
40
  def broadcast_action_to(*streamables, action:, target: nil, targets: nil, attributes: {}, **rendering)
41
- broadcast_stream_to(*streamables, content: turbo_stream_action_tag(action, target: target, targets: targets, template:
42
- rendering.delete(:content) || rendering.delete(:html) || (rendering[:render] != false && rendering.any? ? render_format(:html, **rendering) : nil),
43
- **attributes
44
- ))
41
+ broadcast_stream_to(*streamables, content: turbo_stream_action_tag(
42
+ action, target: target, targets: targets, template: render_broadcast_action(rendering), **attributes)
43
+ )
45
44
  end
46
45
 
47
46
  def broadcast_replace_later_to(*streamables, **opts)
@@ -70,7 +69,7 @@ module Turbo::Streams::Broadcasts
70
69
 
71
70
  def broadcast_refresh_later_to(*streamables, request_id: Turbo.current_request_id, **opts)
72
71
  refresh_debouncer_for(*streamables, request_id: request_id).debounce do
73
- Turbo::Streams::BroadcastStreamJob.perform_later stream_name_from(streamables), content: turbo_stream_refresh_tag(request_id: request_id, **opts)
72
+ Turbo::Streams::BroadcastStreamJob.perform_later stream_name_from(streamables), content: turbo_stream_refresh_tag(request_id: request_id, **opts).to_str # Sidekiq requires job arguments to be valid JSON types, such as String
74
73
  end
75
74
  end
76
75
 
@@ -111,4 +110,16 @@ module Turbo::Streams::Broadcasts
111
110
  def render_format(format, **rendering)
112
111
  ApplicationController.render(formats: [ format ], **rendering)
113
112
  end
113
+
114
+ def render_broadcast_action(rendering)
115
+ content = rendering.delete(:content)
116
+ html = rendering.delete(:html)
117
+ render = rendering.delete(:render)
118
+
119
+ if render == false
120
+ nil
121
+ else
122
+ content || html || (render_format(:html, **rendering) if rendering.present?)
123
+ end
124
+ end
114
125
  end
@@ -1,20 +1,23 @@
1
1
  # Turbo is built to work with native navigation principles and present those alongside what's required for the web. When you
2
- # have Turbo Native clients running (see the Turbo iOS and Turbo Android projects for details), you can respond to native
3
- # requests with three dedicated responses: <tt>recede</tt>, <tt>resume</tt>, <tt>refresh</tt>.
2
+ # have Hotwire Native clients running (see the Hotwire Native iOS and Hotwire Native Android projects for details),
3
+ # you can respond to native requests with three dedicated responses: <tt>recede</tt>, <tt>resume</tt>, <tt>refresh</tt>.
4
4
  #
5
- # turbo-android handles these actions automatically. You are required to implement the handling on your own for turbo-ios.
5
+ # Hotwire Native Android and Hotwire Native iOS handle these actions automatically.
6
6
  module Turbo::Native::Navigation
7
7
  extend ActiveSupport::Concern
8
8
 
9
9
  included do
10
- helper_method :turbo_native_app?
10
+ helper_method :hotwire_native_app?, :turbo_native_app?
11
11
  end
12
12
 
13
- # Turbo Native applications are identified by having the string "Turbo Native" as part of their user agent.
14
- def turbo_native_app?
15
- request.user_agent.to_s.match?(/Turbo Native/)
13
+ # Hotwire Native applications are identified by having the string "Hotwire Native" as part of their user agent.
14
+ # Legacy Turbo Native applications use the "Turbo Native" string.
15
+ def hotwire_native_app?
16
+ request.user_agent.to_s.match?(/(Turbo|Hotwire) Native/)
16
17
  end
17
-
18
+
19
+ alias_method :turbo_native_app?, :hotwire_native_app?
20
+
18
21
  # Tell the Turbo Native app to dismiss a modal (if presented) or pop a screen off of the navigation stack. Otherwise redirect to the given URL if Turbo Native is not present.
19
22
  def recede_or_redirect_to(url, **options)
20
23
  turbo_native_action_or_redirect url, :recede, :to, options
@@ -39,7 +39,7 @@ module Turbo::Streams::ActionHelper
39
39
  # turbo_stream_refresh_tag
40
40
  # # => <turbo-stream action="refresh"></turbo-stream>
41
41
  def turbo_stream_refresh_tag(request_id: Turbo.current_request_id, **attributes)
42
- turbo_stream_action_tag(:refresh, **{ "request-id": request_id }.compact, **attributes)
42
+ turbo_stream_action_tag(:refresh, "request-id": request_id.presence, **attributes)
43
43
  end
44
44
 
45
45
  private
@@ -48,7 +48,13 @@ module Turbo::StreamsHelper
48
48
  # It is also possible to pass additional parameters to the channel by passing them through `data` attributes:
49
49
  #
50
50
  # <%= turbo_stream_from "room", channel: RoomChannel, data: {room_name: "room #1"} %>
51
+ #
52
+ # Raises an +ArgumentError+ if all streamables are blank
53
+ #
54
+ # <%= turbo_stream_from("") %> # => ArgumentError: streamables can't be blank
55
+ # <%= turbo_stream_from("", nil) %> # => ArgumentError: streamables can't be blank
51
56
  def turbo_stream_from(*streamables, **attributes)
57
+ raise ArgumentError, "streamables can't be blank" unless streamables.any?(&:present?)
52
58
  attributes[:channel] = attributes[:channel]&.to_s || "Turbo::StreamsChannel"
53
59
  attributes[:"signed-stream-name"] = Turbo::StreamsChannel.signed_stream_name(streamables)
54
60
 
@@ -3,6 +3,8 @@ import { subscribeTo } from "./cable"
3
3
  import snakeize from "./snakeize"
4
4
 
5
5
  class TurboCableStreamSourceElement extends HTMLElement {
6
+ static observedAttributes = ["channel", "signed-stream-name"]
7
+
6
8
  async connectedCallback() {
7
9
  connectStreamSource(this)
8
10
  this.subscription = await subscribeTo(this.channel, {
@@ -15,6 +17,14 @@ class TurboCableStreamSourceElement extends HTMLElement {
15
17
  disconnectedCallback() {
16
18
  disconnectStreamSource(this)
17
19
  if (this.subscription) this.subscription.unsubscribe()
20
+ this.subscriptionDisconnected()
21
+ }
22
+
23
+ attributeChangedCallback() {
24
+ if (this.subscription) {
25
+ this.disconnectedCallback()
26
+ this.connectedCallback()
27
+ }
18
28
  }
19
29
 
20
30
  dispatchMessageEvent(data) {
@@ -1,4 +1,4 @@
1
- # Turbo streams can be broadcasted directly from models that include this module (this is automatically done for Active Records).
1
+ # Turbo streams can be broadcasted directly from models that include this module (this is automatically done for Active Records if ActiveJob is loaded).
2
2
  # This makes it convenient to execute both synchronous and asynchronous updates, and render directly from callbacks in models
3
3
  # or from controllers or jobs that act on those models. Here's an example:
4
4
  #
@@ -241,13 +241,13 @@ module Turbo::Broadcastable
241
241
  #
242
242
  # # Sends <turbo-stream action="remove" target="clearance_5"></turbo-stream> to the stream named "identity:2:clearances"
243
243
  # clearance.broadcast_remove_to examiner.identity, :clearances
244
- def broadcast_remove_to(*streamables, target: self)
245
- Turbo::StreamsChannel.broadcast_remove_to(*streamables, target: target) unless suppressed_turbo_broadcasts?
244
+ def broadcast_remove_to(*streamables, target: self, **rendering)
245
+ Turbo::StreamsChannel.broadcast_remove_to(*streamables, **extract_options_and_add_target(rendering, target: target)) unless suppressed_turbo_broadcasts?
246
246
  end
247
247
 
248
248
  # Same as <tt>#broadcast_remove_to</tt>, but the designated stream is automatically set to the current model.
249
- def broadcast_remove
250
- broadcast_remove_to self
249
+ def broadcast_remove(**rendering)
250
+ broadcast_remove_to self, **rendering
251
251
  end
252
252
 
253
253
  # Replace this broadcastable model in the dom for subscribers of the stream name identified by the passed
@@ -265,7 +265,7 @@ module Turbo::Broadcastable
265
265
  # # to the stream named "identity:2:clearances"
266
266
  # clearance.broadcast_replace_to examiner.identity, :clearance, attributes: { method: :morph }, partial: "clearances/other_partial", locals: { a: 1 }
267
267
  def broadcast_replace_to(*streamables, **rendering)
268
- Turbo::StreamsChannel.broadcast_replace_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
268
+ Turbo::StreamsChannel.broadcast_replace_to(*streamables, **extract_options_and_add_target(rendering, target: self)) unless suppressed_turbo_broadcasts?
269
269
  end
270
270
 
271
271
  # Same as <tt>#broadcast_replace_to</tt>, but the designated stream is automatically set to the current model.
@@ -288,7 +288,7 @@ module Turbo::Broadcastable
288
288
  # # to the stream named "identity:2:clearances"
289
289
  # # clearance.broadcast_update_to examiner.identity, :clearances, attributes: { method: :morph }, partial: "clearances/other_partial", locals: { a: 1 }
290
290
  def broadcast_update_to(*streamables, **rendering)
291
- Turbo::StreamsChannel.broadcast_update_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
291
+ Turbo::StreamsChannel.broadcast_update_to(*streamables, **extract_options_and_add_target(rendering, target: self)) unless suppressed_turbo_broadcasts?
292
292
  end
293
293
 
294
294
  # Same as <tt>#broadcast_update_to</tt>, but the designated stream is automatically set to the current model.
@@ -308,8 +308,10 @@ module Turbo::Broadcastable
308
308
  # # to the stream named "identity:2:clearances"
309
309
  # clearance.broadcast_before_to examiner.identity, :clearances, target: "clearance_5",
310
310
  # partial: "clearances/other_partial", locals: { a: 1 }
311
- def broadcast_before_to(*streamables, target:, **rendering)
312
- Turbo::StreamsChannel.broadcast_before_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
311
+ def broadcast_before_to(*streamables, target: nil, targets: nil, **rendering)
312
+ raise ArgumentError, "at least one of target or targets is required" unless target || targets
313
+
314
+ Turbo::StreamsChannel.broadcast_before_to(*streamables, **extract_options_and_add_target(rendering.merge(target: target, targets: targets)))
313
315
  end
314
316
 
315
317
  # Insert a rendering of this broadcastable model after the target identified by it's dom id passed as <tt>target</tt>
@@ -324,8 +326,10 @@ module Turbo::Broadcastable
324
326
  # # to the stream named "identity:2:clearances"
325
327
  # clearance.broadcast_after_to examiner.identity, :clearances, target: "clearance_5",
326
328
  # partial: "clearances/other_partial", locals: { a: 1 }
327
- def broadcast_after_to(*streamables, target:, **rendering)
328
- Turbo::StreamsChannel.broadcast_after_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
329
+ def broadcast_after_to(*streamables, target: nil, targets: nil, **rendering)
330
+ raise ArgumentError, "at least one of target or targets is required" unless target || targets
331
+
332
+ Turbo::StreamsChannel.broadcast_after_to(*streamables, **extract_options_and_add_target(rendering.merge(target: target, targets: targets)))
329
333
  end
330
334
 
331
335
  # Append a rendering of this broadcastable model to the target identified by it's dom id passed as <tt>target</tt>
@@ -341,7 +345,7 @@ module Turbo::Broadcastable
341
345
  # clearance.broadcast_append_to examiner.identity, :clearances, target: "clearances",
342
346
  # partial: "clearances/other_partial", locals: { a: 1 }
343
347
  def broadcast_append_to(*streamables, target: broadcast_target_default, **rendering)
344
- Turbo::StreamsChannel.broadcast_append_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
348
+ Turbo::StreamsChannel.broadcast_append_to(*streamables, **extract_options_and_add_target(rendering, target: target)) unless suppressed_turbo_broadcasts?
345
349
  end
346
350
 
347
351
  # Same as <tt>#broadcast_append_to</tt>, but the designated stream is automatically set to the current model.
@@ -362,7 +366,7 @@ module Turbo::Broadcastable
362
366
  # clearance.broadcast_prepend_to examiner.identity, :clearances, target: "clearances",
363
367
  # partial: "clearances/other_partial", locals: { a: 1 }
364
368
  def broadcast_prepend_to(*streamables, target: broadcast_target_default, **rendering)
365
- Turbo::StreamsChannel.broadcast_prepend_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
369
+ Turbo::StreamsChannel.broadcast_prepend_to(*streamables, **extract_options_and_add_target(rendering, target: target)) unless suppressed_turbo_broadcasts?
366
370
  end
367
371
 
368
372
  # Same as <tt>#broadcast_prepend_to</tt>, but the designated stream is automatically set to the current model.
@@ -389,7 +393,7 @@ module Turbo::Broadcastable
389
393
  # # to the stream named "identity:2:clearances"
390
394
  # clearance.broadcast_action_to examiner.identity, :clearances, action: :prepend, target: "clearances"
391
395
  def broadcast_action_to(*streamables, action:, target: broadcast_target_default, attributes: {}, **rendering)
392
- Turbo::StreamsChannel.broadcast_action_to(*streamables, action: action, target: target, attributes: attributes, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
396
+ Turbo::StreamsChannel.broadcast_action_to(*streamables, action: action, attributes: attributes, **extract_options_and_add_target(rendering, target: target)) unless suppressed_turbo_broadcasts?
393
397
  end
394
398
 
395
399
  # Same as <tt>#broadcast_action_to</tt>, but the designated stream is automatically set to the current model.
@@ -399,7 +403,7 @@ module Turbo::Broadcastable
399
403
 
400
404
  # Same as <tt>broadcast_replace_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
401
405
  def broadcast_replace_later_to(*streamables, **rendering)
402
- Turbo::StreamsChannel.broadcast_replace_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
406
+ Turbo::StreamsChannel.broadcast_replace_later_to(*streamables, **extract_options_and_add_target(rendering, target: self)) unless suppressed_turbo_broadcasts?
403
407
  end
404
408
 
405
409
  # Same as <tt>#broadcast_replace_later_to</tt>, but the designated stream is automatically set to the current model.
@@ -409,7 +413,7 @@ module Turbo::Broadcastable
409
413
 
410
414
  # Same as <tt>broadcast_update_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
411
415
  def broadcast_update_later_to(*streamables, **rendering)
412
- Turbo::StreamsChannel.broadcast_update_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
416
+ Turbo::StreamsChannel.broadcast_update_later_to(*streamables, **extract_options_and_add_target(rendering, target: self)) unless suppressed_turbo_broadcasts?
413
417
  end
414
418
 
415
419
  # Same as <tt>#broadcast_update_later_to</tt>, but the designated stream is automatically set to the current model.
@@ -419,7 +423,7 @@ module Turbo::Broadcastable
419
423
 
420
424
  # Same as <tt>broadcast_append_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
421
425
  def broadcast_append_later_to(*streamables, target: broadcast_target_default, **rendering)
422
- Turbo::StreamsChannel.broadcast_append_later_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
426
+ Turbo::StreamsChannel.broadcast_append_later_to(*streamables, **extract_options_and_add_target(rendering, target: target)) unless suppressed_turbo_broadcasts?
423
427
  end
424
428
 
425
429
  # Same as <tt>#broadcast_append_later_to</tt>, but the designated stream is automatically set to the current model.
@@ -429,7 +433,7 @@ module Turbo::Broadcastable
429
433
 
430
434
  # Same as <tt>broadcast_prepend_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
431
435
  def broadcast_prepend_later_to(*streamables, target: broadcast_target_default, **rendering)
432
- Turbo::StreamsChannel.broadcast_prepend_later_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
436
+ Turbo::StreamsChannel.broadcast_prepend_later_to(*streamables, **extract_options_and_add_target(rendering, target: target)) unless suppressed_turbo_broadcasts?
433
437
  end
434
438
 
435
439
  # Same as <tt>#broadcast_prepend_later_to</tt>, but the designated stream is automatically set to the current model.
@@ -449,7 +453,7 @@ module Turbo::Broadcastable
449
453
 
450
454
  # Same as <tt>broadcast_action_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
451
455
  def broadcast_action_later_to(*streamables, action:, target: broadcast_target_default, attributes: {}, **rendering)
452
- Turbo::StreamsChannel.broadcast_action_later_to(*streamables, action: action, target: target, attributes: attributes, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
456
+ Turbo::StreamsChannel.broadcast_action_later_to(*streamables, action: action, attributes: attributes, **extract_options_and_add_target(rendering, target: target)) unless suppressed_turbo_broadcasts?
453
457
  end
454
458
 
455
459
  # Same as <tt>#broadcast_action_later_to</tt>, but the designated stream is automatically set to the current model.
@@ -485,7 +489,7 @@ module Turbo::Broadcastable
485
489
  # desireable for model callbacks, certainly not if those callbacks are inside of a transaction. Most of the time you should
486
490
  # be using +broadcast_render_later_to+, unless you specifically know why synchronous rendering is needed.
487
491
  def broadcast_render_to(*streamables, **rendering)
488
- Turbo::StreamsChannel.broadcast_render_to(*streamables, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
492
+ Turbo::StreamsChannel.broadcast_render_to(*streamables, **extract_options_and_add_target(rendering, target: self)) unless suppressed_turbo_broadcasts?
489
493
  end
490
494
 
491
495
  # Same as <tt>broadcast_render_to</tt> but run asynchronously via a <tt>Turbo::Streams::BroadcastJob</tt>.
@@ -496,7 +500,7 @@ module Turbo::Broadcastable
496
500
  # Same as <tt>broadcast_render_later</tt> but run with the added option of naming the stream using the passed
497
501
  # <tt>streamables</tt>.
498
502
  def broadcast_render_later_to(*streamables, **rendering)
499
- Turbo::StreamsChannel.broadcast_render_later_to(*streamables, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
503
+ Turbo::StreamsChannel.broadcast_render_later_to(*streamables, **extract_options_and_add_target(rendering)) unless suppressed_turbo_broadcasts?
500
504
  end
501
505
 
502
506
  private
@@ -504,11 +508,17 @@ module Turbo::Broadcastable
504
508
  self.class.broadcast_target_default
505
509
  end
506
510
 
511
+ def extract_options_and_add_target(rendering = {}, target: broadcast_target_default)
512
+ broadcast_rendering_with_defaults(rendering).tap do |options|
513
+ options[:target] = target if !options.key?(:target) && !options.key?(:targets)
514
+ end
515
+ end
516
+
507
517
  def broadcast_rendering_with_defaults(options)
508
518
  options.tap do |o|
509
519
  # Add the current instance into the locals with the element name (which is the un-namespaced name)
510
520
  # as the key. This parallels how the ActionView::ObjectRenderer would create a local variable.
511
- o[:locals] = (o[:locals] || {}).reverse_merge!(model_name.element.to_sym => self, request_id: Turbo.current_request_id).compact
521
+ o[:locals] = (o[:locals] || {}).reverse_merge!(model_name.element.to_sym => self).compact
512
522
 
513
523
  if o[:html] || o[:partial]
514
524
  return o
@@ -77,8 +77,9 @@ class Turbo::Streams::TagBuilder
77
77
  # <%= turbo_stream.replace "clearance_5" do %>
78
78
  # <div id='clearance_5'>Replace the dom target identified by clearance_5</div>
79
79
  # <% end %>
80
- def replace(target, content = nil, **rendering, &block)
81
- action :replace, target, content, **rendering, &block
80
+ # <%= turbo_stream.replace clearance, "<div>Morph the dom target</div>", method: :morph %>
81
+ def replace(target, content = nil, method: nil, **rendering, &block)
82
+ action :replace, target, content, method: method, **rendering, &block
82
83
  end
83
84
 
84
85
  # Replace the <tt>targets</tt> in the dom with either the <tt>content</tt> passed in, a rendering result determined
@@ -90,8 +91,9 @@ class Turbo::Streams::TagBuilder
90
91
  # <%= turbo_stream.replace_all ".clearance_item" do %>
91
92
  # <div class='.clearance_item'>Replace the dom target identified by the class clearance_item</div>
92
93
  # <% end %>
93
- def replace_all(targets, content = nil, **rendering, &block)
94
- action_all :replace, targets, content, **rendering, &block
94
+ # <%= turbo_stream.replace_all clearance, "<div>Morph the dom target</div>", method: :morph %>
95
+ def replace_all(targets, content = nil, method: nil, **rendering, &block)
96
+ action_all :replace, targets, content, method: method, **rendering, &block
95
97
  end
96
98
 
97
99
  # Insert the <tt>content</tt> passed in, a rendering result determined by the <tt>rendering</tt> keyword arguments,
@@ -155,8 +157,9 @@ class Turbo::Streams::TagBuilder
155
157
  # <%= turbo_stream.update "clearance_5" do %>
156
158
  # Update the content of the dom target identified by clearance_5
157
159
  # <% end %>
158
- def update(target, content = nil, **rendering, &block)
159
- action :update, target, content, **rendering, &block
160
+ # <%= turbo_stream.update clearance, "<div>Morph the dom target</div>", method: :morph %>
161
+ def update(target, content = nil, method: nil, **rendering, &block)
162
+ action :update, target, content, method: method, **rendering, &block
160
163
  end
161
164
 
162
165
  # Update the <tt>targets</tt> in the dom with either the <tt>content</tt> passed in or a rendering result determined
@@ -168,8 +171,9 @@ class Turbo::Streams::TagBuilder
168
171
  # <%= turbo_stream.update_all "clearance_item" do %>
169
172
  # Update the content of the dom target identified by the class clearance_item
170
173
  # <% end %>
171
- def update_all(targets, content = nil, **rendering, &block)
172
- action_all :update, targets, content, **rendering, &block
174
+ # <%= turbo_stream.update_all clearance, "<div>Morph the dom target</div>", method: :morph %>
175
+ def update_all(targets, content = nil, method: nil, **rendering, &block)
176
+ action_all :update, targets, content, method: method, **rendering, &block
173
177
  end
174
178
 
175
179
  # Append to the target in the dom identified with <tt>target</tt> either the <tt>content</tt> passed in or a
@@ -228,53 +232,43 @@ class Turbo::Streams::TagBuilder
228
232
  action_all :prepend, targets, content, **rendering, &block
229
233
  end
230
234
 
231
- # Morph the <tt>target</tt> in the dom with either the <tt>content</tt> passed in or a rendering result determined
232
- # by the <tt>rendering</tt> keyword arguments, the content in the block, or the rendering of the target as a record. Examples:
235
+ # Creates a `turbo-stream` tag with an `[action="refresh"`] attribute and a
236
+ # `[request-id]` attribute that defaults to `Turbo.current_request_id`:
233
237
  #
234
- # <%= turbo_stream.morph "clearance_5", "<div id='clearance_5'>Morph the dom target identified by clearance_5</div>" %>
235
- # <%= turbo_stream.morph clearance %>
236
- # <%= turbo_stream.morph clearance, partial: "clearances/clearance", locals: { title: "Hello" } %>
237
- # <%= turbo_stream.morph "clearance_5" do %>
238
- # <div id='clearance_5'>Morph the dom target identified by clearance_5</div>
239
- # <% end %>
240
- def morph(target, content = nil, **rendering, &block)
241
- action :morph, target, content, **rendering, &block
242
- end
243
-
244
- # Morph the <tt>targets</tt> in the dom with either the <tt>content</tt> passed in or a rendering result determined
245
- # by the <tt>rendering</tt> keyword arguments, the content in the block, or the rendering of the targets as a record. Examples:
238
+ # turbo_stream.refresh
239
+ # # => <turbo-stream action="refresh" request-id="ef083d55-7516-41b1-ad28-16f553399c6a"></turbo-stream>
246
240
  #
247
- # <%= turbo_stream.morph_all ".clearance_item", "<div class='clearance_item'>Morph the dom target identified by the class clearance_item</div>" %>
248
- # <%= turbo_stream.morph_all clearance %>
249
- # <%= turbo_stream.morph_all clearance, partial: "clearances/clearance", locals: { title: "Hello" } %>
250
- # <%= turbo_stream.morph_all ".clearance_item" do %>
251
- # <div class='clearance_item'>Morph the dom target identified by the class clearance_item</div>
252
- # <% end %>
253
- def morph_all(targets, content = nil, **rendering, &block)
254
- action_all :morph, targets, content, **rendering, &block
241
+ # turbo_stream.refresh request_id: "abc123"
242
+ # # => <turbo-stream action="refresh" request-id="abc123"></turbo-stream>
243
+ def refresh(...)
244
+ turbo_stream_refresh_tag(...)
255
245
  end
256
246
 
257
247
  # Send an action of the type <tt>name</tt> to <tt>target</tt>. Options described in the concrete methods.
258
- def action(name, target, content = nil, allow_inferred_rendering: true, **rendering, &block)
248
+ def action(name, target, content = nil, method: nil, allow_inferred_rendering: true, **rendering, &block)
259
249
  template = render_template(target, content, allow_inferred_rendering: allow_inferred_rendering, **rendering, &block)
260
250
 
261
- turbo_stream_action_tag name, target: target, template: template
251
+ turbo_stream_action_tag name, target: target, template: template, method: method
262
252
  end
263
253
 
264
254
  # Send an action of the type <tt>name</tt> to <tt>targets</tt>. Options described in the concrete methods.
265
- def action_all(name, targets, content = nil, allow_inferred_rendering: true, **rendering, &block)
255
+ def action_all(name, targets, content = nil, method: nil, allow_inferred_rendering: true, **rendering, &block)
266
256
  template = render_template(targets, content, allow_inferred_rendering: allow_inferred_rendering, **rendering, &block)
267
257
 
268
- turbo_stream_action_tag name, targets: targets, template: template
258
+ turbo_stream_action_tag name, targets: targets, template: template, method: method
269
259
  end
270
260
 
271
261
  private
272
262
  def render_template(target, content = nil, allow_inferred_rendering: true, **rendering, &block)
273
263
  case
264
+ when target.respond_to?(:render_in) && content.nil?
265
+ target.render_in(@view_context, &block)
274
266
  when content.respond_to?(:render_in)
275
267
  content.render_in(@view_context, &block)
276
268
  when content
277
269
  allow_inferred_rendering ? (render_record(content) || content) : content
270
+ when block_given? && (rendering.key?(:partial) || rendering.key?(:layout))
271
+ @view_context.render(formats: [ :html ], layout: rendering[:partial], **rendering, &block)
278
272
  when block_given?
279
273
  @view_context.capture(&block)
280
274
  when rendering.any?
data/config/routes.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  Rails.application.routes.draw do
2
- get "recede_historical_location" => "turbo/native/navigation#recede", as: :turbo_recede_historical_location
3
- get "resume_historical_location" => "turbo/native/navigation#resume", as: :turbo_resume_historical_location
4
- get "refresh_historical_location" => "turbo/native/navigation#refresh", as: :turbo_refresh_historical_location
2
+ get "recede_historical_location", to: "turbo/native/navigation#recede", as: :turbo_recede_historical_location
3
+ get "resume_historical_location", to: "turbo/native/navigation#resume", as: :turbo_resume_historical_location
4
+ get "refresh_historical_location", to: "turbo/native/navigation#refresh", as: :turbo_refresh_historical_location
5
5
  end if Turbo.draw_routes
@@ -5,20 +5,6 @@ module Turbo
5
5
  system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{File.expand_path("../install/#{path}.rb", __dir__)}"
6
6
  end
7
7
 
8
- def redis_installed?
9
- Gem.win_platform? ?
10
- system('where redis-server > NUL 2>&1') :
11
- system('which redis-server > /dev/null')
12
- end
13
-
14
- def switch_on_redis_if_available
15
- if redis_installed?
16
- Rake::Task["turbo:install:redis"].invoke
17
- else
18
- puts "Run turbo:install:redis to switch on Redis and use it in development for turbo streams"
19
- end
20
- end
21
-
22
8
  def using_bun?
23
9
  Rails.root.join("bun.config.js").exist?
24
10
  end
@@ -43,24 +29,16 @@ namespace :turbo do
43
29
  desc "Install Turbo into the app with asset pipeline"
44
30
  task :importmap do
45
31
  Turbo::Tasks.run_turbo_install_template "turbo_with_importmap"
46
- Turbo::Tasks.switch_on_redis_if_available
47
32
  end
48
33
 
49
34
  desc "Install Turbo into the app with webpacker"
50
35
  task :node do
51
36
  Turbo::Tasks.run_turbo_install_template "turbo_with_node"
52
- Turbo::Tasks.switch_on_redis_if_available
53
37
  end
54
38
 
55
39
  desc "Install Turbo into the app with bun"
56
40
  task :bun do
57
41
  Turbo::Tasks.run_turbo_install_template "turbo_with_bun"
58
- Turbo::Tasks.switch_on_redis_if_available
59
- end
60
-
61
- desc "Switch on Redis and use it in development"
62
- task :redis do
63
- Turbo::Tasks.run_turbo_install_template "turbo_needs_redis"
64
42
  end
65
43
  end
66
44
  end
data/lib/turbo/engine.rb CHANGED
@@ -5,6 +5,7 @@ module Turbo
5
5
  isolate_namespace Turbo
6
6
  config.eager_load_namespaces << Turbo
7
7
  config.turbo = ActiveSupport::OrderedOptions.new
8
+ config.turbo.test_connect_after_actions = %i[visit]
8
9
  config.autoload_once_paths = %W(
9
10
  #{root}/app/channels
10
11
  #{root}/app/controllers
@@ -15,6 +16,27 @@ module Turbo
15
16
  #{root}/app/jobs
16
17
  )
17
18
 
19
+ # If the parent application does not use Active Job, app/jobs cannot
20
+ # be eager loaded, because it references the ActiveJob constant.
21
+ #
22
+ # When turbo-rails depends on Rails 7 or above, the entire block can be
23
+ # reduced to
24
+ #
25
+ # unless defined?(ActiveJob)
26
+ # Rails.autoloaders.once.do_not_eager_load("#{root}/app/jobs")
27
+ # end
28
+ #
29
+ initializer "turbo.no_active_job", before: :set_eager_load_paths do
30
+ unless defined?(ActiveJob)
31
+ if Rails.autoloaders.zeitwerk_enabled?
32
+ Rails.autoloaders.once.do_not_eager_load("#{root}/app/jobs")
33
+ else
34
+ # This else branch only runs in Rails 6.x + classic mode.
35
+ config.eager_load_paths.delete("#{root}/app/jobs")
36
+ end
37
+ end
38
+ end
39
+
18
40
  # If the parent application does not use Action Cable, app/channels cannot
19
41
  # be eager loaded, because it references the ActionCable constant.
20
42
  #
@@ -71,7 +93,9 @@ module Turbo
71
93
 
72
94
  initializer "turbo.broadcastable" do
73
95
  ActiveSupport.on_load(:active_record) do
74
- include Turbo::Broadcastable
96
+ if defined?(ActiveJob)
97
+ include Turbo::Broadcastable
98
+ end
75
99
  end
76
100
  end
77
101
 
@@ -101,8 +125,10 @@ module Turbo
101
125
 
102
126
  ActiveSupport.on_load(:action_cable) do
103
127
  ActiveSupport.on_load(:active_support_test_case) do
104
- require "turbo/broadcastable/test_helper"
105
- include Turbo::Broadcastable::TestHelper
128
+ if defined?(ActiveJob)
129
+ require "turbo/broadcastable/test_helper"
130
+ include Turbo::Broadcastable::TestHelper
131
+ end
106
132
  end
107
133
  end
108
134
 
@@ -126,5 +152,24 @@ module Turbo
126
152
  end
127
153
  end
128
154
  end
155
+
156
+ initializer "turbo.system_test_helper" do
157
+ ActiveSupport.on_load(:action_dispatch_system_test_case) do
158
+ require "turbo/system_test_helper"
159
+ include Turbo::SystemTestHelper
160
+ end
161
+ end
162
+
163
+ config.after_initialize do |app|
164
+ ActiveSupport.on_load(:action_dispatch_system_test_case) do
165
+ app.config.turbo.test_connect_after_actions.map do |method|
166
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
167
+ def #{method}(...) # def visit(...)
168
+ super.tap { connect_turbo_cable_stream_sources } # super.tap { connect_turbo_cable_stream_sources }
169
+ end # end
170
+ RUBY
171
+ end
172
+ end
173
+ end
129
174
  end
130
175
  end