Module: Turbo::Broadcastable

Extended by:
ActiveSupport::Concern
Defined in:
app/models/concerns/turbo/broadcastable.rb,
lib/turbo/broadcastable/test_helper.rb

Overview

This approach is an alternative to fine-grained stream actions targeting specific DOM elements. It offers good fidelity with a much simpler programming model. As a tradeoff, the fidelity you can reach is often not as high as with targeted stream actions since it renders the entire page again.

The broadcast_refreshes class method configures the model to broadcast a “page refresh” on creates, updates, and destroys to a stream name derived at runtime by the stream symbol invocation. Examples

class Board < ApplicationRecord
  broadcast_refreshes
end

In this example, when a board is created, updated, or destroyed, a Turbo Stream for a page refresh will be broadcasted to all clients subscribed to the “boards” stream.

This works great in hierarchical structures, where the child record touches parent records automatically to invalidate the cache:

class Column < ApplicationRecord
  belongs_to :board, touch: true # +Board+ will trigger a page refresh on column changes
end

You can also specify the streamable declaratively by passing a symbol to the broadcast_refreshes_to method:

class Column < ApplicationRecord
  belongs_to :board
  broadcast_refreshes_to :board
end

For more granular control, you can also broadcast a “page refresh” to a stream name derived from the passed streamables by using the instance-level methods broadcast_refresh_to or broadcast_refresh_later_to. These methods are particularly useful when you want to trigger a page refresh for more specific scenarios. Example:

class Clearance < ApplicationRecord
  belongs_to :petitioner, class_name: "Contact"
  belongs_to :examiner,   class_name: "User"

  after_create_commit :broadcast_refresh_later

  private
    def broadcast_refresh_later
      broadcast_refresh_later_to examiner.identity, :clearances
    end
end

In this example, a “page refresh” is broadcast to the stream named “identity:<identity-id>:clearances” after a new clearance is created. All clients subscribed to this stream will refresh the page to reflect the changes.

When broadcasting page refreshes, Turbo will automatically debounce multiple calls in a row to only broadcast the last one. This is meant for scenarios where you process records in mass. Because of the nature of such signals, it makes no sense to broadcast them repeatedly and individually.

Suppressing broadcasts

Sometimes, you need to disable broadcasts in certain scenarios. You can use .suppressing_turbo_broadcasts to create execution contexts where broadcasts are disabled:

class Message < ApplicationRecord
  after_create_commit :update_message

  private
    def update_message
      broadcast_replace_to(user, :message, target: "message", renderable: MessageComponent.new)
    end
end

Message.suppressing_turbo_broadcasts do
  Message.create!(board: board) # This won't broadcast the replace action
end

Defined Under Namespace

Modules: ClassMethods, TestHelper

Instance Method Summary collapse

Instance Method Details

#broadcast_action(action, target: broadcast_target_default, attributes: {}, **rendering) ⇒ Object

Same as #broadcast_action_to, but the designated stream is automatically set to the current model.



388
389
390
# File 'app/models/concerns/turbo/broadcastable.rb', line 388

def broadcast_action(action, target: broadcast_target_default, attributes: {}, **rendering)
  broadcast_action_to self, action: action, target: target, attributes: attributes, **rendering
end

#broadcast_action_later(action:, target: broadcast_target_default, attributes: {}, **rendering) ⇒ Object

Same as #broadcast_action_later_to, but the designated stream is automatically set to the current model.



448
449
450
# File 'app/models/concerns/turbo/broadcastable.rb', line 448

def broadcast_action_later(action:, target: broadcast_target_default, attributes: {}, **rendering)
  broadcast_action_later_to self, action: action, target: target, attributes: attributes, **rendering
end

#broadcast_action_later_to(*streamables, action:, target: broadcast_target_default, attributes: {}, **rendering) ⇒ Object

Same as broadcast_action_to but run asynchronously via a Turbo::Streams::BroadcastJob.



443
444
445
# File 'app/models/concerns/turbo/broadcastable.rb', line 443

