如何在父模型的验证错误后显示嵌套表单验证错误?

时间:2015-05-12 03:32:55

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

使用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) %>

2 个答案:

答案 0 :(得分:2)

错误消息的顺序似乎反映了模型文件中验证和<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android" > <!-- Border --> <item> <shape> <solid android:color="@color/gray"></solid> </shape> </item> <!-- Body --> <item android:bottom="1dp" android:right="0dp" android:left="0dp" android:top="0dp"> <shape> <solid android:color="@color/white"></solid> </shape> </item> </layer-list> <EditText android:id="@+id/edt_password" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:background="@drawable/edittext" android:digits="1234567890" android:ellipsize="end" android:focusableInTouchMode="true" android:inputType="numberPassword" android:singleLine="true" android:textColor="@color/dark" /> 的顺序。将验证按照您希望的顺序排列,最后使用accepts_nested_attributes_for。要获得您给出的订单,请尝试以下方法:

parent_model.rb

accepts_nested_attributes_for

child_model.rb

...

validates :name, # validations hash
validates :description, # validations hash
validates :others, # validations hash

accepts_nested_attributes_for :child_model

...

每个哈希中各个验证的顺序似乎也会产生影响,因为它会更改显示特定属性的错误消息的顺序。

答案 1 :(得分:2)

更新1

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

更新2:

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

应用程序/模型/ 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

...

应用程序/模型/ child_model.rb

...

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

...

应用程序/助手/ 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

更新结束

我对error_messages中的application_helper.rb函数进行了一些更改,现在所有内容都按照我想要的方式运行:主要表单验证错误位于顶部,嵌套表单验证错误在这些之下,除了在主窗体错误下移动嵌套表单错误外,错误的顺序不会改变。

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

应用程序/助手/ 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来获取所有名称。您还必须添加以下内容:

配置/区域设置/ en.yml

en:
  space: " "

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

配置/区域设置/ zh.yml

zh:
  space: ""

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

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

配置/区域设置/ 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(此部分仅在表面上进行了测试,可能需要一些其他配置):

配置/ application.rb中

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