# Controller responding with Turbo Stream
class PostsController < ApplicationController
def create
@post = Post.new(post_params)
if @post.save
respond_to do |format|
format.turbo_stream do
render turbo_stream: turbo_stream.prepend(
"posts",
partial: "posts/post",
locals: { post: @post }
)
end
format.html { redirect_to @post }
end
else
render :new, status: :unprocessable_entity
end
end
def destroy
@post = Post.find(params[:id])
@post.destroy
respond_to do |format|
format.turbo_stream do
render turbo_stream: turbo_stream.remove(@post)
end
format.html { redirect_to posts_path }
end
end
def update
@post = Post.find(params[:id])
if @post.update(post_params)
respond_to do |format|
format.turbo_stream do
render turbo_stream: [
turbo_stream.replace(
@post,
partial: "posts/post",
locals: { post: @post }
),
turbo_stream.update(
"flash",
partial: "shared/flash",
locals: { message: "Post updated!" }
)
]
end
format.html { redirect_to @post }
end
end
end
end
# Broadcasting Turbo Streams (for real-time updates)
class Post < ApplicationRecord
after_create_commit do
broadcast_prepend_to "posts",
target: "posts",
partial: "posts/post",
locals: { post: self }
end
after_update_commit do
broadcast_replace_to "posts"
end
after_destroy_commit do
broadcast_remove_to "posts"
end
# Broadcast to specific users
after_create_commit do
broadcast_prepend_to(
[user, "posts"],
target: "user_posts",
partial: "posts/post"
)
end
end
# Multiple Turbo Stream actions
def multi_update
render turbo_stream: [
turbo_stream.replace("post_#{@post.id}", partial: "posts/post"),
turbo_stream.update("comments_count", @post.comments.count),
turbo_stream.append("notifications", partial: "shared/notification"),
turbo_stream.remove("draft_indicator")
]
end
<!-- app/views/posts/create.turbo_stream.erb -->
<%= turbo_stream.prepend "posts", @post %>
<%= turbo_stream.update "post_count", Post.count %>
<%= turbo_stream.replace "new_post_form" do %>
<%= render "form", post: Post.new %>
<% end %>
<!-- Multiple targeted updates -->
<%= turbo_stream.replace dom_id(@post) do %>
<%= render @post %>
<% end %>
<%= turbo_stream.update "flash" do %>
<div class="alert alert-success">Post created!</div>
<% end %>
<!-- Turbo Stream from Action Cable -->
<!-- app/views/posts/_post.html.erb -->
<%= turbo_stream_from "posts" %>
<div id="posts">
<%= render @posts %>
</div>
<!-- Subscribes to posts channel, receives real-time updates -->
<!-- Main page with turbo frame -->
<div class="posts">
<%= turbo_frame_tag "posts_list" do %>
<%= render @posts %>
<%= link_to "Load more", posts_path(page: @next_page),
class: "load-more" %>
<% end %>
</div>
<!-- Clicking "Load more" only replaces posts_list frame -->
<!-- Nested frames for independent updates -->
<div class="post">
<h1><%= @post.title %></h1>
<%= turbo_frame_tag dom_id(@post, :comments) do %>
<%= render @post.comments %>
<%= link_to "Load more comments",
post_comments_path(@post, page: @next_page) %>
<% end %>
<%= turbo_frame_tag dom_id(@post, :edit) do %>
<%= link_to "Edit post", edit_post_path(@post) %>
<% end %>
</div>
<!-- Edit form returns matching turbo frame -->
<%= turbo_frame_tag dom_id(@post, :edit) do %>
<%= form_with model: @post do |f| %>
<%= f.text_field :title %>
<%= f.text_area :content %>
<%= f.submit %>
<% end %>
<% end %>
<!-- Breaking out of frame navigation -->
<%= link_to "View all posts", posts_path,
data: { turbo_frame: "_top" } %>
<!-- Lazy loading frames -->
<%= turbo_frame_tag "recommendations",
src: recommendations_path,
loading: :lazy do %>
<p>Loading recommendations...</p>
<% end %>
Hotwire Turbo delivers SPA speed without JavaScript complexity. Turbo Drive accelerates navigation by replacing page body without full reload. Turbo Frames update page sections independently—click a frame link, only that frame refreshes. Turbo Streams push real-time updates from server—append, prepend, replace, remove, update actions. I combine Turbo with Rails—form submissions return Turbo Streams for dynamic UI updates. Turbo Native wraps web apps in native mobile shells. Progressive enhancement is key—apps work without JavaScript. Turbo reduces client-side JavaScript by ~90%, keeping logic server-side. Understanding Turbo Frame navigation rules is crucial—breaking out with data-turbo-frame="_top". Turbo makes reactive UIs accessible to Rails developers without React/Vue complexity.