Mensaje de error de validación totalmente personalizado con Rails

260

Al usar Rails, intento obtener un mensaje de error como "El campo de la canción no puede estar vacío" al guardar. Haciendo lo siguiente:

validates_presence_of :song_rep_xyz, :message => "can't be empty"

... solo muestra "Song Rep XYW no puede estar vacío", lo que no es bueno porque el título del campo no es fácil de usar. ¿Cómo puedo cambiar el título del campo en sí? Podría cambiar el nombre real del campo en la base de datos, pero tengo varios campos de "canción" y necesito tener nombres de campo específicos.

No quiero hackear el proceso de validación de rails y creo que debería haber una forma de solucionarlo.

marcgg
fuente

Respuestas:

432

Ahora, la forma aceptada de establecer los nombres humanizados y los mensajes de error personalizados es usar configuraciones regionales .

# config/locales/en.yml
en:
  activerecord:
    attributes:
      user:
        email: "E-mail address"
    errors:
      models:
        user:
          attributes:
            email:
              blank: "is required"

Ahora se han cambiado el nombre humanizado y el mensaje de validación de presencia para el atributo "correo electrónico".

Los mensajes de validación se pueden configurar para un modelo específico + atributo, modelo, atributo o globalmente.

graywh
fuente
19
Si está usando mongoid, reemplace activerecord: con mongoid:
Intentss
88
@graywh: ¿Dónde deben publicarse las preguntas sobre una respuesta, si no es en los comentarios? Aquí está la guía I18n: guides.rubyonrails.org/i18n.html
Tyler Rick
44
Por cierto: si pasa un símbolo para el parámetro de mensaje de su validador en Rails 3.1.3, le indicará el alcance que estaba buscando, ya que no lo encontrará, para que sepa exactamente qué poner en su locales yml.
aceofspades
44
Bueno, esto está bien y todo, pero ¿qué pasa si ingenuamente anteponer el nombre de la columna (no importa cuán legible sea para humanos) conduciría a una gramática completamente modificada (especialmente en idiomas que no sean inglés)? ¿Realmente necesito usar errors.add :base, msg? Me gustaría saber de qué columna se trata el error, para poder mostrarlo en el campo de formulario correcto.
panzi
66
@graywh Tal vez me falta algo, pero ¿no siempre antepone el nombre de la columna antes del mensaje? Incluso en inglés me gustaría tener, por ejemplo, The password is wrong.o en The email address is not valid.lugar de Password is wrong.y Email is not valid..
panzi
65

En su modelo:

validates_presence_of :address1, message: 'Put some address please' 

En tu opinión

<% m.errors.each do |attr, msg|  %>
 <%= msg %>
<% end %>

Si lo haces en su lugar

<%= attr %> <%= msg %>

aparece este mensaje de error con el nombre del atributo

address1 Put some address please

si desea obtener el mensaje de error para un solo atributo

<%= @model.errors[:address1] %>
Federico
fuente
Esa no es una solución aceptable. ¿Qué sucede si deseo el comportamiento predeterminado para todos los demás atributos (attr + msg)?
Rômulo Ceccon
Ahí tienes ... puedes jugar con esas 2 cosas y hacer que funcione
Federico
Tienes que usar un símbolo para que se vea en tus archivos yml, comovalidates_presence_of :address1, :message => :put_some_address_please
Federico
Esto no es aceptable, ya que el nombre del campo se incluye
fatuhoku
62

Prueba esto.

class User < ActiveRecord::Base
  validate do |user|
    user.errors.add_to_base("Country can't be blank") if user.country_iso.blank?
  end
end

Encontré esto aquí .

Aquí hay otra forma de hacerlo. Lo que debe hacer es definir un método human_attribute_name en la clase de modelo. El método pasa el nombre de la columna como una cadena y devuelve la cadena para usar en los mensajes de validación.

class User < ActiveRecord::Base

  HUMANIZED_ATTRIBUTES = {
    :email => "E-mail address"
  }

  def self.human_attribute_name(attr)
    HUMANIZED_ATTRIBUTES[attr.to_sym] || super
  end

