class CommentsController < ApplicationController
def create
@post = Post.find(params[:post_id])
@comment = @post.comments.build(comment_params)
@comment.author = current_user
if @comment.save
respond_to do |format|
format.turbo_stream do
render turbo_stream: [
turbo_stream.append("comments", partial: "comments/comment", locals: { comment: @comment }),
turbo_stream.update("comment_count", html: "#{@post.comments.count} comments"),
turbo_stream.replace("new_comment", partial: "comments/form", locals: { post: @post, comment: Comment.new })
]
end
format.html { redirect_to @post }
end
else
render :new, status: :unprocessable_entity
end
end
private
def comment_params
params.require(:comment).permit(:body)
end
end
<div class="post">
<h1><%= @post.title %></h1>
<%= simple_format @post.body %>
<div class="comments-section mt-8">
<h2 id="comment_count"><%= @post.comments.count %> comments</h2>
<div id="comments" class="space-y-4 my-6">
<%= render @post.comments %>
</div>
<div id="new_comment">
<%= render "comments/form", post: @post, comment: Comment.new %>
</div>
</div>
</div>
Turbo Streams enable surgical DOM updates from the server without writing JavaScript. After a successful form submission, instead of redirecting, I return a Turbo Stream response that appends, prepends, replaces, or removes specific elements. This is perfect for creating items in lists, updating counters, or showing flash messages. Combined with Action Cable, Turbo Streams power real-time collaborative features—when one user creates a comment, all viewers see it instantly via a broadcast. The seven stream actions (append, prepend, replace, update, remove, before, after) cover most UI update patterns. I use the turbo_stream_from helper to subscribe to broadcast channels and turbo_stream.append in controllers to generate the response.