Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I'm trying to understand how I can achieve this. Can anyone advise me or point me in the right direction?

What I currently do, which is shown in the code snippet below, allows me to display 1 error from each field at a time. It’s not not quite exactly what I want to do.

I want to display 1 error message at a time. For example, “first name can't be blank”, then once that error has been resolved, the next error in the array of errors should be displayed. This should keep happening until all errors have been resolved.

<% @user.errors.each do |attr, msg| %>
<%= "#{attr} #{msg}" if @user.errors[attr].first == msg %> 
<% end %>
like image 344
LondonGuy Avatar asked Oct 24 '11 16:10

LondonGuy


People also ask

What are validations in Rails?

Rails validation defines valid states for each of your Active Record model classes. They are used to ensure that only valid details are entered into your database. Rails make it easy to add validations to your model classes and allows you to create your own validation methods as well.


4 Answers

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.

like image 127
Wade Tandy Avatar answered Sep 20 '22 05:09

Wade Tandy


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? %> 
like image 31
LondonGuy Avatar answered Sep 21 '22 05:09

LondonGuy


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

like image 45
illusionist Avatar answered Sep 23 '22 05:09

illusionist


I resolved it like this:

<% @user.errors.each do |attr, msg| %>
  <li>
    <%= @user.errors.full_messages_for(attr).first if @user.errors[attr].first == msg %>
  </li>
<% end %>

This way you are using the locales for the error messages.

like image 44
nscherzer Avatar answered Sep 23 '22 05:09

nscherzer