Полностью настраиваемое сообщение об ошибке валидации с Rails

260

Используя Rails, я пытаюсь получить сообщение об ошибке типа «Поле песни не может быть пустым» при сохранении. Делать следующее:

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

... отображается только «Song Rep XYW не может быть пустым», что не очень хорошо, поскольку заголовок поля не является удобным для пользователя. Как я могу изменить заголовок самого поля? Я мог бы изменить фактическое имя поля в базе данных, но у меня есть несколько полей «песни», и мне нужно иметь конкретные имена полей.

Я не хочу взламывать процесс проверки рельсов, и я чувствую, что должен быть способ исправить это.

marcgg
источник

Ответы:

432

Теперь общепринятым способом установки гуманизированных имен и пользовательских сообщений об ошибках является использование локалей .

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

Теперь гуманизированное имя и сообщение проверки присутствия для атрибута «email» были изменены.

Сообщения проверки могут быть установлены для конкретной модели + атрибут, модель, атрибут или глобально.

graywh
источник
19
Если вы используете mongoid, замените activerecord: mongoid:
Intentss
88
@ graywh: Где следует публиковать вопросы об ответе, если не в комментариях? Вот руководство по I18n: guides.rubyonrails.org/i18n.html
Тайлер Рик,
4
Между прочим: если вы передадите символ для параметра сообщения вашего валидатора в Rails 3.1.3, он сообщит вам искомую область, так как он не будет найден, так что вы точно знаете, что поместить в ваш локали yml.
aceofspades
4
Ну, это хорошо и все, но что, если наивное добавление имени столбца (независимо от того, насколько оно читаемо человеком) приведет к полностью развернутой грамматике (особенно в неанглийских языках)? Мне действительно нужно использовать errors.add :base, msg? Я хотел бы знать, в каком столбце содержится ошибка, чтобы отобразить ее в правильном поле формы.
Panzi
6
@ graywh Может быть, я что-то упустил, но не всегда ли перед именем сообщения стоит имя столбца? Даже на английском я хотел бы иметь, например, The password is wrong.или The email address is not valid.вместо Password is wrong.и Email is not valid..
Panzi
65

В вашей модели:

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

По вашему мнению

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

Если вы делаете вместо

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

вы получаете это сообщение об ошибке с именем атрибута

address1 Put some address please

если вы хотите получить сообщение об ошибке для одного атрибута

<%= @model.errors[:address1] %>
Federico
источник
Это не приемлемое решение. Что если я хочу поведение по умолчанию для всех других атрибутов (attr + msg)?
Ромуло Цеккон
Вот и все .. вы можете поиграть с этими двумя вещами и заставить это работать
Федерико
Вы должны использовать символ, чтобы он выглядел в ваших файлах yml, например,validates_presence_of :address1, :message => :put_some_address_please
Federico
Это недопустимо, так как имя поля включается
фатухоку
62

Попробуй это.

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

Я нашел это здесь .

Вот еще один способ сделать это. Что вы делаете, это определяете метод human_attribute_name в классе модели. Метод передает имя столбца в виде строки и возвращает строку для использования в сообщениях проверки.

class User < ActiveRecord::Base

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

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

end

Приведенный выше код отсюда

Maulin
источник
Проблема в том, что мое поле называется: song_rep_xyz (ну, что-то сложное), что не удобно для пользователя
marcgg
16
для Rails 3 «def self.human_attribute_name (attr)» необходимо изменить на «def self.human_attribute_name (attr, options = {})», в противном случае он возвращает ошибку
spacemonkey
3
Спасибо за это. Мне нужно что-то, что сработало для Rails 2. (Да, бедный я ... :)
Дэн Баррон,
18

Да, есть способ сделать это без плагина! Но это не так чисто и элегантно, как использование упомянутого плагина. Вот.

Предполагая, что это Rails 3 (я не знаю, отличается ли он в предыдущих версиях),

сохранить это в вашей модели:

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

и по мнению, вместо того, чтобы оставить

@instance.errors.full_messages

как было бы, когда мы используем генератор скаффолдов, поместите:

@instance.errors.first[1]

И вы получите только сообщение, которое вы указали в модели, без имени атрибута.

Объяснение:

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

Пока что мы показываем только одно сообщение, всегда для первой ошибки. Если вы хотите отобразить все ошибки, вы можете зациклить в хэш и показать значения.

Надеюсь, что это помогло.

Марко Антонио
источник
16

Код Rails3 с полностью локализованными сообщениями:

В модели user.rb определите валидацию

validates :email, :presence => true

В config / locales / en.yml

en:  
  activerecord:
    models: 
      user: "Customer"
    attributes:
      user:
        email: "Email address"
    errors:
      models:
        user:
          attributes:
            email:
              blank: "cannot be empty"
Lukas
источник
15

В пользовательском методе проверки используйте:

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

так как add_to_base устарела.

errors.add_to_base("Custom error message")

amit_saxena
источник
13

Связанный с принятым ответом и другим ответом вниз по списку :

Я подтверждаю, что форк nanamkim custom-err-msg работает с Rails 5 и с настройкой локали.

Вам просто нужно начать сообщение локали с каретки, и оно не должно отображать имя атрибута в сообщении.

Модель определяется как:

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

со следующим en.yml:

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

item.errors.full_messages будет отображать:

You can't create an item without a name

вместо обычного Name You can't create an item without a name

Rystraum
источник
11

