In this article, we will learn how to manage server-side and client-side form validations according to our requirements and how we manage this at the application level.
Recently I have worked on form-validation systems in a couple of modern Rails apps, and I learned a few things along the way I would like to share.
Example and source code
I prepared a demo project here. It’s a Rails 5.2 application with a scaffold generated for managing users. A user has 2 attributes: name and email. The validations are:
Both name and email are required: to exercise client-side validations.
The email has to be unique, to exercise server-side validations.
Server-side validations with Rails
I love Rails validations. They are a powerful mechanism to capture your domain model validation rules, which are essential components of any app. For our example, we can capture the model constraints with something like this:
class User < ApplicationRecord
validates :name, :email, presence: true
validates :email, uniqueness: true
end
A common Rails pattern for dealing with form errors is re-rendering the form with the invalid model carrying the errors to inform about them:
class UsersController < ApplicationController
...
def create
@user = User.new(user_params)
if @user.save
redirect_to @user, notice: 'User was successfully created.'
else
render :new
end
end
def update
if @user.update(user_params)
redirect_to @user, notice: 'User was successfully updated.'
else
render :edit
end
end
...
end
If you are going to use this approach there is a problem with it how we can show errors next to fields.
For dealing with the problem, you can customize ActionView::Base.field_error_proc which is a block of code that Rails uses to render fields with errors. By default, it will wrap them in a div.field_with_errors tag. You can configure it to render the same structure as client-side validations. In our example:
Invalid fields are marked with a .invalid class
Information about the error is shown in a p.error element next to the invalid field.
# Place this code in a initializer.
# E.g: config/initializers/form_errors.rb
ActionView::Base.field_error_proc = Proc.new do |html_tag, instance_tag|
fragment = Nokogiri::HTML.fragment(html_tag)
field = fragment.at('input,select,textarea')
html = if field
field['class'] = "#{field['class']} invalid"
html = <<-HTML
#{fragment&.to_s}
<p class="error">#{instance_tag&.error_message&.first}</p>
HTML
html
else
html_tag
end
html.html_safe
end
CSS — You can customize it according to your requirements.
# app/assets/stylesheets/application.css
.field p{
margin-top: 0;
}
.error{
color: red;
}
.invalid{
border: 1px solid red;
}
The new form validations look like this:
we will have a form that validates duplicated emails consistently with our system.
What I love about this approach is that you don’t need to do any extra work to show model errors in your forms. Just add model validations.
Client side validations with HTML 5
Form validations are captured in the HTML markup. You can read a nice reference here. For our purposes, we will modify the generated scaffold form to use them:
<%= form.text_field :name, required: true %>
...
<%= form.email_field :email, required: true %>
With this in place, the browser won’t allow submitting invalid data, and it will show an error message based on the kind of validation.
This works but you probably won’t love how it looks, and you can’t style it at all.
I like server-side validation because it is not dependent on the client machine or browser or OS.
Conclusions
The discussed approach works great because, with a little bit of infrastructure in place, it lets you express your form validations very succinctly while being robust and comprehensive:
Use HTML5 form validations when they cover the validation you need
Use model validations in your server. This is something you want to do nevertheless.
If this guide has been helpful to you and your team please share it with others!
Top comments (1)
You made my day <3