使用 Rails 的完全自定义验证错误消息

Fully custom validation error message with Rails

提问人:marcgg 提问时间:5/1/2009 最后编辑:marcgg 更新时间:7/8/2023 访问量:264040

问:

使用 Rails,我试图在保存时收到类似“歌曲字段不能为空”的错误消息。执行以下操作:

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

...仅显示“Song Rep XYW can't be empty”,这并不好,因为字段的标题对用户不友好。如何更改字段本身的标题?我可以更改数据库中字段的实际名称,但我有多个“歌曲”字段,并且我确实需要有特定的字段名称。

我不想绕过 rails 的验证过程,我觉得应该有一种方法来解决这个问题。

Ruby-on-Rails(红宝石轨道)

评论


答:

68赞 Maulin 5/1/2009 #1

试试这个。

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

在这里找到了这个。

Rails 3 到 6 的更新:

validate do |user|
  user.errors.add(:base, "Country can't be blank") if user.country_iso.blank?
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

上面的代码来自这里

评论

0赞 marcgg 5/1/2009
问题是我的字段叫做 :song_rep_xyz(嗯,很复杂),这对用户不友好
17赞 spacemonkey 11/24/2010
对于 Rails 3,“def self.human_attribute_name(attr)”需要改为“def self.human_attribute_name(attr, options={})”,否则返回错误
4赞 Dan Barron 11/27/2013
谢谢你。我需要一些对 Rails 2 有用的东西。(是的,可怜的我...... :)
12赞 Ryan Bigg 5/1/2009 #2

我建议安装最初由 David Easley 编写的 custom_error_message gem(或作为插件

它可以让你做一些事情,比如:

validates_presence_of :non_friendly_field_name, :message => "^Friendly field name is blank"

评论

0赞 Jared Brown 1/31/2011
我过去曾使用过这个插件并取得了巨大的成功,尽管它似乎不再定期维护。
1赞 Dorian 5/2/2012
你可以将它作为 Rails 3 的 gem 安装。只需添加到您的 Gemfile 中 - 有关更多详细信息,请参阅 githubgem "custom_error_message"
0赞 olleicua 9/1/2015
正是我需要的
3赞 Rystraum 10/26/2016
@DickieBoy我确认 nanamkim 的 fork (github.com/nanamkim/custom-err-msg) 可以与 Rails 5 一起使用。它实际上与公认的答案相得益彰。我会把它写成一个单独的答案。
0赞 DickieBoy 10/26/2016
@Rystraum 在我的一生中,我不记得围绕这个的用例,但感谢您的回复!我一定会记住它,直到将来。
469赞 graywh 5/19/2010 #3

现在,设置人性化名称和自定义错误消息的公认方法是使用区域设置

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

现在,“email”属性的人性化名称状态验证消息已更改。

可以为特定模型+属性、模型、属性或全局设置验证消息。

评论

20赞 user160917 11/6/2011
如果您使用的是 mongoid,请将 activerecord: 替换为 mongoid:
89赞 Tyler Rick 12/16/2011
@graywh:如果不在评论中,关于答案的问题应该发布在哪里?以下是 I18n 指南:guides.rubyonrails.org/i18n.html
4赞 aceofspades 2/14/2012
顺便说一句:如果你在 Rails 3.1.3 中为验证器的 message 参数传递一个符号,它会告诉你它正在寻找的范围,因为它不会找到,所以你确切地知道在你的语言环境中放什么 yml。
4赞 panzi 2/28/2013
好吧,这很好,但是如果天真地在列名称前面加上标题(无论它多么可读)会导致语法完全错误(尤其是在非英语语言中)怎么办?我真的需要使用吗?我想知道错误是关于哪一列的,这样我就可以在正确的表单字段中显示它。errors.add :base, msg
7赞 panzi 3/11/2013
@graywh 也许我遗漏了一些东西,但它不是总是在消息之前加上列名吗?即使是英语,我也想拥有例如 或代替 和 。The password is wrong.The email address is not valid.Password is wrong.Email is not valid.
20赞 Marco Antonio 2/19/2011 #4

是的,有一种方法可以在没有插件的情况下做到这一点! 但它不如使用上述插件干净优雅。在这里。

假设是 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]

到目前为止,我们只显示一条消息,总是针对第一个错误。如果要显示所有错误,可以在哈希中循环并显示值。

希望能有所帮助。

评论