Я рекомендую установить гем custom_error_message (или как плагин ), изначально написанный Дэвидом Исли

Это позволяет вам делать такие вещи, как:

validates_presence_of :non_friendly_field_name, :message => "^Friendly field name is blank"
Райан Бигг
источник
Я использовал этот плагин в прошлом с большим успехом, хотя, похоже, он больше не поддерживается регулярно.
Джаред Браун
1
Вы также можете установить его как драгоценный камень для рельсов. 3. Просто добавьте его gem "custom_error_message" в свой Gemfile - смотрите github для более подробной информации
Дориан,
Именно то, что мне было нужно
olleicua
3
@DickieBoy Я подтверждаю, что форк nanamkim ( github.com/nanamkim/custom-err-msg ) работает с Rails 5. На самом деле он хорошо согласуется с принятым ответом. Я напишу это как отдельный ответ.
Rystraum
@Rystraum Для жизни я не могу вспомнить случай использования этого, но спасибо за ответ! Я обязательно запомню это на будущее.
DickieBoy
10

Одним из решений может быть изменение формата ошибки i18n по умолчанию:

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

По умолчанию format: %{attribute} %{message}

cappie013
источник
7

Вот еще один способ:

Если вы используете этот шаблон:

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

Вы можете написать собственное сообщение, например:

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

Таким образом, из-за подчеркивания, полное сообщение становится «Мое пользовательское сообщение», но лишний пробел в начале незаметен. Если вам действительно не нужно лишнее пространство в начале, просто добавьте .lstripметод.

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

Метод String.lstrip избавит от лишнего пространства, созданного ': _', и оставит все остальные сообщения об ошибках без изменений.

Или, что еще лучше, используйте первое слово вашего пользовательского сообщения в качестве ключа:

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

Теперь полное сообщение будет «Мое личное сообщение» без лишних пробелов.

Если вы хотите, чтобы полное сообщение начиналось со слова с заглавной буквы, например «URL не может быть пустым», это невозможно сделать. Вместо этого попробуйте добавить другое слово в качестве ключа:

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

Теперь полное сообщение будет «URL не может быть пустым»

Круз Нуньес
источник
ооо, вы даже можете сделать errors.add(:_, 'foobar')и получить «foobar» в качестве сообщения
xxjjnn
6

Просто сделай это обычным способом:

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

Но покажите это так

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

Возвращает

"Email is required."

Метод локализации, безусловно, является «правильным» способом сделать это, но если вы делаете небольшой, неглобальный проект и хотите просто начать работать быстро - это определенно проще, чем скачкообразное изменение файлов.

Мне нравится возможность вставлять имя поля где-то кроме начала строки:

validates_uniqueness_of :email, :message => "There is already an account with that email."
brittohalloran
источник
2

Если вы хотите перечислить их все в хорошем списке, но без использования грубого, нечеловеческого имени, вы можете сделать это ...

object.errors.each do |attr,message|
  puts "<li>"+message+"</li>"
end
Адам
источник
1

По вашему мнению

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

Если вы хотите переопределить сообщение об ошибке без имени атрибута, просто добавьте к сообщению ^ следующим образом:

validates :last_name,
  uniqueness: {
    scope: [:first_name, :course_id, :user_id],
    case_sensitive: false,
    message: "^This student has already been registered."
  }
luckyruby
источник
не работает с рельсами 5.1 / ruby ​​2.4? получить название модели в этой области
Бен
@Ben работает для меня на Rails 5.1.2, Ruby 2.4.1p111. Можете ли вы поделиться своим кодом?
luckyruby
Я думаю, мне пришлось искать дальше, вы можете проверить код и его ответ там stackoverflow.com/q/45128434/102133
Бен
0

Я попробовал следующее, у меня сработало :)

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 %> 
Айгуль
источник
0

Вот мой код, который может быть полезен для вас, если он вам все еще нужен: Моя модель:

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

Затем я создал помощник для отображения сообщений:

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
Алексей Данилевский
источник
0

Уникальный подход, которого я не видел ни у кого!

Единственный способ получить все настройки, которые я хотел, это использовать after_validationобратный вызов, чтобы я мог манипулировать сообщением об ошибке.

  1. Разрешите создание сообщения проверки как обычно, вам не нужно пытаться изменить его в помощнике проверки.

  2. создайте after_validationобратный вызов, который заменит это сообщение проверки в серверной части, прежде чем оно попадет в представление.

  3. В этом after_validationметоде вы можете делать с сообщением проверки все, что захотите, как обычную строку! Вы даже можете использовать динамические значения и вставить их в сообщение проверки.


#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

Метод after_validation будет иметь гораздо большую область действия, чем встроенный помощник проверки рельсов, поэтому вы сможете получить доступ к объекту, который вы проверяете, так же, как вы пытаетесь делать с object.file_name. Что не работает в помощнике проверки, где вы пытаетесь вызвать его.

Примечание: мы используем, ^чтобы избавиться от имени атрибута в начале проверки, как @Rystraum указал на ссылку на этот гем

Сами Бирнбаум
источник
0

Ответ Graywh является лучшим, если он на самом деле отличается в отображении имени поля. В случае динамического имени поля (на основе других полей для отображения), я бы сделал что-то вроде этого

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

Метод full_message в else - это то, что rails использует внутри метода full_messages, поэтому он выдаст обычные ошибки Rails для других случаев (Rails 3.2 и выше)

Нгуен Данг
источник