Rails’ turbo streams broadcasts let you update your UI in real time using a range of handy methods. In this series, I’ll break down these methods in pairs—highlighting their similarities and key differences that can significantly impact data consistency and performance.
For example, today’s post compares broadcast_update_to
with broadcast_update_later_to
. How do they work? What’s similar between them, and what sets them apart? When should you pick one over the other? I’m answering these questions for you and for my future self when I inevitably search for a quick refresher. Sound familiar? ✋🏼
Before we dive into the specifics, let’s cover the core concept behind these methods and the key factors affecting data consistency and performance. This foundation will help you decide which method fits best in different scenarios.
The Immediate vs. Delayed Paradigm
Immediate Methods (e.g.
broadcast_update_to
) send the update right away. They’re great when you’re not inside a database transaction or need instant results. However, if used inside a transaction, they might run before the changes are fully saved, potentially broadcasting incomplete data.Delayed Methods (e.g.
broadcast_update_later_to
) wait until the transaction commits (using ActiveJob) before sending the update. This slight delay ensures that only final, committed data is broadcast, helping you avoid race conditions and making your production app more reliable.
Update Methods
broadcast_update_to
What: Sends an update action immediately to the specified target.
Risks: May broadcast uncommitted data if used inside a transaction.
Performance: Runs synchronously, which can block the main thread.
Usage Note: Avoid using this method within a transaction.
Immediate update example:
Turbo::StreamsChannel.broadcast_update_to(
"posts",
target: "post_#{post.id}",
partial: "posts/post",
locals: { post: post }
)
Controller example – immediate update (⚠️ Not recommended within a transaction):
class NotificationsController < ApplicationController
def update_notification_immediate
Notification.transaction do
@notification = Notification.find(params[:id])
@notification.update!(message: "Immediate update!")
# Immediate broadcast: may send uncommitted data if used inside a transaction.
Turbo::StreamsChannel.broadcast_update_to(
"notifications",
target: "notification_#{@notification.id}",
partial: "notifications/notification",
locals: { notification: @notification }
)
end
respond_to do |format|
format.turbo_stream
format.html { redirect_to notifications_path }
end
end
end
broadcast_update_later_to
What: Schedules an update action to occur only after the transaction is committed.
Benefits: Ensures that only fully committed and consistent data is broadcast.
Performance: Runs asynchronously, so your app continues processing without waiting.
Tip: Use this method when updating records within a transaction (e.g., after saving) to ensure data consistency. Make sure your ActiveJob adapter (like Redis) is properly set up in production.
Delayed update example:
ActiveRecord::Base.transaction do
post.save!
Turbo::StreamsChannel.broadcast_update_later_to(
"posts",
target: "post_#{post.id}",
partial: "posts/post",
locals: { post: post }
)
end
Controller example – delayed update:
class NotificationsController < ApplicationController
def update_notification_delayed
Notification.transaction do
@notification = Notification.find(params[:id])
@notification.update!(message: "Delayed update!")
# Delayed broadcast: fires after the transaction commits,
# ensuring only committed data is broadcast.
Turbo::StreamsChannel.broadcast_update_later_to(
"notifications",
target: "notification_#{@notification.id}",
partial: "notifications/notification",
locals: { notification: @notification }
)
end
respond_to do |format|
format.turbo_stream
format.html { redirect_to notifications_path }
end
end
end
Model level vs. Controller broadcasting
There are two common approaches for broadcasting updates:
Controller Broadcasting:
You manually trigger broadcasts in your controller actions.
Pros: Fine-grained control over each action.
Cons: Can lead to duplicated code and requires manual transaction management.
Model Broadcasting (Callbacks):
You handle broadcasts inside model callbacks (e.g. using after_update_commit
), which means broadcasts occur automatically whenever the model updates.
Pros: Keeps broadcast logic encapsulated, resulting in cleaner controllers and safer broadcasts (only after the transaction commits).
Cons: Less granular control in the controller; broadcasts happen on every update unless conditionally suppressed.
Model-level broadcasting example:
class Notification < ApplicationRecord
include Turbo::Broadcastable
# Automatically broadcasts a delayed update when the record is updated.
after_update_commit :broadcast_notification_update
private
def broadcast_notification_update
broadcast_update_later_to(
"notifications",
target: dom_id(self), # Using Rails' dom_id helper for unique targeting
partial: "notifications/notification",
locals: { notification: self }
)
end
end
Controller example when using model-level broadcasting:
class NotificationsController < ApplicationController
def update
@notification = Notification.find(params[:id])
if @notification.update(notification_params)
# No explicit broadcast needed here; it's handled by the model callback.
respond_to do |format|
format.turbo_stream
format.html { redirect_to notifications_path }
end
else
# Handle validation errors.
render :edit, status: :unprocessable_entity
end
end
private
def notification_params
params.require(:notification).permit(:message, :user_id)
end
end
Both approaches (model and controller) work well—it really depends on your project's needs. Controller broadcasting offers granular control, while model broadcasting encapsulates logic and ensures safety by only broadcasting committed data.
Wrapping up
That’s it for today’s deep dive into update methods in turbo streams! We explored:
The differences between immediate
broadcast_update_to
and delayedbroadcast_update_later_to
updates.Practical examples at both the controller and model level.
Next, I’ll break down another Turbo Streams method, perhaps the remove
methods.
Want to learn more, you must check the following turbo stream and all Rails educational resources:
If you found this helpful and want more hands-on examples, subscribe for updates. Your future self (and your code) will thank you!
Thanks for reading, and happy broadcasting, may your streams always be smooth (and your bugs few) !