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 %>
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.
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.
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? %>
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
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With