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

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

This is the opposite order in which they are declared (as it fields_for

should appear in the scope of the parent form_for

), so it looks like this:

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

      

But the validation errors look like this (using a space as an example of a validation error):

  • Nested # 1 NestedModelName cannot be empty.
  • Nested # 2 NestedModelName cannot be empty.
  • The name cannot be empty.
  • Description cannot be empty.
  • Others cannot be empty.

This is confusing to the user as the errors appear out of order as they appear on the page. It does not expect it to be in the correct position depending on where it appears in the form, as it is obviously just checking each model in turn, but since the nested form model is usually subordinate, it should at least be added to a do not appear at the beginning. Is there a way to get nested form validation errors after parent form validation errors appear?

Additional Information:

Errors are 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 using this in every view file that contains a form:

<%= error_messages(@model) %>

      

+3


source to share


2 answers


Update 1 :

I found that februaryInk's answer was very close to the rule of thumb if you don't have to worry about i18n and your application text translation. If you put has_many :child_model

under all of your checks, the checks will appear in the correct order. However, full_messages

does not display model names or attributes using locale files, so if you need the error messages to be translated (which I do), my answer still looks like a decent solution.

Update 2:

It's just that after posting the first update, I was able to simplify my code, which expands the list significantly messages

, by removing the part that makes the order using detection 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 files config/locales/xx.yml

and config/application.rb

remains unchanged for this updated solution as 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

...

      

Application / 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 function error_messages

in application_helper.rb

and now everything works as I wanted: main form validation errors are at the top, nested form validation errors are below them, the order of errors does not change except for moving nested form errors on main form errors.

My solution was to change the line messages =

in error_messages

as shown below and add a private helper method. (This should probably be broken down to make it easier to read and understand, but I created it in the console to get what I wanted and just pasted it right in from there).

Application / 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 work for other other locales as well as I used I18n

to get all names. You also need to add the following:

config / locale / en.yml



en:
  space: " "

      

This way, model and attribute names will be handled correctly in languages ​​that have or don't have spaces between words (the first language I need to support is Chinese, which has no spaces between words). If you needed to support Chinese, for example, you would use this:

config / locale / zh.yml

zh:
  space: ""

      

If you don't need to support this case, all instances I18n.t('space')

can be replaced with " "

. Model and attribute names can also be translated as, but again, if you don't need to support locales outside of English, you don't need to do anything (although you can use a file en.yml

to change the model names or the attributes that are displayed).

As an example using en.yml

to change names displayed using a generic authors / books example:

config / locale / 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:

  • The name cannot be empty.
  • Book name cannot be empty.
  • The published book cannot be empty.

But with the above additions to en.yml

it, this would be:

  • Nom de plume cannot be empty.
  • The manuscript title cannot be empty.
  • The year of the manuscript cannot be empty.

And of course, if you have a file zh.yml

with the corresponding translations, whatever you have in them will be displayed instead.

If you need to support multiple locales, be sure to add the following to config/application.rb

(this part has only been superficially tested and may require some additional configuration):

config / application.rb

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

      

+2


source


The order of error messages appears to reflect the order of checks and accepts_nested_attributes_for

in the model file. Place the validations in the order you want them to arrive 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 checks in each hash also seems to have an effect in that it changes the order in which the error messages of a particular attribute are displayed.

+2


source







All Articles