erb ruby 67 lines · 3 tabs

Update an error summary area via turbo_stream.update

Shared by codesnips Jan 2026
3 tabs
<h1>Create your account</h1>

<%= render "error_summary", record: @user %>

<%= form_with model: @user, url: signups_path do |f| %>
  <div class="field">
    <%= f.label :email %>
    <%= f.email_field :email, id: "user_email" %>
  </div>

  <div class="field">
    <%= f.label :password %>
    <%= f.password_field :password, id: "user_password" %>
  </div>

  <%= f.submit "Sign up" %>
<% end %>
3 files · erb, ruby Explain with highlit

This snippet shows how a server-rendered error summary block is kept in sync with form validation using Turbo Streams instead of a full page reload. The pattern is useful when a form submits over turbo_stream (the default for Rails 7 form submissions) and the model fails validation: rather than re-rendering the whole page, the controller pushes a targeted turbo_stream.update that swaps only the contents of a stable DOM node.

In _error_summary partial the summary is wrapped in an element whose id is derived from the model via dom_id(record, :errors), giving a predictable target like signup_errors. When the record has no errors the partial renders the container empty but still present, which matters because Turbo can only update a node that already exists in the DOM. The block carries role="alert" and aria-live="assertive" so screen readers announce the messages as they appear, and each message links back to the offending field via full_messages and anchor hrefs.

In SignupsController the create action attempts to persist the user. On success it responds with a redirect that Turbo follows normally. On failure it renders a turbo_stream response whose single stream targets the same dom_id, replacing the inner HTML with the freshly rendered partial that now contains the validation errors. Because the response status is :unprocessable_entity, Turbo treats it as a form error and processes the stream rather than discarding it — returning 200 here would cause Turbo to ignore the body.

The key idea is that the error region is addressed by a stable identifier shared between the initial page render and the update, so turbo_stream.update can find and replace it precisely. This avoids flicker, preserves the rest of the page including untouched inputs, and keeps validation logic entirely on the server. The trade-off is that the summary must always be rendered as an empty shell up front; forgetting that shell is the most common pitfall, since Turbo silently drops updates aimed at a missing target. This approach scales well to multiple independent regions on one page, each with its own dom_id.


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
html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Semantic HTML Example</title>

Semantic HTML5 elements and accessibility best practices

html html5 semantics
by Alex Chang 2 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

Share this code

Here's the card — post it anywhere.

Update an error summary area via turbo_stream.update — share card
Link copied