erb ruby 67 lines · 4 tabs

Inline create form that prepends into a list with Turbo Streams

Shared by codesnips Jan 2026
4 tabs
<%= turbo_stream_from :comments %>

<section class="comments">
  <h2>Discussion</h2>

  <%= turbo_frame_tag "new_comment" do %>
    <%= render "comments/form", comment: Comment.new(post: @post) %>
  <% end %>

  <div id="comments">
    <%= render partial: "comments/comment", collection: @comments %>
  </div>
</section>
4 files · erb, ruby Explain with highlit

This snippet shows the canonical Hotwire pattern for an inline create form that instantly prepends a freshly created record to the top of a list without a full page reload. The three tabs cooperate: the list template establishes the DOM contract with stable IDs, the controller responds to Turbo Stream requests, and the create template describes the two surgical DOM mutations the browser should apply.

In comments/index.html.erb the list is wrapped in a container whose id is comments, matching the dom_id Turbo Streams will target. Each comment is rendered by a partial (comments/_comment.html.erb, referenced implicitly) so the same markup is reused on first paint and on later stream updates — reuse is what keeps the initial HTML and the streamed HTML identical. The turbo_stream_from :comments line subscribes the page to a broadcast channel, but the interesting flow here is the direct response, not broadcasting. The form is built with form_with model: and no local:/data-turbo overrides, so Turbo submits it as a Turbo Stream request by sending an Accept: text/vnd.turbo-stream+html header.

In CommentsController the create action saves the record and then branches on respond_to. The format.turbo_stream branch is the key: Rails automatically looks for create.turbo_stream.erb, so no explicit render is needed on the happy path. The else clause re-renders the form partial with status: :unprocessable_entity inside a stream so validation errors replace the form in place rather than reloading the page. The format.html fallback keeps the endpoint usable without JavaScript, which matters for progressive enhancement and crawlers.

In create.turbo_stream.erb two actions run in sequence. turbo_stream.prepend "comments" inserts the new comment partial at the top of the list, giving the newest-first ordering users expect. turbo_stream.replace "new_comment" swaps the form for a fresh, empty one so the user can immediately post again and any stale error markup is cleared. Targeting by stable DOM id is what makes this robust: the ids in the template must match the stream targets exactly, or the mutation silently does nothing. This approach is ideal when a single client just posted and expects immediate feedback; pair it with broadcast_prepend_to when other connected clients also need the update.


Related snips

ruby
class CommentsController < ApplicationController
  before_action :set_post

  def create
    @comment = @post.comments.build(comment_params)

System test: asserting Turbo Stream responses

rails hotwire turbo
by codesnips 4 tabs
ruby
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

rails activerecord patterns
by Alex Kumar 1 tab
ruby
module Api
  module V1
    class UsersController < BaseController
      def show
        user = User.includes(:profile).find(params[:id])

ETags for conditional requests and caching

rails caching http-caching
by Alex Kumar 1 tab
ruby
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

rails turbo hotwire
by codesnips 4 tabs
ruby
require "csv"

class PeopleCsvStream
  include Enumerable

  HEADERS = %w[id full_name email signed_up_at plan].freeze

Resilient CSV Export as a Streamed Response

rails performance streaming
by codesnips 3 tabs
erb
<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)

rails hotwire stimulus
by Henry Kim 2 tabs

Share this code

Here's the card — post it anywhere.

Inline create form that prepends into a list with Turbo Streams — share card
Link copied