jQuery, Rails, and AJAX

Tuesday, February 24, 2009

On a recent Rails based project, cubeless, I was cleaning up some legacy code with the help of Burin. The project is large and has been through several Rails upgrades but wasn’t fully taking advantage of some of “newer” Rails best practices.

The controllers were heavy with lots of inline RJS and actions that catered only to a particular JavaScript function. We set out to keep the JavaScript isolated to the public/javascripts folder and to use REST in conjunction with the respond_to method. Among the many advantages, this made it easier for us to support those users who do not have JavaScript, for whatever reason.

There are a few tweaks we made to allow jQuery’s AJAX methods to play nice with Rails.

The Authenticity Token

First, we always needed to pass along the authenticity_token for requests using POST. We added a global JavaScript variable named AUTH_TOKEN that stored the authenticity_token.

<%= javascript_tag "var AUTH_TOKEN = #{form_authenticity_token.inspect};" if protect_against_forgery? -%>

Using the ajaxSend method we can make sure that the authenticity_token is always passed for requests using POST like this.

// Always send the authenticity_token with ajax
$(document).ajaxSend(function(event, request, settings) {
    if ( settings.type == 'post' ) {
        settings.data = (settings.data ? settings.data + "&" : "")
            + "authenticity_token=" + encodeURIComponent( AUTH_TOKEN );
    }
});

HTML, not RJS

Next, we wanted Rails to use the js block of the respond_to but actually wanted back a block of HTML specific to what the JavaScript needed. This allows us to keep our JavaScript in the public/javascripts and allow the html block of the respond_to to properly handle HTML requests when JavaScript is not available, for whatever reason.

In a jQuery AJAX call we’d say the following.

$.ajax({
    url: '/post/1/edit', type: 'get', dataType: 'html'
});

Notice we use a dataType of ‘html’ instead of ‘script’. Normally this would land us in the ‘html’ block of respond_to but with the following JavaScript we will instead land in the ‘js’ block of respond_to.

// When I say html I really mean script for rails
$.ajaxSettings.accepts.html = $.ajaxSettings.accepts.script;

This tells jQuery to set the Accepts header to use “text/javascript, application/javascript” so that Rails will think we are requesting RJS.

In Rails we have to be a little more explicit about what we are rendering back to the client by providing the extension. Otherwise it will be looking for an RJS template.

respond_to do |format|
    format.js { render(:partial => 'some_dialog.rhtml', :layout => 'layouts/_dialog.rhtml') }
end

POSTs with no Data

There were some existing POST requests that didn’t actually send any data in this application. If no data is passed then jQuery does not set the Content-Type to “application/x-www-form-urlencoded” as it normally would. This causes some issues for Rails. We added one line to the ajaxSend method we wrote earlier to always make sure that the Content-Type is “application/x-www-form-urlencoded” on POST requests. The updated ajaxSend looks like this.

// Always send the authenticity_token with ajax
$(document).ajaxSend(function(event, request, settings) {
    if ( settings.type == 'post' ) {
        settings.data = (settings.data ? settings.data + "&" : "")
                + "authenticity_token=" + encodeURIComponent( AUTH_TOKEN );
        request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    }
});

Complete Code Example

Here is the complete code example from this article.

// Always send the authenticity_token with ajax
$(document).ajaxSend(function(event, request, settings) {
    if ( settings.type == 'post' ) {
        settings.data = (settings.data ? settings.data + "&" : "")
                + "authenticity_token=" + encodeURIComponent( AUTH_TOKEN );
        request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    }
});

// When I say html I really mean script for rails
$.ajaxSettings.accepts.html = $.ajaxSettings.accepts.script;

I’d like to know what tips and/or tricks you’ve done to enhance the experience of using jQuery with Ruby on Rails.

Posted in jQuery, Ruby on Rails with 5 comments

Comments

I like your solution to the forms authenticity token. It’s cleaner than some of the hacks I have resorted to.

By Jim Barnett on Tuesday, February 24, 2009 at 07:28 PM

Word of caution!

Good call on checking for the type of request on settings.type. We had a very similar piece of code on our application.js to add the authenticity token, but we were doing it both for gets and posts. Modifying the settings.data on GETS causes IE7 to always send POSTS (at least on jQuery 1.2.6), which broke some of our existing functionality

Good post!

By Jorge Sancha on Thursday, April 23, 2009 at 05:22 PM

Nice site! I just subscribed to your RSS feed.

You’ll want to be careful with sending posts with no data. There’s a longstanding bug in Firefox where it does not send the proper content length.

I opened a ticket on this a few months ago and supplied a really simple fix, but the bug ticket hasn’t seen any action since.

Basically, it’s the combination of Firefox + Squid + jQuery with a POST of no data that triggers the issue. It’s more common than thought.

Here’s the bug report (with the fix): http://dev.jquery.com/ticket/4044

By Garrett LeSage on Friday, May  8, 2009 at 03:29 PM

Good and excellent tutorial. thanks.

By joonee on Wednesday, June  3, 2009 at 02:27 AM

tx for this nice system

just a simple change

$(document).ajaxSend(function(event, request, settings) {
    if ( settings.type.**toLowerCase()** == 'post' ) {
        settings.data = (settings.data ? settings.data + "&" : "")
                + "authenticity_token=" + encodeURIComponent( AUTH_TOKEN );
        request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
    }
});

By Guillaume on Friday, April 15, 2011 at 09:33 AM

New Comment