Custom Validation Messages In Rails, the Clean and Easy Way
Posted by g. | Filed under Ruby on Rails
Rails rocks, but let’s be honest. If there’s one thing that sucks about it out of the box, it’s validation and error message handling. This is probably one of the most often asked about topics, and I’ve read dozens of tutorials and seen many plugins intent on fixing the issue.
So, let’s take a second and recap the problem. You’ve got your model object, and you’ve set up your validations.
# models/user.rb validates_presence_of :login, :message => "can't be blank" validates_presence_of :email, :message => "can't be blank"
You try to save your model, validation fails, and you get this ugly thing:
2 errors prohibited this User from being saved
There were problems with the following fields:
- Login can’t be blank
- Email can’t be blank
You’re going to get those cryptic first two lines, and for each error message, the name of the column in the database will precede whatever message you write in. Pretty constricting, if you ask me. Sure, in this case Login can’t be blank and Email can’t be blank don’t actually sound that bad. But imagine if you wanted to keep the column in the database as ‘login’, but to the user you wanted to display Username can’t be blank. Or maybe you want it to say Sorry dude, your username can’t be blank!. You get the point.
In order for your error messages to actually show up, in your erb template you will surely be using the error_messages_for helper. I’m going to show you a quick way of getting rid of (or customizing) those first two lines 2 errors prohibited… and There were problems…. You can use the :header_message and :message options, like so:
# passing nil to the options will get rid of the messages! error_messages_for :user, :header_message => "Custom message one", :message => "Custom message two"
Phew, that’s so much better already! Ok, but we’re still faced with the problem of the error messages themselves. I don’t know about you, but I want to make it so that whatever validation message I specify is exactly what gets shown, and nothing else… like so:
# models/user.rb validates_presence_of :email, :message => "Hey man, you left your email address blank!"
Forget installing plugins, and forget doing user.errors.add_to_base (if you don’t know what I mean by that, don’t worry about it). Instead, let’s overwrite the error_messages_for helper. This way, we’re not changing all your model objects (thus keeping things clean and tidy), and there’s the added bonus of actually understanding what’s going on behind the scenes.
Go here to actually get the error_messages_for source code (click on the show source link). Copy that bad boy, go to your app, and under your app/helpers directory, create a new file. Call it errors_helper.rb. Paste the code in there, such that your file looks like this:
module ErrorsHelper def error_messages_for(*params) # everything else you copied... end end
Take a moment to study this code. If your Ruby know-how is pretty good, you should be able to pretty much tell what’s going on here. There’s a variable named contents, and in that the code throws the :header-message and :message we talked about before, and then an html unordered list of your validation messages.
We’re going to go completely bare-bones here, so I’m going to assume all you want is an unordered list of YOUR validation messages. So let’s get to it. What needs to change is everything below the options[:object_name] ||= params.first line, so that’s all I’m going to show for the sake of brevity.
#options[:header_message] = "#{pluralize(count, 'error')} prohibited this #{options[:object_name].to_s.gsub('_', ' ')} from being saved" unless options.include?(:header_message) #options[:message] ||= 'There were problems with the following fields:' unless options.include?(:message) #error_messages = objects.map {|object| object.errors.full_messages.map {|msg| content_tag(:li, msg) } } error_messages = objects.map {|object| object.errors.collect{ |column,error| content_tag( :li, error ) } } contents = '' #contents << content_tag(options[:header_tag] || :h2, options[:header_message]) unless options[:header_message].blank? #contents << content_tag(:p, options[:message]) unless options[:message].blank? contents << content_tag(:ul, error_messages) content_tag(:div, contents, html) # you still need the else '' end here
If you look closely, instead of taking errors.full_messages, we’re taking the column and error for each one (in our previous example column would be “Email” and error would be “can’t be blank”) and we’re IGNORING the column. We’re only keeping the “can’t be blank” portion of it, which you’re going to make into something custom and pretty such as “You left your email blank”.
Oh, and one more important thing! To tell your app to use your new improved errors_helper.rb, add this to the top of your app/controllers/application.rb:
helper :errorsSo there you have it! Now all you have to do is go style your error div to your heart’s content. If you see any mistakes or have any questions/suggestions, I’d love to hear your comments!
Tags: error messages, rails, ruby, validation
11 Responses to “Custom Validation Messages In Rails, the Clean and Easy Way”
-
Nico Says:
July 15th, 2008 at 1:18 amThanks, nice. It would be even better if the default behaviour was kept if the :message param is omitted in the model. I might look into that.
-
Eric Berry Says:
August 7th, 2008 at 10:52 amAwesome job! This is exactly what I was looking for. Thanks!!!
-
gary Says:
September 12th, 2008 at 4:09 pmnicely done…but you need to uncomment the lines between contents=” and contents << content_tag(:ul, error_messages) or you don’t get the header lines. Also if you change the condition at the end of those two lines to unless (!options[:header_message].blank? and options.include?(:header_message)) and
unless (!options[:message].blank? and options.include?(:message)) you get the default 2 header lines if you don’t specify them in error_messages_for -
sean Says:
September 15th, 2008 at 11:03 amcool, good tip!
can you order or prioritize the validation constraints in a model ?I have multiple constraints, and don’t want dozens of messages if a user leaves a field blank.
I just want to say, that can’t be blank, not “it can’t be blank and it must be numeric and it must be between 5 and 10 digits and…. and… and…”
get me ?
-
Farouk Says:
October 4th, 2008 at 4:25 pmgreat trick, unfortunately, it didn’t work with the following:
validates_length_of :first_name, :within => 3..25 , :message => ‘First name must be between 3 and 25 characters”
any clue?
-
Lenart Rudel Says:
October 13th, 2008 at 5:18 pm@Farouk: validates_length_of takes more paramters. Try setting :too_short, :wrong_length and others
have a look at Rails API to see available parameters. -
Lenart Rudel Says:
October 14th, 2008 at 4:34 am@Farouk: try setting the :too_short and :too_long messages and it will work.
Also another way of changing default error display is by installing advanced_errors plugin (http://github.com/markcatley/advanced_errors/tree/master).
-
arT le Vark Says:
December 17th, 2008 at 5:54 amgreat help!
being a newbie, where can i change the default css-class (ie. ‘fieldWithErrors’) that gets added to the input-field? -
SpeakMy.Name » Blog Archive » Custom validation errors in Rails and ActiveRecord Says:
January 9th, 2009 at 9:09 am[...] validation errors for ActiveRecord I’ve found a number of different solution, including some quite complex ones those rewrite the helper to get required behavior messages. But it seems that the simplest possible [...]
-
g. Says:
February 5th, 2009 at 11:13 am@arT le Vark: Look in \public\stylesheets\scaffold.css. The ‘fieldWithErrors’ class is in there.
-
Kreso Says:
May 20th, 2009 at 10:02 pmGreat stuff, thanks.