вторник, мая 30, 2006

Acts As Authenticated and Ajax

I've been using AAA-based authentication system and got problems with session expiry. When session is expired action from protected page should redirect to login page. But this doesn't work right if the action is called by Ajax.

The solution is to do the redirect with RJS if it is Ajax request.

Though I don't plan to handle redirects in Ajax request other then to do them, I've changed ActionController::Base.redirect_to to produce RJS redirect for Ajax requests:


def redirect_to(options = {}, *params)
if request.xhr?
render :update do |page|
page.redirect_to url_for(options, *params)
end
else
super
end
end

RJS update and redirects

Recently I've ran into problem with processing redirects with Ajax.
First solution that I've found was using custom status code handler in link_to_remote (and some other functions), like this:


<% link_to_remote "Foo",
:url => { :action => "foo" },
302 => "document.location = request.getResponseHeader('location')" %>

But I've got problems with it:
1. It didn't work for me
2. I find it rather harassing to add 302 => .... to every link_to_remote function (which sometimes are hidden deep inside helper methods)

Second attempt to solve this problem was using RJS redirect_to:


render :update do |page|
page.redirect_to '/some/url'
end

This worked fine but had some unwanted side-effects: when redirect is rendered it appeared on a page inside updated element while browser was preparing to do the redirect.
After some investigation I've found out that the javascript was sent with content-type of 'text/javascript' and plain text (without any <script> tag surrounding). That's why after Prototype's Ajax.Updater inserted it into update element it showed up. Then I dug some Prototype's sources and found out that it can strip <script> tags and evaluates everything inside it (if needed). So, my solution was to alter ActionController::Base.render_javascript
to surround javascript code with <script> tags if it was Ajax request.

Here is the code:


module ActionController
class Base
def render_javascript(javascript, status = nil) #:nodoc:
if @request.xhr?
render_text("<script type="'text/javascript'">#{javascript}</script>", status)
else
@response.headers['Content-Type'] = 'text/javascript; charset=UTF-8'
render_text(javascript, status)
end
end
end
end




I found out that content-type recongnition is implemented in Prototype v1.5something... So there is no need to patch render_javascript anymore...

Just be sure to run "rake rails:update:javascripts"