GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS

Written by thoughtbot

Jester 1.3: Jsonic REST

Whew, it's been a while. The most requested feature for Jester has been JSON support, and that's what this release delivers. Let's get right to it.

Jester is available from SVN in trunk form, or a 1.3 release form. You can also download a zipped copy of 1.3. Jester is released under the MIT License.

Using JSON in Jester is easy. Set the format option when defining your model, and JSON will be the format used for all requests dealing with that model. Requests are made using .json as a URL suffix. Like so:


>>> Base.model("User", {format: "json"})
>>> eric = User.find(1)
GET http://localhost:3000/users/1.json

The controller code for this is simple. I prefer using wants.json, not wants.js, leaving the .js extension available for RJS or whatever you want. This works out of the box, with no need to add a mime types. Here's what I did:


def show
    @user = User.find(params[:id])
    respond_to do |wants|
      wants.xml {render :xml => @user.to_xml(:include => :posts)}
      wants.json {render :text => @user.to_json}
    end
  end

Going to /users/1.json produces the following JSON:

{attributes: {id: "1", bio: "", extra_flag: "0", middle_name: "Rogers", active: "1", created_at: "2007-04-25 19:15:10", email: "yes"}}

Note that there isn't any automatic typecasting going on here. The default XML output from an ActiveRecord::Base object includes attributes describing types, but the JSON output doesn't. So, boolean flags will come back as the strings 1 and 0. At the Jester level, I've made two auto-casting assumptions: the ID will be turned into an integer, and any fields named created_at/created_on/updated_at/updated_on will be turned into a Date.


>>> eric = User.find(1)
GET http://localhost:3000/users/1.json
>>> eric.id
1
>>> eric.middle_name
"Rogers"
>>> eric.active
"1"
>>> eric.created_at
Wed Apr 25 2007 15:15:10 GMT-0400 (Eastern Daylight Time)

As a companion feature, Jester supports passing JSON code through the X-JSON header, passing through the second json parameter to any callback you provide to an asynchronous Jester request. I'll just show you.


>>> var type;
>>> User.find(1, {}, function(eric, json) {type = json.active.type})
GET http://localhost:3000/users/1.json
XMLHttpRequest
>>> type
"boolean"

And on the controller side, inside the show action, I have this line:


    headers["X-JSON"] = "{active: {type: 'boolean'}}"

This allows you to pass extra JSON information along with any data returned from the server. You don't have to have the model's format set to json for this to operate, either—you can pass JSON information alongside an XML response in the same way.

A couple other small things:

  • Added helper methods updateAttributes and setAttributes. Both take a hash of attributes, and set the attributes of the object you're calling the method on. The difference between the two is that updateAttributes() will immediately save the object, where setAttributes() won't. Thanks to Floyd for adding these.
  • It's now safe to use Prototype-style hashes ($H) as arguments to build(), create(), setAttributes(), and updateAttributes().


That's it for this round. The next major target is support for using Jester with remote websites, using iframe trickery. I'm open to adding support for server-side callbacks, but I'm not convinced it's worth creating a server/client syntax standard for this yet, when iframes are a viable solution. If you feel differently, well this is the time to talk about it.