Module: Turbo::Broadcastable

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

Overview

Turbo streams can be broadcast directly from models that include this module (this is automatically done for Active Records). This makes it convenient to execute both synchronous and asynchronous updates, and render directly from callbacks in models or from controllers or jobs that act on those models. Here’s an example:

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

  after_create_commit :broadcast_later

  private
    def broadcast_later
      broadcast_prepend_later_to examiner.identity, :clearances
    end
end

This is an example from [HEY](hey.com), and the clearance is the model that drives [the screener](hey.com/features/the-screener/), which gives users the power to deny first-time senders (petitioners) access to their attention (as the examiner). When a new clearance is created upon receipt of an email from a first-time sender, that’ll trigger the call to broadcast_later, which in turn invokes broadcast_prepend_later_to.

That method enqueues a Turbo::Streams::ActionBroadcastJob for the prepend, which will render the partial for clearance (it knows which by calling Clearance#to_partial_path, which in this case returns clearances/_clearance.html.erb), send that to all users that have subscribed to updates (using turbo_stream_from(examiner.identity, :clearances) in a view) using the Turbo::StreamsChannel under the stream name derived from [ examiner.identity, :clearances ], and finally prepend the result of that partial rendering to the target identified with the dom id “clearances” (which is derived by default from the plural model name of the model, but can be overwritten).

You can also choose to render html instead of a partial inside of a broadcast you do this by passing the ‘html:` option to any broadcast method that accepts the **rendering argument. Example:

class Message < ApplicationRecord
  belongs_to :user

  after_create_commit :update_message_count

  private
    def update_message_count
      broadcast_update_to(user, :messages, target: "message-count", html: "<p> #{user.messages.count} </p>")
    end
end

If you want to render a template instead of a partial, e.g. (‘messages/index’ or ‘messages/show’), you can use the ‘template:` option. Again, only to any broadcast method that accepts the `**rendering` argument. Example:

class Message < ApplicationRecord
  belongs_to :user

  after_create_commit :update_message

  private
    def update_message
      broadcast_replace_to(user, :message, target: "message", template: "messages/show", locals: { message: self })
    end
end

If you want to render a renderable object you can use the ‘renderable:` option.

class Message < ApplicationRecord
  belongs_to :user

  after_create_commit :update_message

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

There are four basic actions you can broadcast: remove, replace, append, and prepend. As a rule, you should use the _later versions of everything except for remove when broadcasting within a real-time path, like a controller or model, since all those updates require a rendering step, which can slow down execution. You don’t need to do this for remove, since only the dom id for the model is used.

In addition to the four basic actions, you can also use broadcast_render, broadcast_render_to broadcast_render_later, and broadcast_render_later_to to render a turbo stream template with multiple actions.

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.



314
315
316
# File 'app/models/concerns/turbo/broadcastable.rb', line 314

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.



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

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.



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

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"


309
310
311
# File 'app/models/concerns/turbo/broadcastable.rb', line 309

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 }


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

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.



271
272
273
# File 'app/models/concerns/turbo/broadcastable.rb', line 271

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.



345
346
347
# File 'app/models/concerns/turbo/broadcastable.rb', line 345

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.



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

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 }


266
267
268
# File 'app/models/concerns/turbo/broadcastable.rb', line 266

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 }


234
235
236
# File 'app/models/concerns/turbo/broadcastable.rb', line 234

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.



292
293
294
# File 'app/models/concerns/turbo/broadcastable.rb', line 292

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.



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

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.



350
351
352
# File 'app/models/concerns/turbo/broadcastable.rb', line 350

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 }


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

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



300
301
302
# File 'app/models/concerns/turbo/broadcastable.rb', line 300

def broadcast_refresh
  broadcast_refresh_to self
end

#broadcast_refresh_laterObject



363
364
365
# File 'app/models/concerns/turbo/broadcastable.rb', line 363

def broadcast_refresh_later
  broadcast_refresh_later_to self
end

#broadcast_refresh_later_to(*streamables) ⇒ Object



359
360
361
# File 'app/models/concerns/turbo/broadcastable.rb', line 359

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



296
297
298
# File 'app/models/concerns/turbo/broadcastable.rb', line 296

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.



180
181
182
# File 'app/models/concerns/turbo/broadcastable.rb', line 180

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


175
176
177
# File 'app/models/concerns/turbo/broadcastable.rb', line 175

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.



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

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

#broadcast_render_later(**rendering) ⇒ Object

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



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

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.



415
416
417
# File 'app/models/concerns/turbo/broadcastable.rb', line 415

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.



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

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.



199
200
201
# File 'app/models/concerns/turbo/broadcastable.rb', line 199

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.



325
326
327
# File 'app/models/concerns/turbo/broadcastable.rb', line 325

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.



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

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 }


194
195
196
# File 'app/models/concerns/turbo/broadcastable.rb', line 194

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.



218
219
220
# File 'app/models/concerns/turbo/broadcastable.rb', line 218

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.



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

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.



330
331
332
# File 'app/models/concerns/turbo/broadcastable.rb', line 330

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 }


213
214
215
# File 'app/models/concerns/turbo/broadcastable.rb', line 213

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