0赞 ARK 8/7/2020
可爱。我一直在为我的 API 应用程序寻找一条单行消息,您已经向我展示了如何操作错误类。 TY
2赞 adam 6/23/2011 #5

如果你想把它们都列在一个不错的列表中,但又不使用粗糙的非人类友好名称,你可以这样做......

object.errors.each do |attr,message|
  puts "<li>"+message+"</li>"
end
17赞 Lukas 8/18/2011 #6

带有完全本地化消息的 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"
6赞 brittohalloran 11/24/2011 #7

只需以正常方式进行操作:

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."
72赞 Federico 3/6/2012 #8

在模型中:

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

评论

0赞 Rômulo Ceccon 2/21/2013
这不是一个可接受的解决方案。如果我想要所有其他属性(attr + msg)的默认行为,该怎么办?
0赞 Federico 2/22/2013
给你。。你可以玩这两样东西并让它发挥作用
0赞 Federico 9/4/2015
你必须使用一个符号,这样它才会在你的 yml 文件中查找,比如validates_presence_of :address1, :message => :put_some_address_please
2赞 fatuhoku 11/11/2016
这是不可接受的,因为包含字段名称
0赞 satk0 2/1/2023
我认为我已经将这个答案改进为可以接受的,请查看。
16赞 amit_saxena 5/17/2012 #9

在自定义验证方法中,使用:

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

因为add_to_base已被弃用。

errors.add_to_base("Custom error message")

9赞 Cruz Nunez 10/23/2015 #10

这是另一种方法:

如果使用此模板:

<% 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不能为空”

评论

0赞 xxjjnn 10/12/2018
哎呀,你甚至可以做并得到“foobar”作为消息errors.add(:_, 'foobar')
1赞 alexventuraio 10/6/2021
好的方法,我根据这里的示例构建了自己的实现。谢谢!
13赞 Rystraum 10/26/2016 #11

已接受的答案和列表中的另一个答案相关:

我正在确认 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

1赞 luckyruby 12/2/2016 #12

在您的视野中

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."
  }

评论

0赞 Ben 7/16/2017
不适用于 Rails 5.1 / Ruby 2.4 ?获取该范围内的模型名称
0赞 luckyruby 7/17/2017
@Ben 在 Rails 5.1.2、Ruby 2.4.1p111 上对我有用。你能分享你的代码吗?
0赞 Ben 7/17/2017
我想我必须进一步研究,你可以检查代码和他的答案 stackoverflow.com/q/45128434/102133
0赞 Aigul 12/4/2016 #13

我试着跟随,为我工作:)

1 工作.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 %> 
14赞 cappie013 4/23/2017 #14

一种解决方案可能是更改 i18n 默认错误格式:

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

默认值为format: %{attribute} %{message}

3赞 Oleksii Danylevskyi 8/2/2017 #15

这是我的代码,如果您仍然需要它,它对您有用: 我的模型:

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赞 Aaron Wallentine 5/13/2022
+1 帮助处理复选框接受消息的情况......我想知道是否有办法在没有自定义错误助手的情况下做到这一点,该助手会检查寻找“^”字符串的消息?
0赞 Sami Birnbaum 11/22/2018 #16

一种我从未见过任何人提及的独特方法!

我能够获得我想要的所有自定义的唯一方法是使用回调来允许作错误消息。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方法将比内置的 rails 验证助手具有更大的范围,因此您将能够访问您正在验证的对象,就像您尝试使用 object.file_name 一样。这在您尝试调用它的验证帮助程序中不起作用。

注意:我们使用 来删除验证开始时的属性名称,正如@Rystraum指出的那样引用此 gem^

0赞 An Nguyen Dang 1/17/2020 #17

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

else 上的 full_message 方法是 Rails 在 full_messages 方法中使用的方法,因此在其他情况下(Rails 3.2 及更高版本)会给出正常的 Rails 错误

0赞 satk0 2/1/2023 #18

升级了@Federico的答案,适用于所有字段错误。

在您的 :controller.rb

flash[:alert] = @model.errors.messages.values 
# [["field1_err1", "field1_err2"], ["field2_err1"], ["field3_err1"]]

messages方法“返回属性的哈希值及其错误消息数组”,如 rails 文档中所示。

然后,要在表单中显示这些错误,请执行以下操作:

<% flash.each do |type, type_arr| %>
  <% type_arr.each do |msg| %>
    <ul>
      <li>
        <%= msg.to_sentence %>
      </li>
    </ul>
  <% end %>
<% end %>