giant robots smashing into other giant robots

Written by thoughtbot

emill

Jester In The News

Jester is seeing a little action nowadays. The Google Code Blog has a post today about Google Gears on Rails, a Rails plugin by Michael Marcus and Rui Ma, that extends Jester to wrap over Google Gears. It effectively lets your controllers read and write directly to your users’ machines, so they can use your app offline. Check out the install and usage instructions for details, and listen to the Google Developer Podcast episode where the authors are interviewed.

Nat Budin also just announced that he used Jester to build an in-place-editor plugin called JIPE. He’s built some pretty cool Rails helpers for in-place editing that are more sophisticated than what Rails offers by default. Check out the README for install/usage instructions, it’s pretty easy.

These two projects are great evidence that Jester makes for a great supporting library, something easily built on and extended. Rails’ REST conventions are powerful stuff, and I hope people keep thinking of more ways to prove that.

Using Jester
Jester on Github

emill

Jester 1.6: Modern REST

I’m sticking this version number flag into the ground to bring attention to some crucial Jester updates. Namely, full compatibility with IE7, Safari, and Prototype 1.6. If you’ve been following Jester over on Github, or its SVN trunk, then you’ve already gotten these, some from as far back ago as January. There’s not a whole lot past those compatibility fixes, but you can see what there is over at the commit list for Jester at Github, which naturally includes the commit history prior to Jester’s migration from SVN.

Download a tarball of 1.6 (via Github), or check out the trunk in Git with:
git clone git://github.com/thoughtbot/jester.git.

Jester, if you don’t remember, is a little JavaScript library we developed here to act as a REST client. We modeled it after ActiveResource and its syntax, released it a little over a year ago this time, and it got a modest amount of attention. People seem to widely appreciate its syntax, and a less wide but consistent core of people have found some use out of it, and continue to contribute patches and bug reports to the project. We made a landing site, and a little walkthrough of its syntax and uses there.

The next major development step would be to detach Jester from Prototype and bridge it to JQuery and Mootools, if there’s community interest. Also, once Firefox 3 comes out and has genuine cross site XMLHttpRequests, that would be interesting to support. Let me know any other features you think Jester would benefit from, and might make it more useful to the JavaScript world at large.

Mailing List
Bug Tracker
Landing Site

jyurek

Moving to Github

We’re moving our plugins, gems, and other open source projects to github. I know, it’s a shock. We’re going to keep the SVN versions of them going for a while, but the “official” repositories for each of these is now what’s hosted on github. So get busy with the hot forking action.

We’ve got:

By-and-large, we’ve resisted the move to git, despite the searing heat it’s been putting off in the Ruby community recently. We’re still a centralized development house, and so our internal development will still be SVN-based. It’s just how we roll. Though I imagine a few of the guys here will start picking up git-svn.

cpytel

Using Jester from inside an XPCOM Component

One of the original reasons we decided to write Jester was for use in a XULRunner Application. This would give us the ability to make a desktop client application (written in XML and Javascript) that would talk to a Rails back end via ActiveResource.

Using prototype (one of the dependencies of Jester) in XULRunner is something that has been documented in other places, and once you apply those workaround, using our familiar prototype and Jester from within a XUL application are straightforward.

However, those of you familiar with XUL applications (is there actually anyone out there who is? I’m not sure) will be familiar with the fact that you can write XPCOM Components in Javascript as well as C++ (and other languages like Python and Java).