end

El código anterior es de aquí.

Maulin
fuente
El problema es que mi campo se llama: song_rep_xyz (bueno, algo complicado), lo cual no es fácil de usar
marcgg
16
para Rails 3, "def self.human_attribute_name (attr)" debe cambiarse a "def self.human_attribute_name (attr, options = {})", de lo contrario, devuelve un error
spacemonkey
3
Gracias por esto. Necesitaba algo que funcionara para Rails 2. (Sí, pobre de mí ... :)
Dan Barron
18

Sí, hay una manera de hacer esto sin el complemento. Pero no es tan limpio y elegante como usar el complemento mencionado. Aquí está.

Asumiendo que es Rails 3 (no sé si es diferente en versiones anteriores),

mantén esto en tu modelo:

validates_presence_of :song_rep_xyz, :message => "can't be empty"

y en la vista, en lugar de irse

@instance.errors.full_messages

como sería cuando utilizamos el generador de andamios, pon:

@instance.errors.first[1]

Y obtendrá solo el mensaje que especificó en el modelo, sin el nombre del atributo.

Explicación:

#returns an hash of messages, one element foreach field error, in this particular case would be just one element in the hash:
@instance.errors  # => {:song_rep_xyz=>"can't be empty"}

#this returns the first element of the hash as an array like [:key,"value"]
@instance.errors.first # => [:song_rep_xyz, "can't be empty"]

#by doing the following, you are telling ruby to take just the second element of that array, which is the message.
@instance.errors.first[1]

Hasta ahora solo estamos mostrando un mensaje, siempre para el primer error. Si desea mostrar todos los errores, puede recorrer el hash y mostrar los valores.

Espero que haya ayudado.

Marco Antonio
fuente
16

Código Rails3 con mensajes totalmente localizados:

En el modelo user.rb define la validación

validates :email, :presence => true

En config / locales / en.yml

en:  
  activerecord:
    models: 
      user: "Customer"
    attributes:
      user:
        email: "Email address"
    errors:
      models:
        user:
          attributes:
            email:
              blank: "cannot be empty"
Lukas
fuente
15

En el método de validación personalizado use:

errors.add(:base, "Custom error message")

como add_to_base ha quedado en desuso.

errors.add_to_base("Custom error message")

amit_saxena
fuente
13

Relacionado con la respuesta aceptada y otra respuesta en la lista :

Estoy confirmando que la bifurcación de custom-err-msg de nanamkim funciona con Rails 5 y con la configuración regional.

Solo necesita comenzar el mensaje de configuración regional con un cursor y no debe mostrar el nombre del atributo en el mensaje.

Un modelo definido como:

class Item < ApplicationRecord
  validates :name, presence: true
end

con lo siguiente en.yml:

en:
  activerecord:
    errors:
      models:
        item:
          attributes:
            name:
              blank: "^You can't create an item without a name."

item.errors.full_messages mostrará:

You can't create an item without a name

en lugar de lo habitual Name You can't create an item without a name

Rystraum
fuente
11

Recomiendo instalar la gema custom_error_message (o como complemento ) originalmente escrita por David Easley

Te permite hacer cosas como:

validates_presence_of :non_friendly_field_name, :message => "^Friendly field name is blank"
Ryan Bigg
fuente
He utilizado este complemento en el pasado con gran éxito, aunque ya no parece que se mantenga regularmente.
Jared Brown
1
también puede instalarlo como una gema para los rieles 3. simplemente agréguelo gem "custom_error_message" a su Gemfile - vea github para más detalles
Dorian
Exactamente lo que necesitaba
olleicua
3
@DickieBoy Confirmo que la bifurcación de nanamkim ( github.com/nanamkim/custom-err-msg ) funciona con Rails 5. Realmente funciona bien con la respuesta aceptada. Escribiré esto como una respuesta separada.
Rystraum
@Rystraum Por mi vida no puedo recordar el caso de uso de esto, pero gracias por la respuesta! Me aseguraré de recordarlo para el futuro.
DickieBoy
10

