How to show nested form validation errors after the validation errors for the parent model?

戏子无情 提交于 2020-01-04 04:46:28

问题


Using Ruby on Rails 4.2, I have a nested form. When testing the validations for the entire form, I noticed that the validation errors for the nested form appear at the top of the validation errors list, with the validation errors for the main form appearing below.

This is the opposite order that they are declared (since the fields_for has to appear within the scope of the parent form_for), so it looks like this:

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

But the validation errors appear like this (using blank as an example validation error):

  • NestedModelName nested #1 can't be blank.
  • NestedModelName nested #2 can't be blank.
  • Name can't be blank.
  • Description can't be blank.
  • Others can't be blank.

This is confusing for the user, since the errors appear out of order to how they appear on the page. It don't expect it to be in the correct position according to where it appears in the form, since it is obviously just validating each model in turn, but since the nested form model is usually subordinate, it should at least be added to the end instead of showing up in the beginning. Is there any way to get the nested form validation errors to appear after the parent form validation errors?

Additional Info:

Errors are being displayed in the view using the following:

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

and by using this in each view file containing a form:

<%= error_messages(@model) %>

回答1:


Update 1:

I discovered that the answer by februaryInk was very close to correct if you don't need to worry about i18n and translation of your application text. If you put the has_many :child_model underneath all your validations, the validations will appear in the correct order. However, full_messages doesn't appear to translate model or attribute names using the locale files, so if you require the error messages to be translated (which I do), my answer still seems like a decent solution.

Update 2:

Just realized after posting the first update that I could simplify my code that generates the messages list a lot by removing the part that does the ordering using the discovery in update 1, and just keep the part that does the translation. So here is my new solution, which is a combination of my update 1 and my original solution. All other information about the config/locales/xx.yml and config/application.rb files is still the same for this updated solution as it was for the original.

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

End of Update

I made a few changes to my error_messages function in application_helper.rb and now have everything working the way I wanted: main form validation errors are on the top, nested form validation errors are under those, the order of the errors does not change except moving the nested form errors under the main form errors.

My solution was to change the messages = line in error_messages as shown below and to add a private helper method. (This should probably be broken down into parts to make it easier to read and understand, but I built it up in the console to get what I wanted and just pasted it directly from there).

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

This solution should also work for other other locales, since I used I18n to get all the names. You will have to add the following also:

config/locales/en.yml

en:
  space: " "

This is so the model and attribute names will be handled correctly in languages that either have or don't have spaces between words (the first locale I need to support is Chinese, which doesn't have spaces between the words). If you did need to support Chinese, for example, you would use this:

config/locales/zh.yml

zh:
  space: ""

If you don't have to support this case, all instances of I18n.t('space') can be replaced with " ". The model and attribute names can also be translated as, but again if you don't need to support locales beyond English you don't need to do anything (although you can use the en.yml file to change the names of the model or attributes that are displayed).

As an example using en.yml to change the names displayed using the common Authors/Books example:

config/locales/en.yml

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

In this example the default, without the above additions to en.yml, would be:

  • Name can't be blank.
  • Book name can't be blank.
  • Book published can't be blank.

But with the above additions to en.yml it would be:

  • Nom de plume can't be blank.
  • Manuscript title can't be blank.
  • Manuscript year can't be blank.

And of course, if you have a zh.yml file with the appropriate translations, whatever you have in those would show up instead.

If you do need to support multiple locales, don't forget to add the following to config/application.rb (this part was only tested superficially, and may need some additional configuration):

config/application.rb

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



回答2:


The order for error messages seems to reflect the order of the validations and accepts_nested_attributes_for in your model file. Put the validations in the order you want them to come, with accepts_nested_attributes_for last. To get the order you gave as an example, try this:

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

...

The order of the individual validations in each hash also seems to have an effect in that it changes the order that a particular attribute's error messages are displayed.



来源:https://stackoverflow.com/questions/30181303/how-to-show-nested-form-validation-errors-after-the-validation-errors-for-the-pa

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!