【问题标题】:How to show nested form validation errors after the validation errors for the parent model?如何在父模型的验证错误后显示嵌套表单验证错误?
【发布时间】:2015-05-12 03:32:54
【问题描述】:

使用 Ruby on Rails 4.2,我有一个嵌套表单。在测试整个表单的验证时,我注意到嵌套表单的验证错误出现在验证错误列表的顶部,而主表单的验证错误出现在下方。

这是它们被声明的相反顺序(因为fields_for必须出现在父form_for的范围内),所以它看起来像这样:

[name        ]
[description ]
[others      ]
[nested #1   ]
[nested #2   ]

但是验证错误看起来像这样(以空白为例验证错误):

  • NestedModelName 嵌套 #1 不能为空。
  • NestedModelName 嵌套 #2 不能为空。
  • 名称不能为空。
  • 描述不能为空。
  • 其他不能为空。

这会让用户感到困惑,因为错误在页面上的显示方式是乱序的。它不期望它根据它在表单中出现的位置处于正确的位置,因为它显然只是依次验证每个模型,但由于嵌套的表单模型通常是从​​属的,所以至少应该将它添加到结束而不是在开始时出现。有没有办法让嵌套表单验证错误出现在父表单验证错误之后?

附加信息:

使用以下内容在视图中显示错误:

application_helper.rb

def error_messages(resource)

    return '' if resource.errors.empty?

    messages = resource.errors.full_messages.map { |msg| content_tag(:li, msg) }.join
    sentence = I18n.t('errors.messages.not_saved',
                      count: resource.errors.count,
                      resource: resource.class.model_name.human.downcase)
    html = <<-HTML
    <div class="validation-error alert alert-danger alert-dismissable fade in alert-block">
      <button type="button" class="close" data-dismiss="alert" aria-hidden="true">&times;</button>
      <p>#{sentence}</p>
      <ul>
        #{messages}
      </ul>
    </div>
    HTML

  end

并在每个包含表单的视图文件中使用它:

<%= error_messages(@model) %>

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-4 nested-forms


    【解决方案1】:

    更新 1

    如果您不需要担心 i18n 和应用程序文本的翻译,我发现 februaryInk 的答案非常接近正确。如果您将has_many :child_model 置于您的所有验证中,验证将以正确的顺序显示。但是,full_messages 似乎没有使用语言环境文件翻译模型或属性名称,所以如果您需要翻译错误消息(我这样做),我的回答似乎仍然是一个不错的解决方案。

    更新 2:

    在发布第一个更新后刚刚意识到,我可以通过删除使用更新 1 中的发现进行排序的部分来大量简化生成 messages 列表的代码,而只保留进行翻译的部分。所以这是我的新解决方案,它结合了我的更新 1 和我的原始解决方案。对于此更新的解决方案,有关 config/locales/xx.ymlconfig/application.rb 文件的所有其他信息仍与原始解决方案相同。

    app/models/parent_model.rb

    ...
    
    validates :name, # validations hash
    validates :description, # validations hash
    validates :others, # validations hash
    
    has_many :child_models
    accepts_nested_attributes_for :child_models
    
    ...
    

    app/models/child_model.rb

    ...
    
    validates :nested_1, # validations hash
    validates :nested_2, # validations hash
    
    ...
    

    app/helpers/application_helper.rb

    messages = resource.errors.messages.keys.map {|value| error_message_attribute(resource, value) + I18n.t('space') + resource.errors.messages[value].first}.map { |msg| content_tag(:li, msg) }.join
    
    private
      def error_message_attribute(resource, symbol)
        if symbol.to_s.split(".").length > 1
          model_name, attribute_name = symbol.to_s.split(".")
          model_class = model_name.singularize.camelize.constantize
          model_class.model_name.human + I18n.t('space') + model_class.human_attribute_name(attribute_name).downcase
        else
          resource.class.human_attribute_name(symbol)
        end
      end
    

    更新结束

    我在application_helper.rb 中对我的error_messages 函数进行了一些更改,现在一切都按我想要的方式工作:主表单验证错误在顶部,嵌套表单验证错误在这些错误的下面,错误的顺序除了将嵌套表单错误移到主表单错误下之外,不会改变。

    我的解决方案是更改error_messages 中的messages = 行,如下所示,并添加一个私有辅助方法。 (这可能应该分成几部分,以便于阅读和理解,但我在控制台中构建它以获得我想要的,然后直接从那里粘贴)。

    app/helpers/application_helper.rb

    messages = Hash[resource.errors.messages.keys.map.with_index(1) { |attribute, index| [attribute, [index, attribute.match(/\./) ? 1 : 0]] }].sort_by {|attribute, data| [data[1], data[0]]}.collect { |attributes| attributes[0]}.map {|value| error_message_attribute_name(resource, value) + I18n.t('space') + resource.errors.messages[value].first}.map { |msg| content_tag(:li, msg) }.join
    
    private
        def error_message_attribute_name(resource, symbol)
          if symbol.to_s.split(".").length > 1
            model_name, attribute_name = symbol.to_s.split(".")
            model_class = model_name.singularize.camelize.constantize
            model_class.model_name.human + I18n.t('space') + model_class.human_attribute_name(attribute_name).downcase
          else
            resource.class.human_attribute_name(symbol)
          end
        end
    

    此解决方案也适用于其他其他语言环境,因为我使用 I18n 来获取所有名称。您还必须添加以下内容:

    config/locales/en.yml

    en:
      space: " "
    

    这样模型和属性名称将在单词之间有或没有空格的语言中正确处理(我需要支持的第一个语言环境是中文,它在单词之间没有空格)。如果你确实需要支持中文,例如,你会使用这个:

    config/locales/zh.yml

    zh:
      space: ""
    

    如果您不必支持这种情况,I18n.t('space') 的所有实例都可以替换为" "。模型和属性名称也可以翻译为,但同样,如果您不需要支持英语以外的语言环境,则无需执行任何操作(尽管您可以使用 en.yml 文件更改模型名称或显示的属性)。

    例如,使用en.yml 更改使用常见作者/书籍示例显示的名称:

    config/locales/en.yml

    en:
      activerecord:
        models:
          author: "writer"
          book: "manuscript"
        attributes:
          author:
            name: "non de plume"
          book:
            name: "title"
            published: "year"
    

    在此示例中,如果没有对 en.yml 进行上述添加,则默认值为:

    • 名称不能为空。
    • 书名不能为空。
    • 出版的图书不能为空白。

    但在en.yml 中添加上述内容后,它将是:

    • Nom de plume 不能为空。
    • 稿件标题不能为空。
    • 稿件年份不能为空。

    当然,如果您有一个带有适当翻译的zh.yml 文件,那么您在其中的任何内容都会显示出来。

    如果确实需要支持多语言环境,别忘了在config/application.rb中添加以下内容(这部分只是表面测试,可能需要一些额外的配置):

    config/application.rb

    config.i18n.available_locales = [:zh, :en]
    config.i18n.default_locale = :en
    

    【讨论】:

      【解决方案2】:

      错误消息的顺序似乎反映了模型文件中验证和accepts_nested_attributes_for 的顺序。按照您希望它们出现的顺序放置验证,accepts_nested_attributes_for 最后。以您给出的订单为例,试试这个:

      parent_model.rb

      ...
      
      validates :name, # validations hash
      validates :description, # validations hash
      validates :others, # validations hash
      
      accepts_nested_attributes_for :child_model
      
      ...
      

      child_model.rb

      ...
      
      validates :nested_1, # validations hash
      validates :nested_2, # validations hash
      
      ...
      

      每个哈希中单个验证的顺序似乎也有影响,因为它改变了特定属性的错误消息的显示顺序。

      【讨论】:

      • 我已经尝试在验证父模型之后添加accepts_nested_attributes(我首先想到的)。这对订单没有任何影响。
      • 请问您是如何在视图中显示错误的?如题,请您将其添加到您的问题中吗?
      • 添加了错误显示方式的信息。
      • 感谢您添加信息。抱歉,我有一段时间无法回复,也没有帮助您,但我很高兴您明白了这一点。
      • 我希望一些更简单的东西会起作用,就像你建议的那样,但似乎不是这样。仍然认为可能有一些选项或配置可以做到这一点,但我花了几个小时寻找,却一无所获。
      猜你喜欢
      • 2012-10-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-08-24
      • 2020-11-14
      • 1970-01-01
      相关资源
      最近更新 更多