ruby erb javascript 83 lines · 4 tabs

Inline form validation feedback via Turbo Streams

Shared by codesnips Jan 2026
4 tabs
class SignupsController < ApplicationController
  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    if @user.save
      redirect_to dashboard_path, notice: "Welcome aboard!"
    else
      respond_to do |format|
        format.turbo_stream { render :create, status: :unprocessable_entity }
        format.html { render :new, status: :unprocessable_entity }
      end
    end
  end

  def validate_field
    attribute = params.require(:attribute).to_sym
    @user = User.new(user_params)
    @user.valid? # populates errors for every attribute
    @attribute = attribute
    render :validate_field
  end

  private

  def user_params
    params.fetch(:user, {}).permit(:email, :username, :password)
  end
end
4 files · ruby, erb, javascript Explain with highlit

This snippet shows how to give a Rails form immediate, per-field validation feedback without a full page reload or a heavy front-end framework, by leaning on Turbo Streams. The idea is that the server remains the single source of truth for validation: it runs the real ActiveRecord validations and streams back only the fragments that need to change. That avoids duplicating validation logic in JavaScript and keeps the error messages consistent with what a final submit would produce.

The SignupsController handles two moments. In create, a valid record redirects as usual, but an invalid one responds with unprocessable_entity and renders a Turbo Stream template instead of a full HTML page. The separate validate_field action powers live, on-blur checking: it assigns only the submitted attributes, calls valid? to populate errors, and re-renders just the affected field wrapper. Because valid? runs the whole validation suite, the action reads a single attribute param to decide which fragment to stream, keeping unrelated fields untouched.

The signup_field partial is the reusable unit of the UI. Each field lives inside a turbo_frame_tag (or a stably-ided div) so a stream can target it with turbo_stream.replace. It renders the input, an is-invalid class when user.errors[attribute] is present, and the message text. Rendering the same partial from the form and from the stream response guarantees the invalid and valid states look identical.

The validate_field turbo_stream template maps errors to DOM updates: it replaces the field wrapper for the validated attribute so the browser swaps in the new markup in place. On create failure a similar template can replace every field plus a summary.

The signup_form Stimulus controller supplies the progressive-enhancement glue. On blur it submits the field to validate_field via fetch with a Turbo Stream Accept header, and Turbo applies the returned stream automatically. The trade-off is an extra request per field interaction, so debouncing and validating on blur rather than every keystroke keeps traffic sane. The pattern shines when validation rules are non-trivial or server-derived (uniqueness, external checks) and duplicating them client-side would be error-prone.


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 form validation feedback via Turbo Streams — share card
Link copied