58

May be such problem is not new, but I didn't find anything similar. I have such jQuery code:

$.ajax({ 
  url : ('/people/'+id), 
  type : 'DELETE', 
  dataType : 'json', 
  success : function(e) {
    table.getItems(currentPage);
  }
});

My Rails controller looks like this:

def destroy
    @person = Person.find(params[:id])
    @person.destroy

    respond_to do |format|
      format.html { redirect_to(people_url) }
      format.json  { render :json => @person, :status => :ok }
    end
end

This is working.

But when I use the following (as generated by standard), the success callback doesn't get called:

def destroy
    @person = Person.find(params[:id])
    @person.destroy

    respond_to do |format|
      format.html { redirect_to(people_url) }
      format.json  { head :ok }
    end
end

Tested under rails 3.0.3, jQuery 1.4.2 and Firefox 3.6.13.
Firebug says, that query is fired and returns 200 OK in both cases, item is deleted in both cases too. But in second case the callback is not called.

Is there a significant difference in REST, and is there a way to utilize jQuery by using the scaffolded controller?

ernd enson
  • 1,764
  • 1
  • 14
  • 29
sandrew
  • 3,109
  • 5
  • 19
  • 29
  • Are both requests returning valid JSON? Can you sniff with Charles or hit them manually with curl and see what comes back? – Kyle Wild Jan 25 '11 at 08:57
  • there is a missing quote in your url, .. just in case. – jAndy Jan 25 '11 at 08:58
  • missing qoute appeared when I prepared code for a question :) – sandrew Jan 25 '11 at 09:01
  • dorkitude, as I know, in second way it returns empty answer at all, just 200 OK header, and this is recommended REST way. first way returns valid JSON, but such answer is not recommended for resource-delete REST request. – sandrew Jan 25 '11 at 09:04
  • There is a good answer in this dupe, http://stackoverflow.com/questions/12407328/rails-head-ok-interpreted-as-ajaxerror – James McMahon Nov 13 '13 at 16:02

3 Answers3

74

I have run across this a few times and the answer is deceptively simple.

You are using dataType : 'json' in your $.ajax call, so jQuery is expecting a JSON response. With head :ok Rails returns a response containing a single space (http://github.com/rails/rails/issues/1742), which is not accepted by jQuery as valid JSON.

So if you really expect to get either an error or a bare 200 OK header, just set dataType : 'html' in your request and it should work (if you don't set dataType, jQuery will try to guess at what the type is based on response headers, etc., and could still guess json, in which case you'd still have this problem). If you actually expect to get JSON back, instead of using head :ok render something valid with JSON (see comments) or just use head :no_content as suggested by @Waseem

mtjhax
  • 1,988
  • 1
  • 18
  • 23
  • 1
    what would you do to return failure in this case? – lulalala Jun 14 '12 at 10:36
  • 3
    A better way would be to use `head :no_content`. See http://stackoverflow.com/a/12751298/100466 and https://github.com/rails/rails/issues/1742 – Waseem Oct 19 '12 at 08:58
  • 1
    I had a similar problem from the lack of data-type in a rails form (simple_form). Thought I would cross reference with this thread http://stackoverflow.com/questions/10839771/jquery-ajaxerror-runs-even-if-the-response-is-ok-200 which is solved by ```<%= simple_form_for @book, :html => {:'data-type' => :json}, :remote => true do |f| %>``` – genkilabs Jan 18 '13 at 17:31
  • 5
    Didn't work for me until I return a hash via :json as so ":json => { 'message' => 'delete completed' }" – Ash Blue Jan 29 '13 at 02:07
  • As with @AshBlue, returning a string as JSON didn't work. I ended up returning nil with a status code: `render json: nil, status: :ok` – Joshua Pinter Oct 24 '13 at 00:01
  • It is also worth mentioning that returning `status: :success` will also cause an error. It has to be `status: :ok`. – jmarceli Dec 11 '14 at 17:44
6

GearHead is correct, except that the jQuery parser is actually smart enough now to treat an empty string response as null and not attempt to parse it.

However if you have calls that will sometimes receive json and sometimes receive a head response, and you do not have access to the server or do not want to change all your head calls, you can do this alternate solution:

The problem is that Rails sends a single space as an empty response when you use head (see here: How to return truly empty body in rails? i.e. content-length 0)

The relevant parts of the jQuery parseJSON function look like this at the time of this writing:

parseJSON: function( data ) {
    if ( typeof data !== "string" || !data ) {
        return null;
    }

    // Make sure leading/trailing whitespace is removed (IE can't handle it)
    data = jQuery.trim( data );

    // Attempt to parse using the native JSON parser first
    if ( window.JSON && window.JSON.parse ) {
        return window.JSON.parse( data );
    }

As you can see, jQuery is testing whether the data is an empty string before trimming it. It then tries to JSON.parse("") which you can see in your console results in an error, triggering the ajax error callback via a catch statement.

There is a simple fix. jQuery allows you to use converters when one data type is requested and a different one is returned. See here for more details: http://api.jquery.com/extending-ajax/

Since the rails head response renders as text, you can simply define a text to json converter that will trim the response prior to attempting to parse it. Just add this snippet:

// deal with rails ' ' empty response
jQuery.ajaxSetup({
  converters: {
    "text json": function (response) {
      jQuery.parseJSON($.trim(response))
    }
  }
})
Community
  • 1
  • 1
Neil Sarkar
  • 6,834
  • 8
  • 32
  • 30
  • FYI, `jQuery.parseJSON` is just a [passthrough to `JSON.parse`](https://github.com/jquery/jquery/blob/master/src/ajax/parseJSON.js), which throws an exception on an empty string ("unexpected end of input"). So this should explicitly check for the empty response (`$.trim(response).length == 0`) and `return null`. – nornagon Jun 04 '14 at 20:48
0

This is sometimes caused by an old version of the jQuery Validate Plugin. If you are using this plugin, this sometimes leads to this issue. There is an update that fixes this, if it applies to your case.

Alternatively, you can probably figure out what's going wrong by setting up an error handler via:

$.ajaxSetup() or $.ajaxError()

This will probably return a parse error. The newer versions of jQuery are notorious for being very strict with regards to JSON parsing.

Fareesh Vijayarangam
  • 5,037
  • 4
  • 23
  • 18
  • no, I don't use any jQuery plugins (except a some plugins I wrote myself, but they have no influence to ajax). And I know, that in described case server returns empty answer, and jquery, probably, can not parse it. And I'd like to know if there is a pretty way to make it work with this _empty_ answer. – sandrew Mar 16 '11 at 15:45
  • I'd assume that anything that fails to parse, including the empty answer will trigger the ajaxError callback, so you can setup a handler for it there. – Fareesh Vijayarangam Mar 18 '11 at 05:08