This text discusses how I handled validation messages and other notices caused by AJAX requests in Rails.
Update – 20 april 2007: Wiebe fixed a long-standing bug in the code: non-AJAX flash messages were not being discarded at all, resulting in them remaining in the flash a request too long.
Update – 20 may 2006: Wiebe pointed out to me that the FIXME comment in the application controller snippet was very unclear. This is now fixed. He also wanted to remind the unsuspicious reader that the json gem doesn't necessarily need to go into the vendor
directory; it could also be installed globally, which is, incidentally, exactly what we did before freezing it.
I wanted AJAX requests in Rails to result in error handling and message reporting that was at least as nice (and almost as easy) as doing the same thing without AJAX.
Let me start with a short summary of the steps I followed to get easy, transparent error handling for AJAX requests.
4xx
HTTP error.X-JSON
header.X-JSON
header using a custom AJAX responder.4xx
HTTP errorFirst, I needed to distinguish between actions that fail and actions that succeed. Rails makes this as easy as can be:
{ :action => "update", :id => @comment.id },
:update => { :success => "comment", :failure => "comment_edit" } %>
<%= text_field :comment, :author %>
<%= text_area :comment, :body, :cols => 20, :rows => 40 %>
<%= end_form_tag %>]]>
The above form will update the comment
element on success and the comment_edit
element on failure.
Now, I needed to organize the update
action so that it does the right thing on success and on failure.
'show'
else
flash[:invalid] = @comment.errors.full_messages
render :partial => 'edit', :status => 444
end
end]]>
(I use success
and failure
in the flash to better be able to distinguish between different types of messages when adding pretty icons and colors in the CSS stylesheets.)
The most important part here is rendering the edit
partial with a 444
status code. This 444
code will make the request fail, which is just what I needed to rerender the form with invalid fields marked as such. This marking is done automatically by Rails.
Next, I wanted to override Rails' default behaviour because, by default, ActionController surrounds each invalid field with a <div class="fieldWithErrors">
element. This wouldn't be too bad if it were a span (<div>
s in the wrong places will wreak havoc in many otherwise valid pages), but I rather have the classes added to the actual fields. This was done by adding the following code to environment.rb
. (See ticket #2210 for more information on Rails' faulty default.)
X-JSON
headerIn my application.rb
controller file, I added the following.
The flash_to_json_header
after filter converts the flash to a JSON string which is added to the X-JSON
header. The X-JSON
header has a special meaning to Prototype.
To get the to_json
method which I needed, I had to include the following lines in my environment.rb
(after installing the appropriate gem in the vendor
directory).
X-JSON
headerNow, I had to tell Prototype to actually do something with the JSON header. The following code does that (and a little useless bit more).
Note that as of this writing (2006-03-28), Prototype has a nasty JSON parsing
bug where it doesn't surround the JSON to parse with parentheses. See the trouble ticket to check if my patch has already been accepted. If it hasn't, apply it yourself.
The extractMessages(json)
function is the function which does the actual work. The rest of the code in Messages
is very custom and most probably of little interest to most readers.
In standard.rhtml
layout file, I included the following code.
<%= js_extract_messages_from_flash %>]]>
Note that to avoid complete dependency on JavaScript, I should have put a helper in <ul id="messages">
which simply outputs the messages through Ruby. Instead, because JavaScript independence was no requirement for this Intranet application, I used a helper—js_extract_messages_from_flash
—which is defined as part of the ApplicationHelper
module as follows:
0)
result = ""
flash.discard
result
end
end]]>
On 5 May 2006, John Collins has spoken a few nice words about this article. Thanks for that, John!