Rails, REST, and .js (TL;DR: We're doing it wrong)
May 23, 2011
In my last blog article I talked about Returning HTML content from AJAX requests. However, I’ve been struggling with a question since then:
I asserted (implicitly) that the following (and canonical!) pattern for doing AJAX replacement is wrong. I felt this strongly, but why is it wrong?
First, the current pattern
Normal Rails pattern for HTML replacement is to make an AJAX call to the .js format, in which we return something like the following back to the browser:
<%# show.js.erb %> var str = '<div id="<%= dom_id my_object -%>" > Name : <%= my_object.first_name -%> </div>'; $("#destination_div").replace(str);
To this I say Ugh.
Why Ugh? Why is it wrong to return this in the .js format?
Why? The DOM transform operation above isn’t the representation of the object
Let’s review what Rails actions do in the respond_to
block
- format.html returns a HTML representation of the object (for display on screen) — aka: it renders the view
- format.json returns a JSON representation of the object
- format.xml returns an XML representation of the object
These are all pretty normal in the Rails world. The last two take the object and serialize it (using .to_json
or render :xml => object
. Straight from rails generate scaffold
You might imagine (from DHH’s RailsConf keynote introducing Rails 2) that other formats existed, with mostly the same result:
- format.icl might return the iCal representation of the object
- format.csv might return a CSV representation of the object
But in the canonical Rails show.js.erb
file at the beginning of this blog post, the .js is not returning the Javascript representation of the object.
Can I do this with your object?
$.ajax( ... success: function (data, textStatus, xhr) { var obj = eval(data); alert(obj.first_name); /* attribute, like JSON */ alert(obj.api_function() ); /* and here's a function call! */
Answer: No, because your .js “representation” is some DOM injection of an unparsable HTML string.
A pattern for returning an actual Javascript Object
If you return an actual Javascript representation of the object, you have the same data attributes available to you on the JS side as you do on the Ruby side.
For example, if your .js format rendered a .js.erb template like this:
Yes, this smacks on the If you’re using to_json, you’re doing it wrong article by the Gomiso guys. I absolutely agree with their core idea of maybe you should be using views to construct your json objects!
Notice here, we are returning a Javascript object, with data and methods that encapsulate functionality … which is exactly what objects are.
I also know that you use a Ruby object in different contexts than you do a Javascript object. In Ruby I want to perform my business logic - computing priority of due dates, in the case of my Todo object here. In Javascript I care about different operations - perhaps mostly related to AJAX and DOM manipulation.
For example, I’ve always struggled with getting my Rails routes into my unobtrusive Javascript. In Ruby (or .erb code) I use routes and path helpers like I should… and on the Javascript side I hardcode the routes. Heaven help me if I ever have to change the routes - I’ve solved only half the problem!
Since I implemented a routes method in my JS object above, the route will always be correct - because I’m using the normal Rails routes helpers to generate it!
In Rails 3.0.x, it’s non-obvious to keep the Javascript representation of an object in show.js.erb
. This location is certainly it’s not where I would look for the source code to the object! Rails 3.1, I believe, will save this day. Imagine a Rails 3.1 app that uses the assert pipeline to store the Javascript representation in the following hierarchy:
app/ assets/ javascripts/ models/ todo.js.erb
Much better!
To Review
- The normal Rails pattern of returning DOM manipulation functions as the Javascript representation of the object is not RESTful
- … and inconsistent, because every other format (.json, .xml) returns the representation of the object
- We should return actual Javascript objects - data and methods to act on that data
So, how do we do AJAX replacement then?
If we can no longer use the .js format to return a DOM manipulation function, how do we return chunks of HTML to the browser (for a standard “AJAX request and replace” type functionality)?
Answer: Request a partial HTML representation of your object
My Returning HTML content from AJAX requests previous article
covers the how very well, but there are some other advantages:
- We’re returning proper representations for proper formats
- The Javascript that originally makes this AJAX call knows more about the structure of the DOM - and where it wants the result - than a
show.js.erb
that lives on the other conceptional side of the app