def broadcast_action_later_to(*streamables, action:, target: broadcast_target_default, attributes: {}, **rendering)
  Turbo::StreamsChannel.broadcast_action_later_to(*streamables, action: action, target: target, attributes: attributes, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end

#broadcast_action_to(*streamables, action:, target: broadcast_target_default, attributes: {}, **rendering) ⇒ Object

Broadcast a named action, allowing for dynamic dispatch, instead of using the concrete action methods. Examples:

# Sends <turbo-stream action="prepend" target="clearances"><template><div id="clearance_5">My Clearance</div></template></turbo-stream>
# to the stream named "identity:2:clearances"
clearance.broadcast_action_to examiner.identity, :clearances, action: :prepend, target: "clearances"


383
384
385
# File 'app/models/concerns/turbo/broadcastable.rb', line 383

def broadcast_action_to(*streamables, action:, target: broadcast_target_default, attributes: {}, **rendering)
  Turbo::StreamsChannel.broadcast_action_to(*streamables, action: action, target: target, attributes: attributes, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end

#broadcast_after_to(*streamables, target:, **rendering) ⇒ Object

Insert a rendering of this broadcastable model after the target identified by it’s dom id passed as target for subscribers of the stream name identified by the passed streamables. The rendering parameters can be set by appending named arguments to the call. Examples:

# Sends <turbo-stream action="after" target="clearance_5"><template><div id="clearance_6">My Clearance</div></template></turbo-stream>
# to the stream named "identity:2:clearances"
clearance.broadcast_after_to examiner.identity, :clearances, target: "clearance_5"

# Sends <turbo-stream action="after" target="clearance_5"><template><div id="clearance_6">Other partial</div></template></turbo-stream>
# to the stream named "identity:2:clearances"
clearance.broadcast_after_to examiner.identity, :clearances, target: "clearance_5",
  partial: "clearances/other_partial", locals: { a: 1 }


319
320
321
# File 'app/models/concerns/turbo/broadcastable.rb', line 319

def broadcast_after_to(*streamables, target:, **rendering)
  Turbo::StreamsChannel.broadcast_after_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
end

#broadcast_append(target: broadcast_target_default, **rendering) ⇒ Object

Same as #broadcast_append_to, but the designated stream is automatically set to the current model.



340
341
342
# File 'app/models/concerns/turbo/broadcastable.rb', line 340

def broadcast_append(target: broadcast_target_default, **rendering)
  broadcast_append_to self, target: target, **rendering
end

#broadcast_append_later(target: broadcast_target_default, **rendering) ⇒ Object

Same as #broadcast_append_later_to, but the designated stream is automatically set to the current model.



418
419
420
# File 'app/models/concerns/turbo/broadcastable.rb', line 418

def broadcast_append_later(target: broadcast_target_default, **rendering)
  broadcast_append_later_to self, target: target, **rendering
end

#broadcast_append_later_to(*streamables, target: broadcast_target_default, **rendering) ⇒ Object

Same as broadcast_append_to but run asynchronously via a Turbo::Streams::BroadcastJob.



413
414
415
# File 'app/models/concerns/turbo/broadcastable.rb', line 413

def broadcast_append_later_to(*streamables, target: broadcast_target_default, **rendering)
  Turbo::StreamsChannel.broadcast_append_later_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end

#broadcast_append_to(*streamables, target: broadcast_target_default, **rendering) ⇒ Object

Append a rendering of this broadcastable model to the target identified by it’s dom id passed as target for subscribers of the stream name identified by the passed streamables. The rendering parameters can be set by appending named arguments to the call. Examples:

# Sends <turbo-stream action="append" target="clearances"><template><div id="clearance_5">My Clearance</div></template></turbo-stream>
# to the stream named "identity:2:clearances"
clearance.broadcast_append_to examiner.identity, :clearances, target: "clearances"

# Sends <turbo-stream action="append" target="clearances"><template><div id="clearance_5">Other partial</div></template></turbo-stream>
# to the stream named "identity:2:clearances"
clearance.broadcast_append_to examiner.identity, :clearances, target: "clearances",
  partial: "clearances/other_partial", locals: { a: 1 }


335
336
337
# File 'app/models/concerns/turbo/broadcastable.rb', line 335

def broadcast_append_to(*streamables, target: broadcast_target_default, **rendering)
  Turbo::StreamsChannel.broadcast_append_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end

#broadcast_before_to(*streamables, target:, **rendering) ⇒ Object

Insert a rendering of this broadcastable model before the target identified by it’s dom id passed as target for subscribers of the stream name identified by the passed streamables. The rendering parameters can be set by appending named arguments to the call. Examples:

# Sends <turbo-stream action="before" target="clearance_5"><template><div id="clearance_4">My Clearance</div></template></turbo-stream>
# to the stream named "identity:2:clearances"
clearance.broadcast_before_to examiner.identity, :clearances, target: "clearance_5"

# Sends <turbo-stream action="before" target="clearance_5"><template><div id="clearance_4">Other partial</div></template></turbo-stream>
# to the stream named "identity:2:clearances"
clearance.broadcast_before_to examiner.identity, :clearances, target: "clearance_5",
  partial: "clearances/other_partial", locals: { a: 1 }


303
304
305
# File 'app/models/concerns/turbo/broadcastable.rb', line 303

def broadcast_before_to(*streamables, target:, **rendering)
  Turbo::StreamsChannel.broadcast_before_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering))
