Welcome to Giant Robots Smashing Into Other Giant Robots — a weblog about development, business, design and technology — written by thoughtbot.
Jester: JavaScriptian REST
Jester is our implementation of REST, in JavaScript. It provides (nearly) identical syntax to ActiveResource for using REST to find, update, and create data, but from the client side.
Update, 6/16/07: We have released version 1.3 of Jester. You may want to view its release description.
Jester is available from SVN in trunk form, or a 1.1 release form. You can also download a zipped copy of 1.1. Jester is released under the MIT License.
Syntax
All examples below are taken from inside the JavaScript console of Firebug, the best JavaScript development tool you could possibly have.
First, declare a model in Jester by calling model on Base:1 2 3 |
>>> Base.model("User") >>> User Object _name=User _singular=user _plural=users |
1 2 3 |
>>> Base.model("Child", "http://www.thoughtbot.com", "child", "children") >>> Child Object _name=Child _singular=child _plural=children |
1 2 3 |
>>> var Child = new Base("Child", "http://www.thoughtbot.com", "child", "children") >>> Child Object _name=Child _singular=child _plural=children |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
>>> eric = User.find(1) GET http://localhost:3000/users/1.xml Object _name=User _singular=user _plural=users >>> eric.attributes ["active", "email", "id", "name"] >>> eric.id 1 >>> eric.name "Eric Mill" >>> eric.active true |
1 2 3 4 5 6 7 8 9 10 |
>>> floyd = User.create({name: "Floyd Wright", email: "tfwright@thoughtbot.com"})
POST http://localhost:3000/users.xml
Object _name=User _singular=user _plural=users
>>> floyd.id
9
>>> User.find(9).name
GET http://localhost:3000/users/9.xml
"Floyd Wright" |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
>>> eric = User.find(1) GET http://localhost:3000/users/1.xml Object _name=User _singular=user _plural=users >>> eric.email "emill@thoughtbot.com" >>> eric.email = "sandybeach@wintermute.com" "sandybeach@wintermute.com" >>> eric.save() POST http://localhost:3000/users/1.xml true >>> User.find(eric.id).email GET http://localhost:3000/users/1.xml "sandybeach@wintermute.com" |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
>>> chad = User.build({email: "cpytel@thoughtbot.com", name: "Chad Pytel"})
Object _name=User _singular=user _plural=users
>>> chad.new_record()
true
>>> chad.save()
POST http://localhost:3000/users.xml
true
>>> chad.id
9
>>> chad.new_record()
false |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
>>> jared = User.build({name: "", email: ""})
Object _name=User _singular=user _plural=users
>>> jared.save()
POST http://localhost:3000/users.xml
false
>>> jared.errors
["Name can't be blank", "Email can't be blank"]
>>> jared.valid()
false
>>> jared.name = "Jared Carroll"
"Jared Carroll"
>>> jared.email = "emill@thoughtbot.com"
"emill@thoughtbot.com"
>>> jared.save()
POST http://localhost:3000/users.xml
false
>>> jared.errors
["Email has already been taken"]
>>> jared.email = "jcarroll@thoughtbot.com"
"jcarroll@thoughtbot.com"
>>> jared.save()
POST http://localhost:3000/users.xml
true |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
>>> eric = User.find(1) GET http://localhost:3000/users/1.xml Object _name=User _singular=user _plural=users >>> eric.posts [Object _name=Post _singular=post _plural=posts, Object _name=Post _singular=post _plural=posts] >>> eric.posts.first().body "Today I passed the bar exam. Tomorrow, I make Nancy my wife." >>> eric.posts.first().body = "Today I *almost* passed the bar exam. The ring waits one more day." "Today I *almost* passed the bar exam. The ring waits one more day." >>> eric.posts.first().save() POST http://localhost:3000/posts/1.xml true >>> post = Post.find(1) GET http://localhost:3000/posts/1.xml Object _name=Post _singular=post _plural=posts >>> post.body "Today I *almost* passed the bar exam. The ring waits one more day." >>> post.user Object _name=User _singular=user _plural=users >>> post.user.name "Eric Mill" |
Using Jester
Jester depends on two libraries: Prototype, which comes with Rails and most people are familiar with, and ObjTree, a nice DOM parsing engine for JavaScript. Both of these are packaged along with Jester in its SVN repository, so you don’t have to hunt for them yourself. Just make sure you’re including all three in your test file.
<script type="text/javascript" src="/javascripts/prototype.js"></script>
<script type="text/javascript" src="/javascripts/ObjTree.js"></script>
<script type="text/javascript" src="/javascripts/jester.js"></script>
JavaScript in the browser is limited to requests with in only the same domain as the script is running in, so without iframe hackery, Jester is probably only useful for writing client code in your own apps, to talk to itself. We’re investigating whether Jester can use this hackery to make cross-domain requests, but it’s not clear if this will be feasible.
There are also some basic unit tests included inside Jester’s repository, which run using JsUnit. To run them yourself, from Jester’s repository open the file test/jsunit/testRunner.html in your browser, and choose test/jester_test.html as the test file.
The Server Side
These examples are talking with a Rails application whose controllers were generated with ”./script generate scaffold_resource”—in other words, the ideal RESTful controllers. It’s very easy to make your controller RESTful. Here’s the source for the User controller I’m using. The lines that deal with returning HTML have been removed, and I have added “(:include => :posts)” as an argument to to_xml in two places, so associations are included (it’s that easy!).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
class UsersController < ApplicationController # GET /users.xml def index @users = User.find(:all) respond_to do |format| format.xml { render :xml => @users.to_xml(:include => :posts) } end end # GET /users/1.xml def show @user = User.find(params[:id]) respond_to do |format| format.xml { render :xml => @user.to_xml(:include => :posts) } end end # POST /users.xml def create @user = User.new(params[:user]) respond_to do |format| if @user.save format.xml { head :created, :location => user_url(@user) } else format.xml { render :xml => @user.errors.to_xml } end end end # PUT /users/1.xml def update @user = User.find(params[:id]) respond_to do |format| if @user.update_attributes(params[:user]) format.xml { head :ok } else format.xml { render :xml => @user.errors.to_xml } end end end # DELETE /users/1.xml def destroy @user = User.find(params[:id]) @user.destroy respond_to do |format| format.xml { head :ok } end end end |
<user>
<active type="boolean">true</active>
<email>cpytel@thoughtbot.com</email>
<id type="integer">2</id>
<name>Chad Pytel</name>
<posts>
<post>
<title>Life as a Jester</title>
<body>It's not as hard as Master said it would be. Today I made 200 dollars.</body>
<created-at type="datetime">2007-04-01T04:01:56-04:00</created-at>
<id type="integer">2</id>
<user-id type="integer">2</user-id>
</post>
</posts>
</user>
To see some real live examples, the Beast forum is currently implenting some of ActiveResource. Here’s technoweenie’s User account, in XML, and an XML list of selected Users. Pretty much any URL in Beast can have ”.xml” appended to it.
ActiveResource Reference
Taking ARes Out for a Test Drive—Great introduction to ActiveResource, by one of its authors.
ActiveResource and Testing—A post I made here discussing how I tested ActiveResource models.
ActiveResource’s Subversion Repository—Current ActiveResource trunk, at svn.rubyonrails.org.
Thanks
Thanks go to Chad for the original idea, and Jared for writing Jester’s tests.
Jester is new, so we’d love to hear feedback on its strengths and weaknesses. We’re using it ourselves, so it’s under active development and getting plenty of love and attention. Please tell us what you think!
About this entry
You're reading an entry on GIANT ROBOTS SMASHING INTO OTHER GIANT ROBOTS, the company weblog of thoughtbot, inc.
- Author:
- Eric Mill
- Published:
- April 2nd 08:24 AM
- Updated:
- June 19th 02:02 PM
- Sections:
- Development Technology
thoughtbot is hiring
We are hiring web developers and web designers in both Boston and New York, NY.
What are we up to?
We built Shoulda, an eclectic set of additions to Test::Unit; Paperclip to manage uploaded files without hassle; Jester, a REST/ActiveResource client library written in Javascript, and Squirrel, an enhancement for ActiveRecord's find syntax; — amongst some other projects.

Chad (President) and Jon (CTO) co-authored a technical book titled Pro Active Record: Databases with Ruby and Rails, which explores the ins and outs of the ActiveRecord ruby library. You can buy it today at Amazon.com.
About thoughtbot, inc.
We are a small web application development consulting business, with offices in Boston, MA and New York, NY. If you're looking to find a team for your next web development project or your new web application — get in touch.
28 comments
Jump to comment form