RackJSON
Simple Rack middleware for persisting JSON documents.
Installation
gem install rackjson
RackJSON uses MongoDB for persistence so you also need to install MongoDB.
Usage
In your rackup file:
require mongo
require rackjson
expose_resource :collections => [:notes], :db => Mongo::Connection.new.db("mydb")
run lambda { |env|
[404, {'Content-Type' => 'text/plain'}, ["Not Found"]]
}
This will set up a RESTful resource called 'notes' at /notes which will store any JSON document.
Options
Currently you can choose which HTTP verbs the resource will support using the :only
and :except
options. For example this will expose the notes resource to only get methods:
expose_resource :collections => [:notes], :db => Mongo::Connection.new.db("mydb"), :only => [:get]
And to allow every kind of method except deletes:
expose_resource :collections => [:notes], :db => Mongo::Connection.new.db("mydb"), :except => [:delete]
Restricting Access
There are three ways of mounting a restful resource with rackjson:
expose_resource :collections => [:notes], :db => Mongo::Connection.new.db("mydb")
Using expose_resource
won't restrict access to the resource at all, all requests by anyone will succeed.
public_resource :collections => [:notes], :filters => [:user_id], :db => Mongo::Connection.new.db("mydb")
Using public_resource
will allow everyone to perform GET requests against the resource, however to make POST, PUT or DELETE requests the requester must have a specific session param, and will only be able to make requests to documents which match this session param. In the above example this session param is set to user_id and could be set when logging in.
private_resource :collections => [:notes], :filters => [:user_id], :db => Mongo::Connection.new.db("mydb")
private_resource
is very similar except that all requests, including GET requests must also satisfy the pre-condition of having a specific session param.
When creating resources with either the public or private resources the specified session param will be included in the document automatically.
REST API
Collections
To see what actions are available on the notes resource:
curl -i -XOPTIONS http://localhost:9292/notes
HTTP/1.1 200 OK
Connection: close
Date: Sun, 11 Apr 2010 11:09:40 GMT
Content-Type: text/plain
Content-Length: 0
Allow: GET, POST
Listing the existing notes:
curl -i http://localhost:9292/notes
HTTP/1.1 200 OK
Connection: close
Date: Sun, 11 Apr 2010 11:12:04 GMT
Content-Type: application/json
Content-Length: 2
[]
Currently there are no notes, create one with a post request:
curl -i -XPOST -d'{"title":"hello world!"}' http://localhost:9292/notes
HTTP/1.1 201 Created
Connection: close
Date: Sun, 11 Apr 2010 11:14:17 GMT
Content-Type: application/json
Content-Length: 149
{"updated_at":"Sun Apr 11 12:14:17 +0100 2010","title":"hello world!","_id":"4bc1af0934701204fd000001","created_at":"Sun Apr 11 12:14:17 +0100 2010"}
RackJSON will assign an id to this resource as _id. This can be used to access this resource directly
curl -i http://localhost:9292/notes/4bc1af0934701204fd000001
HTTP/1.1 200 OK
Connection: close
Date: Sun, 11 Apr 2010 11:16:30 GMT
Content-Type: application/json
Content-Length: 147
{"updated_at":"Sun Apr 11 11:14:17 UTC 2010","title":"hello world!","_id":"4bc1af0934701204fd000001","created_at":"Sun Apr 11 11:14:17 UTC 2010"}
This resource will also appear in the index of notes resources
curl -i http://localhost:9292/notes
HTTP/1.1 200 OK
Connection: close
Date: Sun, 11 Apr 2010 11:17:27 GMT
Content-Type: application/json
Content-Length: 147
[{"updated_at":"Sun Apr 11 11:14:17 UTC 2010","title":"hello world!","_id":"4bc1af0934701204fd000001","created_at":"Sun Apr 11 11:14:17 UTC 2010"}]
Update a resource using a PUT request
curl -i -XPUT -d'{"title":"updated"}' http://localhost:9292/notes/4bc1af0934701204fd000001
HTTP/1.1 200 OK
Connection: close
Date: Sun, 11 Apr 2010 11:25:04 GMT
Content-Type: application/json
Content-Length: 144
{"updated_at":"Sun Apr 11 12:25:04 +0100 2010","title":"updated","_id":"4bc1af0934701204fd000001","created_at":"Sun Apr 11 12:25:04 +0100 2010"}
A PUT request can also be used to create a resource at the given location:
curl -i -XPUT -d'{"foo":"bar"}' http://localhost:9292/notes/1
HTTP/1.1 200 OK
Connection: close
Date: Sun, 11 Apr 2010 11:27:13 GMT
Content-Type: application/json
Content-Length: 113
{"updated_at":"Sun Apr 11 12:27:13 +0100 2010","_id":1,"foo":"bar","created_at":"Sun Apr 11 12:27:13 +0100 2010"}
Finally a resource can be deleted using a DELETE request
curl -i -XDELETE http://localhost:9292/notes/1
HTTP/1.1 200 OK
Connection: close
Date: Sun, 11 Apr 2010 11:29:14 GMT
Content-Type: application/json
Content-Length: 12
{"ok": "true"}
Nested Documents
Rack::JSON fully supports nested documents. Any element within a document can be accessed directly regardless of how deeply it is nested. For example if the following document exists at the location /notes/1
{
"_id": 1,
"title": "Nested Document",
"author": {
"name": "Bob",
"contacts": {
"email": "[email protected]"
}
},
"viewed_by": [1, 5, 12, 87],
"comments": [{
"user_id": 1
"text": "awesome!"
}]
}
To get just all the comments we can make a get request to /notes/1/comments
curl -i http://localhost:9292/notes/1/comments
HTTP/1.1 200 OK
Connection: close
Date: Sun, 29 Aug 2010 19:43:09 GMT
Content-Type: application/json
Content-Length: 33
[{"text":"awesome!","user_id":1}]
We can also get just the first comment by passing in the index of that comment in the array, to get the first comment make a GET request to /notes/1/comments/0
curl -i http://localhost:9292/notes/1/comments/0
HTTP/1.1 200 OK
Connection: close
Date: Sun, 29 Aug 2010 19:45:28 GMT
Content-Type: application/json
Content-Length: 31
{"text":"awesome!","user_id":1}
If we try and get a comment that doesn't exist in the array a 404 is returned.
curl -i http://localhost:9292/notes/1/comments/1
HTTP/1.1 404 Not Found
Connection: close
Date: Sun, 29 Aug 2010 19:46:46 GMT
Content-Type: text/plain
Content-Length: 15
field not found
Any field within the document is accessable in this way, just append the field name or the index of the item within an array to the url.
As well as providing read access to any field within a document Rack::JSON also allows you to modify or remove any field within a document. To change the value of a field make a PUT request to the fields url and pass the value you want as the body.
Both simple values, numbers and strings, or JSON structures can be set in this way, however if you want to set a field to contain a JSON structure (array or object) you must set the correct content type for the request, application/json.
Array Modifiers
Fields within a document that are arrays also support atomic push and pulls for adding and removing items from an array.
To push a new item onto an array we make a post request to _push
curl -i -XPOST -d'101' http://localhost:9292/notes/1/viewed_by/_push
The above will push the value 101 onto the viewed by array within the note with _id 1.
Similarly an item can be pulled from an array using _pull
curl -i -XPOST -d'101' http://localhost:9292/notes/1/viewed_by/_pull
This will remove the value 101 from the viewed_by array if it already exists. To remove or add more than one item from an array we can use either _pull_all or _push_all passing in an array each time.
Arrays within documents can also be treated like sets and only add items that do not currently exists by using the add_to_set command like below
curl -i -XPOST -d'101' http://localhost:9292/notes/1/viewed_by/_add_to_set
This will only add the value 101 to the viewed_by array if it doesn't already exist.
Incrementing & Decrementing
RackJSON provides a simple means of incrementing and decrementing counters within a document, simply make a post request to either _increment or _decrement as shown below
curl -i -XPOST http://localhost:9292/notes/1/views/_increment
curl -i -XPOST http://localhost:9292/notes/1/views/_decrement
JSON Query
RackJSON supports querying of the resources using JSONQuery style syntax. Pass the JSONQuery as query string parameters when making a get request.
curl http://localhost:9292/notes?[?title=foo]
This will get all resources which have a title attribute = 'foo'. Greater than and less than are also supported:
curl http://localhost:9292/notes?[?rating>5][?position=<10]
The resources can be ordered using the query syntax also, to get the notes ordered by rating:
curl http://localhost:9292/notes?[\rating]
You can limit the number of resources that are returned, to get the first 10 resources:
curl http://localhost:9292/notes?[0:10]
To only select certain properties of a document you can specify the fields you want, to get just the titles
curl http://localhost:9292/notes?[=title]