【问题标题】:Fully custom validation error message with Rails使用 Rails 完全自定义验证错误消息
【发布时间】:2021-12-09 08:53:12
【问题描述】:

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

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

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

我不想破解 Rails 的验证过程,我觉得应该有办法解决这个问题。

【问题讨论】:

    标签: ruby-on-rails


    【解决方案1】:

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

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

    现在人性化名称​​和“电子邮件”属性的存在验证消息已更改。

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

    【讨论】:

    • 如果您使用的是 mongoid,请将 activerecord: 替换为 mongoid:
    • @graywh:如果不在 cmets 中,应该在哪里发布有关答案的问题?这是 I18n 指南:guides.rubyonrails.org/i18n.html
    • 顺便说一句:如果你在 Rails 3.1.3 中为你的验证器的 message 参数传递一个符号,它会告诉你它正在寻找的范围,因为它不会找到,所以你确切地知道在你的语言环境 yml 中放什么。
    • 嗯,这很好,但是如果天真地预先添加列名(无论它多么易于阅读)会导致完全错误的语法(尤其是在非英语语言中)?我真的需要使用errors.add :base, msg吗?我想知道错误是关于哪一列的,所以我可以在正确的表单字段中显示它。
    • @graywh 也许我遗漏了一些东西,但它不是总是在消息之前添加列名吗?即使是英语,我也想拥有例如The password is wrong.The email address is not valid. 而不是 Password is wrong.Email is not valid.
    【解决方案2】:

    在您的模型中:

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

    【讨论】:

    • 这不是一个可接受的解决方案。如果我想要所有其他属性(attr + msg)的默认行为怎么办?
    • 你去吧..你可以玩这两个东西并让它发挥作用
    • 您必须使用一个符号,这样它才能在您的 yml 文件中查找,例如 validates_presence_of :address1, :message =&gt; :put_some_address_please
    • 这是不可接受的,因为包含了字段名称
    【解决方案3】:

    试试这个。

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

    我找到了这个here

    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
    

    以上代码来自here

    【讨论】:

    • 问题是我的字段叫 :song_rep_xyz (嗯,有些复杂),对用户不友好
    • 对于Rails 3,“def self.human_attribute_name(attr)”需要改成“def self.human_attribute_name(attr, options={})”,否则返回错误
    • 谢谢。我需要一些适用于 Rails 2 的东西。(是的,可怜的我...... :)
    【解决方案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]
    

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

    希望有所帮助。

    【讨论】:

    • 可爱。我正在为我的 API 应用程序寻找一条单行消息,您已经向我展示了如何操作错误类。 TY
    【解决方案5】:

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

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

    因为 add_to_base 已被弃用。

    errors.add_to_base("Custom error message")

    【讨论】:

      【解决方案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"
      

      【讨论】:

        【解决方案7】:

        accepted answeranother answer down the list相关:

        我确认 nanamkim's fork of 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

        【讨论】:

          【解决方案8】:

          我建议安装最初由 David Easley 编写的 custom_error_message gem(或 plugin

          它可以让你做这样的事情:

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

          【讨论】:

          • 我过去使用过这个插件并取得了巨大的成功,尽管它似乎不再定期维护了。
          • 您也可以将其安装为 rails 3 的 gem。只需将 gem "custom_error_message" 添加到您的 Gemfile - 请参阅 github 了解更多详细信息
          • 正是我需要的
          • @DickieBoy 我确认 nanamkim 的 fork (github.com/nanamkim/custom-err-msg) 适用于 Rails 5。它实际上与接受的答案很相配。我将把它写成一个单独的答案。
          • @Rystraum 对于我的一生,我不记得这个用例,但感谢您的回复!我一定会记住它以备不时之需。
          【解决方案9】:

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

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

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

          【讨论】:

            【解决方案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 不能为空”

            【讨论】:

            • ooo,你甚至可以通过errors.add(:_, 'foobar') 获取“foobar”作为消息
            • 好方法,我根据此处的示例构建自己的实现。谢谢!
            【解决方案11】:

            按正常方式做:

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

            【讨论】:

              【解决方案12】:

              如果您想将它们全部列出在一个不错的列表中,但不使用粗俗的非人类友好名称,您可以这样做...

              object.errors.each do |attr,message|
                puts "<li>"+message+"</li>"
              end
              

              【讨论】:

                【解决方案13】:

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

                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
                

                【讨论】:

                  【解决方案14】:

                  在你看来

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

                  【讨论】:

                  • 不适用于 rails 5.1 / ruby​​ 2.4 吗?获取该范围内的模型名称
                  • @Ben 在 Rails 5.1.2、Ruby 2.4.1p111 上为我工作。你能分享你的代码吗?
                  • 我想我得再看看,你可以在那里查看代码和他的答案stackoverflow.com/q/45128434/102133
                  【解决方案15】:

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

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

                  【讨论】:

                    【解决方案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

                    【讨论】:

                      【解决方案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 错误

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2011-10-21
                        • 1970-01-01
                        • 2016-04-18
                        • 2016-12-04
                        • 1970-01-01
                        相关资源
                        最近更新 更多