Mensagem de erro de validação totalmente personalizada com o Rails

260

Usando o Rails, estou tentando receber uma mensagem de erro como "O campo da música não pode estar vazio" ao salvar. Fazendo o seguinte:

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

... exibe apenas "O representante da música XYW não pode estar vazio", o que não é bom porque o título do campo não é amigável. Como posso alterar o título do próprio campo? Eu poderia alterar o nome real do campo no banco de dados, mas tenho vários campos de "música" e preciso ter nomes de campos específicos.

Não quero invadir o processo de validação dos trilhos e acho que deve haver uma maneira de corrigir isso.

marcgg
fonte

Respostas:

432

Agora, a maneira aceita de definir nomes humanizados e mensagens de erro personalizadas é usar localidades .

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

Agora, o nome humanizado e a mensagem de validação de presença para o atributo "email" foram alterados.

As mensagens de validação podem ser definidas para um modelo específico + atributo, modelo, atributo ou globalmente.

graywh
fonte
19
Se você estiver usando mongoid, substitua activerecord: por mongoid:
Intentss 6/11/11
88
@ greywh: Onde as perguntas sobre uma resposta devem ser postadas, se não nos comentários? Aqui está o guia da I18n: guias.rubyonrails.org/i18n.html
Tyler Rick
4
A propósito: se você passar um símbolo para o parâmetro de mensagem do seu validador no Rails 3.1.3, ele informará o escopo que estava procurando, pois não foi encontrado, para que você saiba exatamente o que colocar em seu validador. locales yml.
Aceofspades 14/02/12
4
Bem, isso é bom e tudo, mas e se preceder o nome da coluna de forma ingênua (por mais legível que seja humana) levaria a uma gramática completamente reprimida (especialmente em idiomas que não o inglês)? Eu realmente preciso usar errors.add :base, msg? Gostaria de saber de qual coluna se trata o erro, para que eu possa exibi-lo no campo de formulário correto.
Panzi
6
@graywh Talvez esteja faltando alguma coisa, mas ela nem sempre precede o nome da coluna antes da mensagem? Mesmo em inglês, eu gostaria de ter, por exemplo, The password is wrong.ou em The email address is not valid.vez de Password is wrong.e Email is not valid..
panzi
65

No seu modelo:

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

Na sua opinião

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

Se você fizer isso

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

você recebe essa mensagem de erro com o nome do atributo

address1 Put some address please

se você deseja receber a mensagem de erro para um único atributo

<%= @model.errors[:address1] %>
Federico
fonte
Essa não é uma solução aceitável. E se eu quiser o comportamento padrão para todos os outros atributos (attr + msg)?
Rômulo Ceccon 20/02
Lá vai você .. você pode jogar com esses 2 coisas e fazê-lo funcionar
Federico
Você tem que usar um símbolo para que ele irá procurar em seus arquivos yml, comovalidates_presence_of :address1, :message => :put_some_address_please
Federico
Isto não é aceitável, como o nome do campo é incluído
fatuhoku
62

Tente isso.

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

Eu encontrei isso aqui .

Aqui está outra maneira de fazer isso. O que você faz é definir um método human_attribute_name na classe de modelo. O método recebe o nome da coluna como uma sequência e retorna a sequência a ser usada nas mensagens de validação.

class User < ActiveRecord::Base

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

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

end

O código acima é daqui

Maulin
fonte
O problema é que o meu campo é chamado: song_rep_xyz (bem, algo complicado), que não é amigável
marcgg
16
para Rails 3, "self.human_attribute_name def (attr)" precisa ser alterado para "self.human_attribute_name def (attr, options = {})", caso contrário, ele retornará um erro
spacemonkey
3
Obrigado por isso. Eu precisava de algo que funcionou para Rails 2. (Sim, pobre de mim ... :)
Dan Barron
18

Sim, há uma maneira de fazer isso sem o plugin! Mas não é tão limpo e elegante quanto usar o plugin mencionado. Aqui está.

Supondo que seja o Rails 3 (não sei se é diferente nas versões anteriores),

mantenha isso em seu modelo:

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

e na vista, em vez de sair

@instance.errors.full_messages

como seria quando usamos o gerador de andaimes, coloque:

@instance.errors.first[1]

E você receberá apenas a mensagem especificada no modelo, sem o nome do atributo.

Explicação:

#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]

Até agora, estamos apenas exibindo apenas uma mensagem, sempre para o primeiro erro. Se você deseja exibir todos os erros, pode fazer um loop no hash e mostrar os valores.

Espero que tenha ajudado.

Marco Antonio
fonte
16

Código Rails3 com mensagens totalmente localizadas:

No modelo user.rb, defina a validação

validates :email, :presence => true

Em config / locales / en.yml

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

No método de validação customizado, use:

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

como add_to_base foi descontinuado.

errors.add_to_base("Custom error message")

amit_saxena
fonte
13

Relacionado à resposta aceita e outra resposta na lista :

Estou confirmando que o fork do nanamkim de custom-err-msg funciona com o Rails 5 e com a configuração da localidade.

Você só precisa iniciar a mensagem de localidade com um sinal de intercalação e ela não deve exibir o nome do atributo na mensagem.

Um modelo definido como:

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

com o seguinte en.yml:

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

item.errors.full_messages Exibirá:

You can't create an item without a name

em vez do habitual Name You can't create an item without a name

