Motivation
Recently I released a series of posts that walks through how to build a real-world database-backed contact form in a Ruby on Rails application. While working on that series, I decided to also write this shorter post to expand on a topic I touch on, namely, how to display error messages on an Ajax Rails form.
Note: For a high-level overview of Ajax, check out this post.
What am I talking about? Let’s take the following form built with Rails, Ajax, and Bootstrap as an example:
This form uses Ajax to send HTTP POST requests to a model table in a Rails database. In my example, the model is named Messages
.
In the Messages
model I wrote validations to require a correct email format in the email field, as well as a minimum message length in the message field. The validations look like this:
class Message < ActiveRecord::Base
validates :email, :presence => {message: "Email address cannot be blank."}, email_format: {message: "Please enter a valid email address."}, :on => :create
validates :content, :presence => {message: "Message content cannot be blank."}, :length => { :minimum => 2, message: "Message is too short."}, :on => :create
end
See those {message: “...”}
lines? Users need to see those (if and when their form submissions trigger the validation constraints), and in this post I will walk through three ways to display validation error messages to users.
Walkthrough
Method 1: Display All Errors
Step 1: The Form View
Open the form html view (or in my case, the form html partial view). In my example my view file is saved as _form.html.erb
in MyAppName/views/messages
. Add a <span></span>
that has the Bootstrap class .help-block
to the bottom of the form, right before the <% end %>
tag.
<%= form_for @message, :html => {:class => "form-horizontal"}, remote: true do |f| %>
<div class="form-group">
<%= f.label :email, "Email address:", class: "col-sm-2 control-label" %>
<div class="col-sm-10">
<%= f.email_field :email, class: "form-control", :placeholder => "Email"%>
</div>
</div>
...
<span id="helpBlock" class="help-block"></span>
<% end %>
Step 2: The Controller
Open the model controller. In my example my model controller is saved as messages_controller.rb
in MyAppName/app/controllers
. Add the following code:
def create
@message=Message.new(message_params)
respond_to do |format|
if @message.save
format.js
else
format.js
@message.errors.any?
@message.errors.each do |key, value|
end
end
end
end
Step 3: The JavaScript Partial View
Open the JavaScript partial view that is rendered by the Rails create
action. In my example it is saved as create.js.erb
in MyAppName/app/views/messages
. Add the following code:
//remove old validation messages
$(".notice").remove();
//display new validation messages
<% @message.errors.each do |key, value| %>
$(“#helpBlock”).append(‘<div class=”notice”><%= escape_javascript(value)%></div>’);
<% end %>
OR, if you’d like to display the key-value pairs of the @message.errors
hash, then use this code:
//remove old validation messages
$(".notice").remove();
//display new validation messages
<% @message.errors.full_messages.each do |message| %>
$(“#helpBlock”).append(‘<div class=”notice”><%= escape_javascript(message)%></div>’);
<% end %>
Note: If you choose this second variation, there is no need to modify the controller code I wrote above because the create.js.erb
view will render properly as long as one of the variables @message.errors.each do |key, value|
or @message.errors.full_messages.each do |message|
is provided to it by the controller.
The Result
The Result (if you chose the key-value modification)
Though this first method, both modified and unmodified, accomplishes the goal of displaying validation messages to the end user, I think it bombards the user with too much negative information. The next method is user-friendlier.
Method 2: Display Only The First Error
Step 1: The Form View
…is unchanged from the previous method.
Step 2: The Controller
Initialize an empty @errorMessage
array and push in values from the @message.errors
hash.
def create
@message=Message.new(message_params)
respond_to do |format|
@errorMessage = []
if @message.save
format.js
else
format.js
@message.errors.any?
@message.errors.each do |key, value|
@errorMessge.push(value)
end
end
end
end
Step 3: The JavaScript Partial View
Display the first element of the @errorMessage
array.
//remove old validation messages
$(".notice").remove();
//display new validation messages
$(“#helpBlock”).append(‘<div class=”notice”><%= escape_javascript(@errorMessage[0])%></div>’);
The Result
This method is a big improvement over the previous method, but it’s disadvantage is that the end user isn’t fully aware of all the changes that need to be made in order for the form to pass all validation checks. I prefer the next method because it brings attention to each input field that triggered an error without bombarding the user with too much negative information.
Method 3: Display Errors Near Input Fields and use the Bootstrap .has-error Class
In this method I will use the Bootstrap .has-error
class in combination with the Bootstrap .form-group
class to bring attention to specific fields that triggered validation messages.
Step 1: The Form View
In the previous two methods I included one span with the Bootstrap .help-block
class at bottom of the form. In this method I will instead include two unique .help-block
spans beneath each input field.
<%= form_for @message, :html => {:class => "form-horizontal"}, remote: true do |f| %>
<div class="form-group">
<%= f.label :email, "Email address:", class: "col-sm-2 control-label" %>
<div class="col-sm-10">
<%= f.email_field :email, class: "form-control", :placeholder => "Email"%>
<span id = "emailBlock" class="help-block"></span>
</div>
</div>
<div class="form-group">
<%= f.label :content, "Message:", class: "col-sm-2 control-label" %>
<div class="col-sm-10">
<%= f.text_area :content, class: "form-control", rows: 1, :placeholder => "Your message"%>
<span id = "contentBlock" class="help-block"></span>
</div>
</div>
<% end %>
Step 2: The Controller
Initialize an empty error message array for each key within the @message.errors
hash. In this example I have two keys: :email
and :content
. Then, push values from the @message.errors
hash into the corresponding array.
def create
@message=Message.new(message_params)
respond_to do |format|
@errorEmail = []
@errorContent = []
if @message.save
format.js
else
format.js
@message.errors.any?
if (@message.errors["email"] != nil)
@errorName.push(@message.errors["name"][0])
end
if (@message.errors["content"] != nil)
@errorContent.push(@message.errors["content"][0])
end
end
end
end
Step 3: The JavaScript Partial View
Display the first element of each array inside the two .help-block
spans that were added in Step 1. Then use jQuery to add the Bootstrap .has-error
class to the appropriate <div></div>
’s that have the Bootstrap .form-group
class.
//remove old validation messages
$(".notice").remove();
//remove old instances of the .has-error class
$(".form-group").removeClass("has-error");
//display new validation messages and add the Bootstrap .has-error class
$(“#emailBlock”).append(‘<div class=”notice”><%= escape_javascript(@errorEmail[0])%></div>’);
$("#emailBlock:contains(.)").closest(".form-group").addClass("has-error");
$(“#contentBlock”).append(‘<div class=”notice”><%= escape_javascript(@errorContent[0])%></div>’);
$("#contentBlock:contains(.)").closest(".form-group").addClass("has-error");
The Result
Wrap-Up
There you have it: three methods for displaying validation error messages on a Rails form built with Ajax. These three methods each have their own advantages and disadvantages, and which method (or combination of methods) you choose to use should depend on your development and/or user-experience needs.
Did you find this walkthrough helpful?
Do you have questions on the above?
Do you see any errors?
If so, please leave your thoughts in the comments below.