However, once you’re inside an XPCOM component you no longer have any access to Jester, prototype, or even a window object – so you’re not going to get very far when you need to talk to your remote service from within an XPCOM object (such as if you’re using the built-in autocomplete textboxes XULRunner provides.

So, in order to do this you’re going to need to ‘fool’ prototype into thinking it has everything it needs. You can probably actually do this with some slick javascript, but to keep things clear I’ve expanded everything onto its own line:


// Useful definitions for easy XPComponent access.
var Cc = Components.classes;
var Ci = Components.interfaces;
var CC = Components.Constructor;
var XP = {
  newInstance: function(contract_id, nsinterface) {
    return Cc[contract_id].createInstance(nsinterface);
  },
  getService: function(contract_id, nsinterface) {
    return Cc[contract_id].getService(nsinterface);
  },
  getConstructor: CC
}

//lookup and grab an actual window object
var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
               .getService(Components.interfaces.nsIWindowMediator);
var window = wm.getMostRecentWindow("");

var navigator = window.navigator;
var document = window.document;
var Element = window.Element;
var Object = window.Object;
var Number = window.Number;
var String = window.String;
var Enumerable = window.Enumerable;
var Array = window.Array;
var Hash = window.Hash;
var HTMLElement = window.HTMLElement;
var Event = window.Event;
var XPathResult = window.XPathResult;
var DOMParser = window.DOMParser;

You can see that we need to get top level variables for window and all the objects that are actually defined on window but that are usually top level inside a browser.

Believe it or not in this context, we don’t even have the XMLHTTPRequest object, so, we’re going to need to define one of those as well.


function XMLHttpRequest()
{
  var request = Components.
                classes["@mozilla.org/xmlextras/xmlhttprequest;1"].
                createInstance();

  // QI the object to nsIDOMEventTarget to set event handlers on it:
  request.QueryInterface(Components.interfaces.nsIDOMEventTarget);

  // QI it to nsIXMLHttpRequest
  request.QueryInterface(Components.interfaces.nsIXMLHttpRequest);
  
  return request;
}

And finally, we can include prototype and Jester (we’re going to need to supply an include method for this as well):


function include(f) 
{
  var log = false;
  if(typeof(Log) != "undefined") log = new Log();
	if(f.indexOf("chrome://") != 0) f = "chrome://appname/content/" + f;
	try
	{
	  Components.classes["@mozilla.org/moz/jssubscript-loader;1"].getService(Components.interfaces.mozIJSSubScriptLoader).loadSubScript(f);
	}
	catch(e)
	{
		if(log) log.error("Error loading " + f + ": line " + e.lineNumber + ". " + e + "\n");
		else dump("Error loading " + f + ": line " + e.lineNumber + ". " + e + "\n");
	}
}
include("chrome://appname/content/lib/prototype.js");
include("chrome://appname/content/lib/jester.js");

Although Jester is probably one of the most useful things you could include in an XPCOM object, just being able to include prototype in there is pretty handy as well.

On the rare chance that you’re doing RoR and XULRunner development, I hope this is helpful for you.

emill

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:

>>> Base.model("User")
>>> User
Object _name=User _singular=user _plural=users

This creates a global variable called User. It assumes that the URL prefix it uses to base its HTTP requests from is the current domain and port, and assumes “user” and “users” as single and plural forms to make these URLs. There’s no “people/person” intelligence here, so make sure to override these defaults if you need to, like so:

>>> Base.model("Child", "http://www.thoughtbot.com", "child", "children")
>>> Child
Object _name=Child _singular=child _plural=children

If you want to capture the model created in a local variable, or simply prefer more traditional JavaScript syntax, you can do:

>>> var Child = new Base("Child", "http://www.thoughtbot.com", "child", "children")
>>> Child
Object _name=Child _singular=child _plural=children

Find will retrieve a particular instance of your model. Attributes are auto-converted to integer or boolean types if that’s what they are on the server side. The “GET” line is not a return value, just Firebug’s report of activity, but relevant to understanding what’s happening.

>>> 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

Create takes a hash of attribute values. After calling create, the model will fetch its new ID from the return headers.

>>> 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"

Updating is as simple as changing one of the properties and calling save

.

>>> 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"

Sadly, there’s one area where Jester’s syntax can’t match ActiveResource’s perfectly. The method “new” has been renamed to build, due to “new” being an illegal method name in JavaScript up to 1.6. Hopefully this can be updated as the browser landscape evolves. Build was chosen because it is similarly used in ActiveRecord to replace “new” on an association array, where “new” cannot be used.

>>> 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

Error validations are supported. If a model fails to save, save returns false, and the model’s errors property is set with an array of the error messages returned.

>>> 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

Lastly, associations are also supported. If the association data is included in the XML, they’ll be loaded into the returned model as Jester models of their own, using the same assumptions on naming and URL prefix described above. They’re full models, so you can edit and save them as you would the parent. Has_many relationships come back as simple arrays, has_one relationships as a property. In this example, User has_many :posts, and Post belongs_to :user.

>>> 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!).


&lt;script type="text/javascript" src="/javascripts/prototype.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript" src="/javascripts/ObjTree.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript" src="/javascripts/jester.js"&gt;&lt;/script&gt;

An example of the XML produced here, of a User with one Post, at /users/2.xml:

<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!