使用Ruby on Rails 4.2,我有一个嵌套的表单。在测试整个表单的验证时,我注意到嵌套表单的验证错误出现在验证错误列表的顶部,主表单的验证错误出现在下面。
这是它们被声明的相反顺序(因为fields_for
必须出现在父form_for
的范围内),所以它看起来像这样:
[name ]
[description ]
[others ]
[nested #1 ]
[nested #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">×</button>
<p>#{sentence}</p>
<ul>
#{messages}
</ul>
</div>
HTML
end
并在包含表单的每个视图文件中使用它:
<%= error_messages(@model) %>
答案 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.yml
和config/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
的上述补充,它将是:
当然,如果您有一个zh.yml
文件,其中包含相应的翻译,那么您所拥有的内容就会显示出来。
如果您确实需要支持多个区域设置,请不要忘记将以下内容添加到config/application.rb
(此部分仅在表面上进行了测试,可能需要一些其他配置):
配置/ application.rb中
config.i18n.available_locales = [:zh, :en]
config.i18n.default_locale = :en