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.
Related snips
class CommentsController < ApplicationController
before_action :set_post
def create
@comment = @post.comments.build(comment_params)
System test: asserting Turbo Stream responses
class Post < ApplicationRecord
belongs_to :author, class_name: 'User'
has_many :comments, dependent: :destroy
scope :published, -> { where.not(published_at: nil).where('published_at <= ?', Time.current) }
scope :draft, -> { where(published_at: nil) }
ActiveRecord scopes for reusable query logic
module Api
module V1
class UsersController < BaseController
def show
user = User.includes(:profile).find(params[:id])
ETags for conditional requests and caching
class PostsController < ApplicationController
def index
@posts = Post.includes(:author)
.order(created_at: :desc)
.page(params[:page])
.per(10)
Turbo Frames: infinite scroll with lazy-loading frame
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
<form data-controller="query-sync" data-action="change->query-sync#apply">
<select name="status" class="rounded border p-2">
<option value="">Any</option>
<option value="open">Open</option>
<option value="closed">Closed</option>
</select>
Filter UI that syncs query params via Stimulus (no front-end router)
Share this code
Here's the card — post it anywhere.