Display Error Messages For Multiple Models in Rails

Posted on May 22, 2006 by Scott Leberknight

Normally in Rails when you want to display validation error messages for a model object, you simply use the error_messages_for() helper method. For simple sites this is usually just fine, as it displays a message stating that something went wrong along with a list of validation error messages. If you use the Rails helper methods to generate your HTML controls, then those fields are also wrapped in a div element which can be styled to indicate there were problems with that field. So you have some explanation of the validation errors, generally at the top of the page, and the fields where validation errors occurred can be styled as such, for example with a red outline or a red backgroun or whatever makes it clear to users there is a problem with the field.

This works very well when you are validating a single model object. But what if you are validating multiple models? You could have a separate error_messages_for() for each model object. That will work, but it is ugly, since you'll have a separate list of error messages for every model object you are validating. I searched the Rails API and could not find a method to display errors for multiple models, so I wrote one that is based on the error_messages_for() method. Basically I copied Rails' error_messages_for() method and then modified it to display messages for multiple models.

It works like this. You pass in an array of object names for which to collect and display errors instead of a single object name. The first object name is assumed to be the "main" object (e.g. the "master" in a master/detail relationship) and is used in the error explanation should any validation errors occur. For example, assume you have a page that allows you to create a stock watch list and also add stock ticker symbols for that watch list. The "main" object here should be the watch list object, and if validation errors occur then the message the gets printed is "2 errors prohibited this watch list from being saved." This makes some sense, at least to me, since the watch list is the main thing you are saving; the stock ticker symbols can be entered if the user wants, but are not required since they could be added later. As with the Rails error_messages_for() method, you can pass in additional options.

That's pretty much it, except for the code. So here is the code:

def error_messages_for_multiple_objects(object_names, options = {})
  options = options.symbolize_keys
  object_name_for_error = object_names[0]
  all_errors = ""
  all_errors_count = 0
  object_names.each do |object_name|
    object = instance_variable_get("@#{object_name}")
    if object && !object.errors.empty?
      object_errors = object.errors.full_messages.collect {
        |msg| content_tag("li", msg)
      }
      all_errors_count += object_errors.size
      all_errors << "#{object_errors}"
    end
  end

  if all_errors_count > 0
    tag = content_tag("div",
      content_tag(
        options[:header_tag] || "h2",
        "#{pluralize(all_errors_count, "error")} prohibited this" \
        " #{object_name_for_error.to_s.gsub("_", " ")} from being saved"
      ) +
      content_tag("p", "There were problems with the following fields:") +
      content_tag("ul", all_errors),
        "id" => options[:id] || "errorExplanation",
        "class" => options[:class] || "errorExplanation"
    )
  else
    ""
  end
end

The code works like this. First we extract the name of the "main" object as the first element of the object_names array. Next we loop through all the model objects, checking if there are any errors. If there are errors we collect them in a string containing an li tag for each error and append them to the all_errors string. Once we've checked all the objects for errors, and if there were errors, we wrap all_errors in a div containing a header, the main error message, and finally create an unordered list and stuff all_errors in that list. If there were no errors at all we simply return an empty string. That's it. Now you can easily display a list of validation errors for multiple model objects, using code like this: error_messages_for_multiple_objects( ["watch_list", "stock1", "stock2"] ). Of course if we have an unknown number of stocks in this case, then we could construct the array of object names first and then pass it into the method, which would be more flexible.



Post a Comment:
Comments are closed for this entry.