Easy Peasy Form Validation Errors with Rails Turbo Frames (modals)

An abstract representation of a floating web component modal, set against a blurred, minimalistic background, emphasizing modern web design and user interaction with a restrained color palette.

Form Validations (or officially Active Record Validations) are a fundamental feature of the Rails framework since its very beginning.

Throughout this post I will continue using Form Validations, as I not only use them with Active Record objects (eg. Model), but (more often) with Form Objects (eg. SignupForm).

They ensure that only valid data is saved to the database, enforcing rules and constraints on model attributes to maintain data integrity and, also important, improve user experience.

I use Form Validations quite a lot, as it’s a super easy way to provide my app’s users with feedback about what’s incorrect about the data they submitted.

As a little side-note: I mostly use Form Validations for UX reasons when submitting forms, the data integrity is better handled on a database level.

Quick Primer on Form Validations

Rails comes with common built-in validators, such as presence, uniqueness, format, length, and many others.

class Comment < ApplicationRecord
  belongs_to :post

  validates :body, presence: true, length: { minimum: 10 }
end

When you try to save a record, like this Comment without a body, Rails returns an ActiveModel::Errors object to hold errors related to validation failures. In your view you can display them like so:

<% if @comment.errors.any? %>
  <⁠% @comment.errors.full_messages.each do |message| %>
    <%= message %>
  <⁠% end %>
<% end %>

But when you have a form in a modal or slide-over (that both use turbo-frames), you notice that suddenly your app breaks when validation errors appear.

How to Show Form Validations in a Turbo Frame

Let’s see how to get this working with turbo-frames.

The comments form is shown in a modal like below. It’s displayed after clicking a link like this link_to "Comment", new_comment_path, data: {turbo_frame: "modal"}.

# app/views/comments/new.html.erb
<%= ModalComponent.new %>
  <⁠%= component "comments/form", comment: @comment %>
<% end %>

(this is using the Rails Designer ModalComponent)

The controller is as vanilla-Rails as it gets. Just take note of status: :unprocessable_entity part. This sends a 422 status code to the browser that Turbo requires/expects to be able to replace the content with the response.

  # app/controllers/comments_controller.rb
  def create
    @comment = Comment.new(comment_params)

    respond_to do |format|
      if @comment.save
        flash.now[:success] = Created

        format.turbo_stream
        format.html { redirect_to comments_path, notice: "Created" }
      else
        flash.now[:success] = "Error"

        format.turbo_stream { render :new, status: :unprocessable_entity }
        format.html { render :new, status: :unprocessable_entity }
      end
    end
  end

Here the block, for both the html-format and the turbo_stream-format, are the same. For HTML responses it uses the new.html.erb view template. So now create a app/views/comments/new.turbo_stream.html turbo-stream template and you are off to the races.

# app/views/comments/new.turbo_stream.html
<%= turbo_stream.replace "modal" do %>
  <⁠%= ModalComponent.new %>
    <%= component "comments/form", comment: @comment %>
  <% end %>
<⁠% end %>

Notice that the content is almost the same as the app/views/comments/new.html.erb view template. I’m fine with this little duplication.

And that is all that is needed to display Form Validations in your turbo-frame Modals and Slide-overs.

Get UI & Product Engineering Insights for Rails Apps (and product updates!)

Published at . Last updated at . Have suggestions or improvements on this content? Do reach out.

UI components for Ruby on Rails apps

$ 129 one-time
payment

Get Access
  • One-time Payment

  • Access to the Entire Library

  • Built for Ruby on Rails (inc. Rails 8)

  • Designed with Tailwind CSS and Enhanced with Hotwire

  • Updates for 12 months