How do I display Ruby on Rails form validation error messages one at a time?

After experimenting for a few hours I figured it out.

<% if @user.errors.full_messages.any? %>
  <% @user.errors.full_messages.each do |error_message| %>
    <%= error_message if @user.errors.full_messages.first == error_message %> <br />
  <% end %>
<% end %>

Even better:

<%= @user.errors.full_messages.first if @user.errors.any? %>

ActiveRecord stores validation errors in an array called errors. If you have a User model then you would access the validation errors in a given instance like so:

@user = User.create[params[:user]] # create will automatically call validators

if @user.errors.any? # If there are errors, do something

  # You can iterate through all messages by attribute type and validation message
  # This will be something like:
  # attribute = 'name'
  # message = 'cannot be left blank'
  @user.errors.each do |attribute, message|
    # do stuff for each error
  end

  # Or if you prefer, you can get the full message in single string, like so:
  # message = 'Name cannot be left blank'
  @users.errors.full_messages.each do |message|
    # do stuff for each error
  end

  # To get all errors associated with a single attribute, do the following:
  if @user.errors.include?(:name)
    name_errors = @user.errors[:name]

    if name_errors.kind_of?(Array)
      name_errors.each do |error|
        # do stuff for each error on the name attribute
      end
    else
      error = name_errors
      # do stuff for the one error on the name attribute.
    end
  end
end

Of course you can also do any of this in the views instead of the controller, should you want to just display the first error to the user or something.


A better idea,

if you want to put the error message just beneath the text field, you can do like this

.row.spacer20top
  .col-sm-6.form-group
    = f.label :first_name, "*Your First Name:"
    = f.text_field :first_name, :required => true, class: "form-control"
    = f.error_message_for(:first_name)

What is error_message_for?
--> Well, this is a beautiful hack to do some cool stuff

# Author Shiva Bhusal
# Aug 2016
# in config/initializers/modify_rails_form_builder.rb
# This will add a new method in the `f` object available in Rails forms
class ActionView::Helpers::FormBuilder
  def error_message_for(field_name)
    if self.object.errors[field_name].present?
      model_name              = self.object.class.name.downcase
      id_of_element           = "error_#{model_name}_#{field_name}"
      target_elem_id          = "#{model_name}_#{field_name}"
      class_name              = 'signup-error alert alert-danger'
      error_declaration_class = 'has-signup-error'

      "<div id=\"#{id_of_element}\" for=\"#{target_elem_id}\" class=\"#{class_name}\">"\
      "#{self.object.errors[field_name].join(', ')}"\
      "</div>"\
      "<!-- Later JavaScript to add class to the parent element -->"\
      "<script>"\
          "document.onreadystatechange = function(){"\
            "$('##{id_of_element}').parent()"\
            ".addClass('#{error_declaration_class}');"\
          "}"\
      "</script>".html_safe
    end
  rescue
    nil
  end
end

Result enter image description here

Markup Generated after error

<div id="error_user_email" for="user_email" class="signup-error alert alert-danger">has already been taken</div>
<script>document.onreadystatechange = function(){$('#error_user_email').parent().addClass('has-signup-error');}</script>

Corresponding SCSS

  .has-signup-error{
    .signup-error{
      background: transparent;
      color: $brand-danger;
      border: none;
    }

    input, select{
      background-color: $bg-danger;
      border-color: $brand-danger;
      color: $gray-base;
      font-weight: 500;
    }

    &.checkbox{
      label{
        &:before{
          background-color: $bg-danger;
          border-color: $brand-danger;
        }
      }
    }

Note: Bootstrap variables used here