One of the sweet things Rails provides is practically free error notification on forms. With just a little bit of
<%= error_messages_for :post %>validations on your model, and some logic in your controller, bam, instant error messaging. Very simple.
But what if your form is using AJAX to post? The page doesn't re-render, so how is our buddy, error_messages_for, going to work? Well its not, we're gonna have to use AJAX to display the errors. It turns out this isn't too touch, but as usual we want to keep it DRY.
The Controller
class PostsController < ApplicationController def create @post = Post.new(params[:post]) @post.save! # automatically renders create.rjs.erb rescue ActiveRecord::RecordInvalid @model = @kit_item render :template => 'shared/validation_error.rjs.erb' end end
We will attempt to create the Post. If all goes well, then the create rjs action will fire and all is well. But if the record fails validation, we catch it, assign the current model to @model, and render shared/validaiton_error.rjs.erb. This is our generic AJAX action for rendering errors for Models.
The AJAX
Create shared/validation_error.rjs and use this little snippit, and thats pretty much it for this part.
page.replace_html "errors_for_#{@model.class.name.underscore}", "Oops! <ul>" + @model.errors.collect{|k,v| "<li>The #{k} #{v}</li>"}.to_s + "</ul>" page.visual_effect :highlight, "errors_for_#{@model.class.name.underscore}", :startcolor => "'#ff0000'", :endcolor => "'#aca2b6'"
So, the errors just get collected and printed out to a div in the page. Let's go add this fancy div.
The View
This part is pretty simple. You just need to open up your form partial, posts/_form.html.erb and add this little view helper to the form:
<%= error_div_for post %>
This does nothing but prints out a div with a specific ID to prepare for our RJS action, if it's needed. So let's go make the view helper.
module ApplicationHelper def error_div_for(model) %{<div id="errors_for_#{model.class.name.underscore}"></div>} end end
Now we can just call this simple view helper and pass in any model and it will dynamically build a div with the appropriate ID inside.
And thats pretty much it. To go through it again, when the form is loaded, the error div will be placed in the page, waiting to be used. Once the form is submitted in the background, the controller's create action will attempt to save it. If the record is invalid with validation errors, then the universal validation_errror RJS template will be fired off and replace all the errors from the failed validation, into the div we placed in the page. Oh and of course a little red flash action to make sure the user sees the errors.
Popularity: 15% [?]




About
Mmm, Del.icio.us
Just curious. Why did you break consistency in the view with #{Inflector.underscore(model.class)} instead of using model.class.name.underscore again? Since this is a rails extension to String, I would expect this to work in a helper method. Does it not?
nah, it was just inconsistent... sheesh. Rubyists are so picky :)
Thanks for the hot tip.
Nice -- I think I remember (vaguely!) talking to you about this!
Yeah, you convinced me to post it. So I wrote it up on the plane to Ca, heh.
This is exactly what I was looking for, but I'm having some issues getting it to work. When I try to render validation_errors.rjs, I get this error, like its looking for an rhtml template file:
ActionController::MissingTemplate (Missing template /Users/browell/Development/zipFive/trunk/src/app/views/shared/validation_error.rjs.rhtml):
Any ideas? Thanks!
I ended up doing the code from the validation_errors.rjs file right in my controller, something like:
render(:update) { |page|
page.replace_html "errors_for_#{@model.class.name.underscore}", "" + @model.errors.collect{|k,v| "#{k.humanize} #{v}"}.to_s + ""
page.visual_effect :highlight, "errors_for_#{@model.class.name.underscore}", :startcolor => "'#ff0000'", :endcolor => "'#aca2b6'"
}
The solution originally given looks way more elegant though, but I couldn't get it to work. If someone knows how to get it to work and can let me know, that'd be awesome!
Nice start, although I would implement some of the things in a different way. Neverthelesse, thank you very much for the starting point. Beside many smaller things I changed the helper code a bit:
def error_div_for(obj)
field = case obj
when Symbol
obj.to_s
when String
obj.underscore
when Class
obj.name.underscore
else
obj.class.name.underscore
end
%{}
end