end

#broadcast_prepend(target: broadcast_target_default, **rendering) ⇒ Object

Same as #broadcast_prepend_to, but the designated stream is automatically set to the current model.



361
362
363
# File 'app/models/concerns/turbo/broadcastable.rb', line 361

def broadcast_prepend(target: broadcast_target_default, **rendering)
  broadcast_prepend_to self, target: target, **rendering
end

#broadcast_prepend_later(target: broadcast_target_default, **rendering) ⇒ Object

Same as #broadcast_prepend_later_to, but the designated stream is automatically set to the current model.



428
429
430
# File 'app/models/concerns/turbo/broadcastable.rb', line 428

def broadcast_prepend_later(target: broadcast_target_default, **rendering)
  broadcast_prepend_later_to self, target: target, **rendering
end

#broadcast_prepend_later_to(*streamables, target: broadcast_target_default, **rendering) ⇒ Object

Same as broadcast_prepend_to but run asynchronously via a Turbo::Streams::BroadcastJob.



423
424
425
# File 'app/models/concerns/turbo/broadcastable.rb', line 423

def broadcast_prepend_later_to(*streamables, target: broadcast_target_default, **rendering)
  Turbo::StreamsChannel.broadcast_prepend_later_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end

#broadcast_prepend_to(*streamables, target: broadcast_target_default, **rendering) ⇒ Object

Prepend a rendering of this broadcastable model to the target identified by it’s dom id passed as target for subscribers of the stream name identified by the passed streamables. The rendering parameters can be set by appending named arguments to the call. Examples:

# Sends <turbo-stream action="prepend" target="clearances"><template><div id="clearance_5">My Clearance</div></template></turbo-stream>
# to the stream named "identity:2:clearances"
clearance.broadcast_prepend_to examiner.identity, :clearances, target: "clearances"

# Sends <turbo-stream action="prepend" target="clearances"><template><div id="clearance_5">Other partial</div></template></turbo-stream>
# to the stream named "identity:2:clearances"
clearance.broadcast_prepend_to examiner.identity, :clearances, target: "clearances",
  partial: "clearances/other_partial", locals: { a: 1 }


356
357
358
# File 'app/models/concerns/turbo/broadcastable.rb', line 356