Una solución podría ser cambiar el formato de error predeterminado i18n:

en:
  errors:
    format: "%{message}"

El valor predeterminado es format: %{attribute} %{message}

cappie013
fuente
7

Aquí hay otra manera:

Si usa esta plantilla:

<% if @thing.errors.any? %>
  <ul>
    <% @thing.errors.full_messages.each do |message| %>
      <li><%= message %></li>
    <% end %>
  </ul>
<% end %>

Puede escribir su propio mensaje personalizado como este:

class Thing < ActiveRecord::Base

  validate :custom_validation_method_with_message

  def custom_validation_method_with_message
    if some_model_attribute.blank?
      errors.add(:_, "My custom message")
    end
  end

De esta manera, debido al guión bajo, el mensaje completo se convierte en "Mi mensaje personalizado", pero el espacio extra al principio es imperceptible. Si realmente no quieres ese espacio extra al principio, solo agrega el .lstripmétodo.

<% if @thing.errors.any? %>
  <ul>
    <% @thing.errors.full_messages.each do |message| %>
      <li><%= message.lstrip %></li>
    <% end %>
  </ul>
<% end %>

El método String.lstrip eliminará el espacio adicional creado por ': _' y dejará los demás mensajes de error sin cambios.

O incluso mejor, use la primera palabra de su mensaje personalizado como clave:

  def custom_validation_method_with_message
    if some_model_attribute.blank?
      errors.add(:my, "custom message")
    end
  end

Ahora el mensaje completo será "Mi mensaje personalizado" sin espacio adicional.

Si desea que el mensaje completo comience con una palabra en mayúscula como "La URL no puede estar en blanco", no puede hacerlo. En su lugar, intente agregar alguna otra palabra como clave:

  def custom_validation_method_with_message
    if some_model_attribute.blank?
      errors.add(:the, "URL can't be blank")
    end
  end

Ahora el mensaje completo será "La URL no puede estar en blanco"

Cruz Nunez
fuente
ooo, incluso puedes hacer errors.add(:_, 'foobar')y obtener 'foobar' como mensaje
xxjjnn
6

Solo hazlo de la manera normal:

validates_presence_of :email, :message => "Email is required."

Pero muéstrelo así en su lugar

<% if @user.errors.any? %>
  <% @user.errors.messages.each do |message| %>
    <div class="message"><%= message.last.last.html_safe %></div>
  <% end %>
<% end %>

Devoluciones

"Email is required."

El método de localización es definitivamente la forma "adecuada" de hacer esto, pero si está haciendo un pequeño proyecto no global y desea comenzar rápidamente, esto es definitivamente más fácil que el salto de archivos.

Me gusta por la capacidad de poner el nombre del campo en otro lugar que no sea el comienzo de la cadena:

validates_uniqueness_of :email, :message => "There is already an account with that email."
Brittohalloran
fuente
2

Si desea enumerarlos a todos en una buena lista pero sin usar el nombre grosero no humano, puede hacer esto ...

object.errors.each do |attr,message|
  puts "<li>"+message+"</li>"
end
Adán
fuente
1

En tu opinión

object.errors.each do |attr,msg|
  if msg.is_a? String
    if attr == :base
      content_tag :li, msg
    elsif msg[0] == "^"
      content_tag :li, msg[1..-1]
    else
      content_tag :li, "#{object.class.human_attribute_name(attr)} #{msg}"
    end
  end
end

Cuando desee anular el mensaje de error sin el nombre del atributo, simplemente anteponga el mensaje con ^ así:

validates :last_name,
  uniqueness: {
    scope: [:first_name, :course_id, :user_id],
    case_sensitive: false,
    message: "^This student has already been registered."
  }
luckyruby
fuente
no funciona con rieles 5.1 / ruby ​​2.4? obtener el nombre del modelo en ese ámbito
Ben
@Ben funciona para mí en Rails 5.1.2, Ruby 2.4.1p111. ¿Puedes compartir tu código?
luckyruby
Supongo que tuve que buscar más, puedes consultar el código y su respuesta allí stackoverflow.com/q/45128434/102133
Ben
0

