ruby on rails - validations - Mensajes de error de validación de rieles: mostrar solo un mensaje de error por campo
validate integer ruby (13)
¿Qué tal este @event.errors[:title].first
?
Rails muestra todos los mensajes de error de validación asociados con un campo dado. Si tengo tres validates_XXXXX_of :email
, y dejo el campo en blanco, recibo tres mensajes en la lista de errores.
Ejemplo:
validates_presence_of :name
validates_presence_of :email
validates_presence_of :text
validates_length_of :name, :in => 6..30
validates_length_of :email, :in => 4..40
validates_length_of :text, :in => 4..200
validates_format_of :email, :with => /^([^@/s]+)@((?:[-a-z0-9]+/.)+[a-z]{2,})$/i<br/>
<%= error_messages_for :comment %>
me da:
7 errors prohibited this comment from being saved
There were problems with the following fields:
Name can''t be blank
Name is too short (minimum is 6 characters)
Email can''t be blank
Email is too short (minimum is 4 characters)
Email is invalid
Text can''t be blank
Text is too short (minimum is 4 characters)
Es mejor mostrar un mensaje a la vez. ¿Hay alguna manera fácil de solucionar este problema? Parece sencillo tener una condición como: Si encontró un error para :email
, deje de validar :email
y salte al otro campo.
Agregue un método a la clase ActiveModel :: Errors
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
lib/core_ext/rails/active_model/errors.rb
a un archivo, como lib/core_ext/rails/active_model/errors.rb
. Cree un archivo config/initializers/core_ext.rb
y agregue un require "core_ext/rails/active_model/errors.rb"
a él.
Bert más en RailsForum escribió sobre esto hace un tiempo. Escribió el código a continuación y agregué algunos ajustes menores para que se ejecutara en Rails-3.0.0-beta2.
Agregue esto a un archivo llamado app/helpers/errors_helper.rb
y simplemente agregue helper "errors"
a su controlador.
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
Creo que la forma más fácil es usar la opción allow_bank. Por ejemplo, para evitar mostrar el mensaje de que el nombre es demasiado corto cuando el campo se deja en blanco, puede hacer lo siguiente:
validates_length_of :name, allow_blank:true, :in => 6..30
Escribí un ayudante personalizado
def display_error(field)
if @user.errors[field].any?
raw @user.errors[field].first+"<br>"
end
end
y luego lo uso a la vista en el campo de texto como tal
<%= display_error(:password) %>
Imo simplier es:
<% @model.errors.each do |attr, msg| %>
<%= "#{attr} #{msg}" if @model.errors[attr].first == msg %>
<% end %>
Mi parche mono de ActiveModel::Errors
class lib/core_ext/rails/active_model/errors.rb
(uso este código para la versión de Ruby on Rails 5.0):
module ActiveModel
class Errors
# don''t add an attribute''s error message to details
# if it already contains at least one message
alias_method :old_add, :add
def add(attribute, message = :invalid, options = {})
if details[attribute.to_sym].size.zero?
old_add(attribute, message, options)
end
end
end
end
Cree un archivo config/initializers/core_ext.rb
y agregue un requerimiento core_ext/rails/active_model/errors.rb
a él.
Mostraría todos los mensajes de error en una línea y en un formato de oración. No desea que el usuario corrija un error y termine teniendo otro error del que no estaba enterado después del envío. Decirles todas las reglas les ahorrará clics. Dicho esto, así es como lo haría:
flash_message_now("error",
@album.errors.keys.map { |k| "#{Album.human_attribute_name(k)} #{@album.errors[k].to_sentence}"}.to_sentence
)
con flash_message_now definido en ApplicationController (puede agregarlo a un ayudante)
def flash_message_now(type, text)
flash.now[type] ||= []
flash.now[type] << text
end
O simplemente puede modificar la matriz (con el método ''bang'' delete_at), por lo que todo lo que sigue se queda en los rieles predeterminados, i18n, etc.
<% @article.errors.keys.each { |attr| @article.errors[attr].delete_at(1) } %>
Código de trabajo completo:
<% 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 %>
Similar a la respuesta de olovwia :
<% @errors.keys.each do |attr| %>
<%= "#{attr.capitalize} #{@errors[attr].first}."%>
<% end %>"
Utilizo este código para la versión de Ruby on Rails 3.0, que puse en lib/core_ext/rails/active_model/errors.rb
:
module ActiveModel
class Errors
def full_message_per_field
messages_per_field = []
handled_attributes = []
each do |attribute, messages|
next if handled_attributes.include? attribute
messages = Array.wrap(messages)
next if messages.empty?
if attribute == :base
messages_per_field << messages.first
else
attr_name = attribute.to_s.gsub(''.'', ''_'').humanize
attr_name = @base.class.human_attribute_name(attribute, :default => attr_name)
options = { :default => "%{attribute} %{message}", :attribute => attr_name }
messages_per_field << I18n.t(:"errors.format", options.merge(:message => messages.first))
end
handled_attributes << attribute
end
messages_per_field
end
end
end
Este es esencialmente el mismo código que ActiveModel::Errors#full_messages
, pero no mostrará más de un error por atributo. Asegúrese de solicitar el archivo (por ejemplo, en un inicializador) y ahora puede llamar @model.errors.full_message_per_field do |message| ...
@model.errors.full_message_per_field do |message| ...
[Actualización] Enero / 2013 a Rails 3.2.x - sintaxis de actualización; agregar especificaciones
Inspirado por los nuevos métodos de validation en Rails 3.0, estoy agregando este pequeño Validator. Lo llamo ReduceValidator
.
lib/reduce_validator.rb
:
# show only one error message per field
#
class ReduceValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return until record.errors.messages.has_key?(attribute)
record.errors[attribute].slice!(-1) until record.errors[attribute].size <= 1
end
end
Mi modelo se ve como ... fíjate en :reduce => true
:
validates :title, :presence => true, :inclusion => { :in => %w[ Mr Mrs ] }, :reduce => true
validates :firstname, :presence => true, :length => { :within => 2..50 }, :format => { :without => /^/D{1}[.]/i }, :reduce => true
validates :lastname, :presence => true, :length => { :within => 2..50 }, :format => { :without => /^/D{1}[.]/i }, :reduce => true
Funciona como un encanto en mi proyecto actual de Rails. La ventaja es que puse el validador solo en algunos campos, no en todos.
spec/lib/reduce_validator_spec.rb
:
require ''spec_helper''
describe ReduceValidator do
let(:reduce_validator) { ReduceValidator.new({ :attributes => {} }) }
let(:item) { mock_model("Item") }
subject { item }
before(:each) do
item.errors.add(:name, "message one")
item.errors.add(:name, "message two")
end
it { should have(2).error_on(:name) }
it "should reduce error messages" do
reduce_validator.validate_each(item, :name, '''')
should have(1).error_on(:name)
end
end
# 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