Previously I said that it’s an awful practice to render a turbo stream broadcast form a model, and suggested doing it from a controller. That’s better, however that makes the controller much more complex. I would like to introduce the Broadcaster pattern.

By moving your broadcasts rendering logic from a controller into the app/services directory, you can:

  • ✅ clean up
  • ✅ test in isolation
  • ✅ trigger from console
  • ✅ reuse in different places

Here’s an example of moving broadcast logic from a controller into app/services/broadcasters/*:

def create
  if @record.save
-    Turbo::StreamsChannel.broadcast_action_later_to(:posts_list, target: :posts, action: :prepend, partial: 'posts/post', locals: { post: post } )
-    Turbo::StreamsChannel.broadcast_update_to(:posts_list, target: :posts_counter, html: Post.count )
+    Broadcasters::Posts::Created.new(post).call
    respond_to do |format|
      format.html
      format.turbo_stream
    end
  end
end

Create a Service Object that would contain the above broadcasting logic:

# app/services/broadcasters/posts/created.rb
class Broadcasters::Posts::Created
  attr_reader :post

  def initialize(post)
    @post = post
  end

  def call
    # as many broadcasts as you wish!
    prepend_post
    update_counter
  end

  def prepend_post
    Turbo::StreamsChannel.broadcast_action_later_to(
      :posts_list,
      target: :posts,
      action: :prepend,
      partial: 'posts/post',
      locals: { post: post }
    )
  end

  def update_counter
    Turbo::StreamsChannel.broadcast_update_to(
      :posts_list,
      target: :posts_counter,
      html: Post.count
    )
  end
end

Now you can trigger this broadcasters from anywhere in your app:

# rails console
post = Post.last
Broadcasters::Posts::Created.new(post).call

That’s it!

P.S. For everything to work, sometimes you might have to include specific Rails helpers in your service object:

class Broadcasters::Posts::Created
+  include ActionView::RecordIdentifier
+  include Turbo::StreamsHelper
+  include Rails.application.routes.url_helpers