Intenté seguir, funcionó para mí :)

1 trabajo.rb

class Job < ApplicationRecord
    validates :description, presence: true
    validates :title, 
              :presence => true, 
              :length => { :minimum => 5, :message => "Must be at least 5 characters"}
end

2 jobs_controller.rb

def create
      @job = Job.create(job_params)
      if @job.valid?
        redirect_to jobs_path
      else
        render new_job_path
      end     
    end

3 _form.html.erb

<%= form_for @job do |f| %>
  <% if @job.errors.any? %>
    <h2>Errors</h2>
    <ul>
      <% @job.errors.full_messages.each do |message|%>
        <li><%= message %></li>
      <% end %>  
    </ul>
  <% end %>
  <div>
    <%= f.label :title %>
    <%= f.text_field :title %>
  </div>
  <div>
    <%= f.label :description %>
    <%= f.text_area :description, size: '60x6' %>

  </div>
  <div>
    <%= f.submit %>
  </div>
<% end %> 
Aigul
fuente
0

Aquí está mi código que puede ser útil para usted en caso de que aún lo necesite: Mi modelo:

validates :director, acceptance: {message: "^Please confirm that you are a director of the company."}, on: :create, if: :is_director?

Luego he creado un ayudante para mostrar mensajes:

module ErrorHelper
  def error_messages!
    return "" unless error_messages?
    messages = resource.errors.full_messages.map { |msg|
       if msg.present? && !msg.index("^").nil?
         content_tag(:p, msg.slice((msg.index("^")+1)..-1))
       else
         content_tag(:p, msg)
       end
    }.join

    html = <<-HTML
      <div class="general-error alert show">
        #{messages}
      </div>
    HTML

    html.html_safe
  end

  def error_messages?
    !resource.errors.empty?
  end
end
Oleksii Danylevskyi
fuente
0

¡Un enfoque único que no he visto mencionar a nadie!

La única forma en que pude obtener toda la personalización que quería era usar una after_validationdevolución de llamada para permitirme manipular el mensaje de error.

  1. Permita que el mensaje de validación se cree normalmente, no necesita intentar cambiarlo en el asistente de validación.

  2. cree una after_validationdevolución de llamada que reemplazará ese mensaje de validación en el back-end antes de que llegue a la vista.

  3. En el after_validationmétodo, puede hacer lo que quiera con el mensaje de validación, ¡como una cadena normal! Incluso puede usar valores dinámicos e insertarlos en el mensaje de validación.


#this could be any validation
validates_presence_of :song_rep_xyz, :message => "whatever you want - who cares - we will replace you later"

after_validation :replace_validation_message

def replace_validation_message
    custom_value = #any value you would like
    errors.messages[:name_of_the_attribute] = ["^This is the replacement message where 
    you can now add your own dynamic values!!! #{custom_value}"]
end

El método after_validation tendrá un alcance mucho mayor que el ayudante de validación de rieles incorporado, por lo que podrá acceder al objeto que está validando como está tratando de hacer con object.file_name. Lo que no funciona en el asistente de validación donde está intentando llamarlo.

Nota: utilizamos el ^para deshacernos del nombre del atributo al comienzo de la validación como @Rystraum señaló haciendo referencia a esta gema

Sami Birnbaum
fuente
0

La respuesta de graywh es la mejor si en realidad es diferente para mostrar el nombre del campo. En el caso de un nombre de campo dinámico (basado en otros campos para mostrar), haría algo como esto

<% object.errors.each do |attr, msg| %>
<li>
  <% case attr.to_sym %>
  <% when :song_rep_xyz %>
    <%= #display error how you want here %>
  <% else %>
    <%= object.errors.full_message(attr, msg) %>
  <% end %>
</li>
<% end %>

el método full_message en el else es lo que usan los rieles dentro del método full_messages, por lo que dará los errores normales de Rails para otros casos (Rails 3.2 y superiores)

An Nguyen Dang
fuente