Rails displays all validation error messages associated with a given field. If I have three validates_XXXXX_of :email
, and I leave the field blank, I get three
Or you can simply modify the array (with 'bang' method delete_at), so everything after stays default rails, i18n etc.
<% @article.errors.keys.each { |attr| @article.errors[attr].delete_at(1) } %>
Complete working code:
<% if @article.errors.any? %>
<% @article.errors.keys.each { |attr| @article.errors[attr].delete_at(1) } %>
<ul>
<% @article.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
<% end %>
Add a method to ActiveModel::Errors class
module ActiveModel
class Errors
def full_unique_messages
unique_messages = messages.map { |attribute, list_of_messages| [attribute, list_of_messages.first] }
unique_messages.map { |attribute_message_pair| full_message *attribute_message_pair }
end
end
end
Add it to a file, like lib/core_ext/rails/active_model/errors.rb
. Create a file config/initializers/core_ext.rb
and add a require "core_ext/rails/active_model/errors.rb"
to it.
Imo simplier is:
<% @model.errors.each do |attr, msg| %>
<%= "#{attr} #{msg}" if @model.errors[attr].first == msg %>
<% end %>
I wrote a custom helper
def display_error(field)
if @user.errors[field].any?
raw @user.errors[field].first+"<br>"
end
end
and then I use it in view under the text field like so
<%= display_error(:password) %>
# Extracts at most <strong>one error</strong> message <strong>per field</strong> from the errors-object.
# @param [ActiveModel::Errors] the_errors_object The errors-object.
# @raise [ArgumentError] If the given argument is not an instance of ActiveModel::Errors.
# @return [Array] A string-array containing at most one error message per field from the given errors-object.
def get_one_error_per_field(the_errors_object)
if the_errors_object.is_a? ActiveModel::Errors
errors = {}
the_errors_object.each do |field_name, associated_error|
errors[field_name] = the_errors_object.full_message(field_name, associated_error) unless errors[field_name]
end
return errors.values
else
raise ArgumentError.new('The given argument isn\'t an instance of ActiveModel::Errors!')
end
end
Bert over at RailsForum wrote about this a little while back. He wrote the code below and I added some minor tweaks for it to run on Rails-3.0.0-beta2.
Add this to a file called app/helpers/errors_helper.rb
and simply add helper "errors"
to your controller.
module ErrorsHelper
# see: lib/action_view/helpers/active_model_helper.rb
def error_messages_for(*params)
options = params.extract_options!.symbolize_keys
objects = Array.wrap(options.delete(:object) || params).map do |object|
object = instance_variable_get("@#{object}") unless object.respond_to?(:to_model)
object = convert_to_model(object)
if object.class.respond_to?(:model_name)
options[:object_name] ||= object.class.model_name.human.downcase
end
object
end
objects.compact!
count = objects.inject(0) {|sum, object| sum + object.errors.count }
unless count.zero?
html = {}
[:id, :class].each do |key|
if options.include?(key)
value = options[key]
html[key] = value unless value.blank?
else
html[key] = 'errorExplanation'
end
end
options[:object_name] ||= params.first
I18n.with_options :locale => options[:locale], :scope => [:errors, :template] do |locale|
header_message = if options.include?(:header_message)
options[:header_message]
else
locale.t :header, :count => count, :model => options[:object_name].to_s.gsub('_', ' ')
end
message = options.include?(:message) ? options[:message] : locale.t(:body)
error_messages = objects.sum do |object|
object.errors.on(:name)
full_flat_messages(object).map do |msg|
content_tag(:li, ERB::Util.html_escape(msg))
end
end.join.html_safe
contents = ''
contents << content_tag(options[:header_tag] || :h2, header_message) unless header_message.blank?
contents << content_tag(:p, message) unless message.blank?
contents << content_tag(:ul, error_messages)
content_tag(:div, contents.html_safe, html)
end
else
''
end
end
####################
#
# added to make the errors display in a single line per field
#
####################
def full_flat_messages(object)
full_messages = []
object.errors.each_key do |attr|
msg_part=msg=''
object.errors[attr].each do |message|
next unless message
if attr == "base"
full_messages << message
else
msg=object.class.human_attribute_name(attr)
msg_part+= I18n.t('activerecord.errors.format.separator', :default => ' ') + (msg_part=="" ? '': ' & ' ) + message
end
end
full_messages << "#{msg} #{msg_part}" if msg!=""
end
full_messages
end
end