def broadcast_prepend_to(*streamables, target: broadcast_target_default, **rendering)
  Turbo::StreamsChannel.broadcast_prepend_to(*streamables, target: target, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end

#broadcast_refreshObject

Same as #broadcast_refresh_to, but the designated stream is automatically set to the current model.



374
375
376
# File 'app/models/concerns/turbo/broadcastable.rb', line 374

def broadcast_refresh
  broadcast_refresh_to self
end

#broadcast_refresh_laterObject

Same as #broadcast_refresh_later_to, but the designated stream is automatically set to the current model.



438
439
440
# File 'app/models/concerns/turbo/broadcastable.rb', line 438

def broadcast_refresh_later
  broadcast_refresh_later_to self
end

#broadcast_refresh_later_to(*streamables) ⇒ Object

Same as broadcast_refresh_to but run asynchronously via a Turbo::Streams::BroadcastJob.



433
434
435
# File 'app/models/concerns/turbo/broadcastable.rb', line 433

def broadcast_refresh_later_to(*streamables)
  Turbo::StreamsChannel.broadcast_refresh_later_to(*streamables, request_id: Turbo.current_request_id) unless suppressed_turbo_broadcasts?
end

#broadcast_refresh_to(*streamables) ⇒ Object

Broadcast a “page refresh” to the stream name identified by the passed streamables. Example:

# Sends <turbo-stream action="refresh"></turbo-stream> to the stream named "identity:2:clearances"
clearance.broadcast_refresh_to examiner.identity, :clearances


369
370
371
# File 'app/models/concerns/turbo/broadcastable.rb', line 369

def broadcast_refresh_to(*streamables)
  Turbo::StreamsChannel.broadcast_refresh_to(*streamables) unless suppressed_turbo_broadcasts?
end

#broadcast_removeObject

Same as #broadcast_remove_to, but the designated stream is automatically set to the current model.



249
250
251
# File 'app/models/concerns/turbo/broadcastable.rb', line 249

def broadcast_remove
  broadcast_remove_to self
end

#broadcast_remove_to(*streamables, target: self) ⇒ Object

Remove this broadcastable model from the dom for subscribers of the stream name identified by the passed streamables. Example:

# Sends <turbo-stream action="remove" target="clearance_5"></turbo-stream> to the stream named "identity:2:clearances"
clearance.broadcast_remove_to examiner.identity, :clearances


244
245
246
# File 'app/models/concerns/turbo/broadcastable.rb', line 244

def broadcast_remove_to(*streamables, target: self)
  Turbo::StreamsChannel.broadcast_remove_to(*streamables, target: target) unless suppressed_turbo_broadcasts?
end

#broadcast_render(**rendering) ⇒ Object

Render a turbo stream template with this broadcastable model passed as the local variable. Example:

# Template: entries/_entry.turbo_stream.erb
<%= turbo_stream.remove entry %>

<%= turbo_stream.append "entries", entry if entry.active? %>

Sends:

<turbo-stream action="remove" target="entry_5"></turbo-stream>
<turbo-stream action="append" target="entries"><template><div id="entry_5">My Entry</div></template></turbo-stream>

…to the stream named “entry:5”.

Note that rendering inline via this method will cause template rendering to happen synchronously. That is usually not desireable for model callbacks, certainly not if those callbacks are inside of a transaction. Most of the time you should be using broadcast_render_later, unless you specifically know why synchronous rendering is needed.



469
470
471
# File 'app/models/concerns/turbo/broadcastable.rb', line 469

def broadcast_render(**rendering)
  broadcast_render_to self, **rendering
end

#broadcast_render_later(**rendering) ⇒ Object

Same as broadcast_render_to but run asynchronously via a Turbo::Streams::BroadcastJob.



484
485
486
# File 'app/models/concerns/turbo/broadcastable.rb', line 484

def broadcast_render_later(**rendering)
  broadcast_render_later_to self, **rendering
end

#broadcast_render_later_to(*streamables, **rendering) ⇒ Object

Same as broadcast_render_later but run with the added option of naming the stream using the passed streamables.



490
491
492
# File 'app/models/concerns/turbo/broadcastable.rb', line 490

def broadcast_render_later_to(*streamables, **rendering)
  Turbo::StreamsChannel.broadcast_render_later_to(*streamables, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end

#broadcast_render_to(*streamables, **rendering) ⇒ Object

Same as broadcast_render but run with the added option of naming the stream using the passed streamables.

Note that rendering inline via this method will cause template rendering to happen synchronously. That is usually not desireable for model callbacks, certainly not if those callbacks are inside of a transaction. Most of the time you should be using broadcast_render_later_to, unless you specifically know why synchronous rendering is needed.



479
480
481
# File 'app/models/concerns/turbo/broadcastable.rb', line 479

def broadcast_render_to(*streamables, **rendering)
  Turbo::StreamsChannel.broadcast_render_to(*streamables, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end

#broadcast_replace(**rendering) ⇒ Object

Same as #broadcast_replace_to, but the designated stream is automatically set to the current model.



268
269
270
# File 'app/models/concerns/turbo/broadcastable.rb', line 268

def broadcast_replace(**rendering)
  broadcast_replace_to self, **rendering
end

#broadcast_replace_later(**rendering) ⇒ Object

Same as #broadcast_replace_later_to, but the designated stream is automatically set to the current model.



398
399
400
# File 'app/models/concerns/turbo/broadcastable.rb', line 398

def broadcast_replace_later(**rendering)
  broadcast_replace_later_to self, **rendering
end

#broadcast_replace_later_to(*streamables, **rendering) ⇒ Object

Same as broadcast_replace_to but run asynchronously via a Turbo::Streams::BroadcastJob.



393
394
395
# File 'app/models/concerns/turbo/broadcastable.rb', line 393

def broadcast_replace_later_to(*streamables, **rendering)
  Turbo::StreamsChannel.broadcast_replace_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end

#broadcast_replace_to(*streamables, **rendering) ⇒ Object

Replace this broadcastable model in the dom for subscribers of the stream name identified by the passed streamables. The rendering parameters can be set by appending named arguments to the call. Examples:

# Sends <turbo-stream action="replace" target="clearance_5"><template><div id="clearance_5">My Clearance</div></template></turbo-stream>
# to the stream named "identity:2:clearances"
clearance.broadcast_replace_to examiner.identity, :clearances

# Sends <turbo-stream action="replace" target="clearance_5"><template><div id="clearance_5">Other partial</div></template></turbo-stream>
# to the stream named "identity:2:clearances"
clearance.broadcast_replace_to examiner.identity, :clearances, partial: "clearances/other_partial", locals: { a: 1 }


263
264
265
# File 'app/models/concerns/turbo/broadcastable.rb', line 263

def broadcast_replace_to(*streamables, **rendering)
  Turbo::StreamsChannel.broadcast_replace_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end

#broadcast_update(**rendering) ⇒ Object

Same as #broadcast_update_to, but the designated stream is automatically set to the current model.



287
288
289
# File 'app/models/concerns/turbo/broadcastable.rb', line 287

def broadcast_update(**rendering)
  broadcast_update_to self, **rendering
end

#broadcast_update_later(**rendering) ⇒ Object

Same as #broadcast_update_later_to, but the designated stream is automatically set to the current model.



408
409
410
# File 'app/models/concerns/turbo/broadcastable.rb', line 408

def broadcast_update_later(**rendering)
  broadcast_update_later_to self, **rendering
end

#broadcast_update_later_to(*streamables, **rendering) ⇒ Object

Same as broadcast_update_to but run asynchronously via a Turbo::Streams::BroadcastJob.



403
404
405
# File 'app/models/concerns/turbo/broadcastable.rb', line 403

def broadcast_update_later_to(*streamables, **rendering)
  Turbo::StreamsChannel.broadcast_update_later_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end

#broadcast_update_to(*streamables, **rendering) ⇒ Object

Update this broadcastable model in the dom for subscribers of the stream name identified by the passed streamables. The rendering parameters can be set by appending named arguments to the call. Examples:

# Sends <turbo-stream action="update" target="clearance_5"><template><div id="clearance_5">My Clearance</div></template></turbo-stream>
# to the stream named "identity:2:clearances"
clearance.broadcast_update_to examiner.identity, :clearances

# Sends <turbo-stream action="update" target="clearance_5"><template><div id="clearance_5">Other partial</div></template></turbo-stream>
# to the stream named "identity:2:clearances"
clearance.broadcast_update_to examiner.identity, :clearances, partial: "clearances/other_partial", locals: { a: 1 }


282
283
284
# File 'app/models/concerns/turbo/broadcastable.rb', line 282

def broadcast_update_to(*streamables, **rendering)
  Turbo::StreamsChannel.broadcast_update_to(*streamables, target: self, **broadcast_rendering_with_defaults(rendering)) unless suppressed_turbo_broadcasts?
end