Standard JSON Response for Rails and jQuery Jun 22

Update: I’ve moved this into a gem: js_message. It takes care of adding the new Rails mime type (the gem uses .jsm instead of .jsonr), the jQuery extension, and the controller method.

After programming with jQuery to do some unobtrusive javascript, a few coworkers and I came to a conclusion that I’d like to share. When dealing with ajax form submissions, there were a few common behaviors that I would deal with when the server response came back. These were:

  • On form submission, the server sends back a success response with a message, an html snippet, or both. The ajax callbacks then manipulate and inject the HTML back into the page.
  • On form submission, the server sends back a success response and a URL. The ajax callbacks then redirect the browser to said URL.
  • On form submission, the server sends back an error response with a message, an HTML snippet, or both. The ajax callbacks then manipulate and inject the HTML back into the page.

Introducing a Standardized JSON Response

Since these cases were happening quite often, I refactored the Rails application and the javascript ajax calls to handle them. I turned the above behavior into JSON object with a standard structure. The structure looks like the following:

{
  status: "ok|redirect|error",
  to: "http://www.redirect-url.com",
  html: "<b>Insert html</b>",
  message: "Insert some message here"
}

Returning this structure on ajax requests allowed me to simplify the ajax callbacks and it kept the javascript callback code manageable. Before we get to the simplification, let’s break down the different properties of this response object.

status:

status represents the success/error response from the server. Note, it’s not the HTTP status, but the application request status. Say you have a blog, and you’re on the article creation page. When you submit the form via ajax, it should return either a successful response or a failure response. How do you know what the response is? You could look at the HTTP response code, and if it was 200+, it was a successful response, and if it’s any other, it’s an error response. This would work, but it didn’t take care of the redirect behavior that I described above. So that’s where status came in. It is always returned in the response and will be one of the three values:

  • ok – successful response
  • redirect – successful response that should redirect the user
  • error – failure response

to:

to is set to a URL when the status is set to redirect.

html:

html can be any type of HTML that the server deems necessary to accompany a successful or failed request. For instance, let’s say you have a blog with a comment form to allow users to comment on your blog posts. When the user types in a comment hits submit, an ajax call is made to your server, and the rendered version of the comment is sent back in the html property if the comment was saved successfully. Your ajax callback can then take that HTML and inject it into the listing of comments on the page. This is the exact behavior of the comment forms on this site.

message:

message is any type of message the server deems necessary to accompany a successful or failed request. So going along with the above commenting system, say you wanted the comment form to ensure the user left a name. If the user did not leave a name, the response from the server can have a message saying that the user needs to enter their name before they can comment. message is used for this purpose. You can also think of the message property as anything you would put into the Rails flash object.

Rails

Now, let’s talk about the Rails side of the equation. Since I’m returning a json object, I would normally just tack on some respond_to code with the :json format, but this is a standardize response object, and I don’t want to confuse any other code I’ve written that returns JSON. Instead, I aliased the JSON format, and called it :jsonr (not the prettiest name, but if you have any other ideas, you’re welcome to leave a comment :). I added the following to the config/initializers/mime_types.rb:

# config/initializers/mime_types.rb
Mime::Type.register_alias "application/json", :jsonr, %w( text/x-json )

Now, in my controllers, I can write the following code:

Successful response

respond_to do |format|
  # ... other formats here ...

  format.jsonr do
    render :json => { 
      :status => :ok, 
      :message => "Success!",
      :html => "...insert html..."
    }.to_json
  end        
end

Successful response with a redirect

respond_to do |format|
  # ... other formats here ...
  
  format.jsonr do
    render :json => { 
      :status => :redirect, 
      :to => "http://www.google.com"
    }.to_json
  end        
end

Failure response

respond_to do |format|
  # ... other formats here ...

  format.jsonr do
    # NOTE: I send back a 400 status code
    render :status => 400, :json => { 
      :status => :error, 
      :message => "Error!",
      :html => "... insert html ..."
    }.to_json
  end        
end

Obviously, writing the above snippets of code over and over again in controllers is not very DRY, so I refactored the above into:

# Add this inside app/controllers/application_controller.rb
def render_json_response(type, hash)
  unless [ :ok, :redirect, :error ].include?(type)
    raise "Invalid json response type: #{type}"
  end

  # To keep the structure consistent, we'll build the json 
  # structure with the default properties.
  #
  # This will also help other developers understand what 
  # is returned by the server by looking at this method.
  default_json_structure = { 
    :status => type, 
    :html => nil, 
    :message => nil, 
    :to => nil }.merge(hash)

  render_options = {:json => default_json_structure}  
  render_options[:status] = 400 if type == :error

  render(render_options)
end

Now I can replace the WET snippets of code with the following DRY snippets:

# Success scenario with html
format.jsonr do
  render_json_response :ok, :html => "<b>Hello, world!</b>", :message => "Ajax response succeeded!"
end

# Success with a redirect
format.jsonr do
  render_json_response :redirect, :to => "http://www.paydrotalks.com"
end

# Failure scenario
format.jsonr do
  render_json_response :error, :message => "Oops!"
end

Javascript

Now, for these jsonr requests, the server response comes in two flavors. The first is when the server processed the request successfully. It will respond with a simple 200 HTTP status and the standardized jsonr object. This jsonr object will include either the status = "ok" or the status = "redirect". The second flavor is when the server processed the request unsuccessfully (the failure state). The server, in this case, will respond with a 400 HTTP status code and the jsonr object.

The following solution is built on top of the jQuery framework, but it should be pretty straightforward in any other javascript framework.

It’s fairly simple for jQuery, in that I just need to provide the success and the error callbacks in the $.ajax call.

For the $.ajax success callback, it looks like this:

$.ajax({
   // ... other options ...

  success: function(jsonResponse){
    if(jsonResponse.status == "redirect"){
      window.location = jsonResponse.to;
    }
    else {
      // ... handle the success scenario here ...
    }
  }
});

For the $.ajax error callback, it looks like this:

$.ajax({
  // ... other options ...

  error: function(XMLHttpRequest, textStatus){
    if(XMLHttpRequest.status == 400){
      jsonResponse = eval("(" + XMLHttpRequest.responseText + ")");
      // ... handle error case here with jsonResponse ...
    }
  }
});

The thing is, writing these functions over and over again becomes tedious, and is not DRY. So I refactored these into something nicer. The only things that change often are the success and error functions. In the error case, I don’t want to deal with the XMLHttpRequest object, but just with the returned jsonr object. The following code is how I’d like to make requests:

$.jsonRequest({
  // ... your $.ajax options here ...
  
  success: function(jsonResponse){ /* code */ },
  error: function(jsonResponse){ /* code */ }
});

Notice how the error callback does not deal with the XMLHttpRequest object, but with the jsonr object instead. To implement the above, here is what I’ve come up with:

(function($){
  $.jsonRequest = function(options){

    // Store the success function in the temp var validResponseCB. 
    // If the user did not specify a success function, we'll default it to 
    // log the jsonr object.
    var validResponseCB = options.success || function(jsonResponse){
      console.log("Valid response returned");
      console.log(jsonResponse);
    };
    
    // Store the error function in the temp var invalidResponseCB.
    // If the user did not specify an error function, we'll default it to 
    // log the jsonr object.
    var invalidResponseCB = options.error || function(jsonResponse){
      console.log("Invalid response returned");
      conosle.log(jsonResponse);
    };
    
    // Provide a more common success function for the $.ajax callback.
    // Also, take care of the redirect scenario, since that will be common
    // in all jsonr responses (if the server returns a status = redirect).
    options.success = function(jsonResponse){
      if(jsonResponse.status == "redirect"){
        window.location = jsonResponse.to;
      }
      else{
        validResponseCB(jsonResponse);
      }
    };
    
    // Provide a common error function for $.ajax.
    // When the error's status is 400, evaluate the responseText
    // and pass that into the invalidResponseCB function. 
    options.error = function(XMLHttpRequest, textStatus){
      if(XMLHttpRequest.status == 400){
        jsonResponse = window.eval(XMLHttpRequest.responseText);
        invalidResponseCB(jsonResponse);
      }
    };
    

    // The real action takes place here.
    $.ajax(options);
  };
})(jQuery);

With the above function, it’s now easy to make jsonr requests. It behaves similarly to jQuery’s $.ajax function, except that both the success and error callbacks take a jsonr object as an argument.

Here’s a simple example that just calls the javascript alert function with the jsonr object’s message property. This assumes that the server sets the message property of the object.

$.jsonRequest({ 
  
  // Note: You can choose to use the .jsonr extension
  // or add "format=jsonr" to the data. I've chosen to use the
  // extension.
  url: "http://www.example.com/comments.jsonr"  
  type: "POST",
  data: /* gather form data here - probably something like $("form").serialize() */,
  
  success: function(jsonResponse){
    alert("Success: " + jsonResponse.message);
  },
  
  error: function(jsonResponse){
    alert("Request Error: " + jsonResponse.message);
  }
});

There are more modifications and refactorings that can happen to this code, but hopefully it gets the idea across. One incarnation of the above code was written by my coworker, in which the response object also has the ability to specify certain javascript libraries to be reloaded after the request.

Anyways, I’ve created a zip file for both the ruby and javascript code which can be downloaded (jsonr.zip). Hope this helps! If you have any questions feel free to leave me a comment or email me.