Rystraum
fonte
11

Eu recomendo instalar a gem custom_error_message (ou como um plug - in ) originalmente escrita por David Easley

Permite fazer coisas como:

validates_presence_of :non_friendly_field_name, :message => "^Friendly field name is blank"
Ryan Bigg
fonte
Eu usei este plugin no passado com muito sucesso, embora ele não pareça mais ser mantido regularmente.
Jared Brown
1
você pode ALSE instalá-lo como uma jóia para rails 3. basta adicionar gem "custom_error_message" ao seu Gemfile - ver github para mais detalhes
Dorian
Exatamente o que eu precisava
olleicua
3
@DickieBoy Confirmo que o garfo do nanamkim ( github.com/nanamkim/custom-err-msg ) funciona com o Rails 5. Na verdade, ele joga bem com a resposta aceita. Vou escrever isso como uma resposta separada.
Rystraum 26/10/16
@Rystraum Pela minha vida, não me lembro dos casos em torno disso, mas obrigado pela resposta! Vou me lembrar disso no futuro.
DickieBoy
10

Uma solução pode ser alterar o formato de erro padrão do i18n:

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

O padrão é format: %{attribute} %{message}

cappie013
fonte
7

Aqui está outra maneira:

Se você usar este modelo:

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

Você pode escrever sua própria mensagem personalizada como esta:

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

Dessa forma, devido ao sublinhado, a mensagem completa se torna "Minha mensagem personalizada", mas o espaço extra no início é imperceptível. Se você realmente não quer esse espaço extra no começo, basta adicionar o .lstripmétodo.

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

O método String.lstrip se livra do espaço extra criado por ': _' e deixa inalteradas outras mensagens de erro.

Ou melhor ainda, use a primeira palavra da sua mensagem personalizada como chave:

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

Agora a mensagem completa será "Minha mensagem personalizada" sem espaço extra.

Se você deseja que a mensagem completa comece com uma palavra em maiúscula como "O URL não pode ficar em branco", isso não pode ser feito. Em vez disso, tente adicionar outra palavra como chave:

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

Agora a mensagem completa será "O URL não pode ficar em branco"

Cruz Nunez
fonte
ooo, você mesmo pode fazer errors.add(:_, 'foobar')e receber 'foobar' como a mensagem
xxjjnn
6

Apenas faça da maneira normal:

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

Mas exiba assim

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

Devoluções

"Email is required."

O método de localização é definitivamente a maneira "correta" de fazer isso, mas se você estiver fazendo um projeto pequeno e não global e quiser apenas avançar rapidamente - isso é definitivamente mais fácil do que o salto de arquivos.

Eu gosto da capacidade de colocar o nome do campo em outro lugar que não seja o início da string:

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

Se você quiser listá-los todos em uma lista legal, mas sem usar o nome não amigável, você pode fazer isso ...

object.errors.each do |attr,message|
  puts "<li>"+message+"</li>"
end
Adão
fonte
1

Na sua opinião

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

Quando você deseja substituir a mensagem de erro sem o nome do atributo, basta preceder a mensagem com ^ assim:

validates :last_name,
  uniqueness: {
    scope: [:first_name, :course_id, :user_id],
    case_sensitive: false,
    message: "^This student has already been registered."
  }
luckyruby
fonte
não funciona com trilhos 5.1 / ruby ​​2.4? recebendo o nome do modelo nesse âmbito
Ben
O @Ben funciona para mim no Rails 5.1.2, Ruby 2.4.1p111. Você pode compartilhar seu código?
luckyruby
eu acho que eu tinha que olhar mais longe, você pode verificar o código e sua resposta lá stackoverflow.com/q/45128434/102133
Ben
0

Eu tentei seguir, trabalhou para mim :)

1 job.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
fonte
0

Aqui está o meu código que pode ser útil para você, caso você ainda precise: Meu modelo:

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

Então eu criei um auxiliar para mostrar mensagens:

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
fonte
0

Uma abordagem única que nunca vi ninguém mencionar!

A única maneira de obter toda a personalização que eu queria era usar um after_validationretorno de chamada para me permitir manipular a mensagem de erro.

  1. Permita que a mensagem de validação seja criada normalmente, não é necessário tentar alterá-la no auxiliar de validação.

  2. crie um after_validationretorno de chamada que substitua a mensagem de validação no back-end antes de chegar à exibição.

  3. No after_validationmétodo, você pode fazer o que quiser com a mensagem de validação, como uma string normal! Você pode até usar valores dinâmicos e inseri-los na mensagem de validação.


#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

O método after_validation terá um escopo muito maior do que o auxiliar de validação de rails embutido, portanto, você poderá acessar o objeto que está validando como está tentando fazer com object.file_name. O que não funciona no auxiliar de validação em que você está tentando chamá-lo.

Nota: usamos o ^para se livrar do nome do atributo no início da validação, como @Rystraum apontou fazendo referência a esta gema

Sami Birnbaum
fonte
0

A resposta de graywh é a melhor se ela realmente for diferente da localização do nome do campo. No caso de um nome de campo dinâmico (com base em outros campos a serem exibidos), eu faria algo assim

<% 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 %>

o método full_message no else é o que os rails usam dentro do método full_messages; portanto, ele fornece os erros normais do Rails para outros casos (Rails 3.2 e superior)

An Nguyen Dang
fonte