View on GitHub

Toast Manual

Toast is a Rack application that hooks into Ruby on Rails. It exposes ActiveRecord models as a web service (REST API).

Version 1.0.*

Table of Contents - Directives

Basics

Toast is a Rack application that hooks into Ruby on Rails providing a REST interface for ActiveRecord models. For each model a HTTP interface can be configured. Using Toast’s configuration DSL one declares which of its attributes are exposed, which are readable and/or writable using white lists.

     Web Service Provider                    Web Service Consumer

       -----------------                      -----------------
       |    Toast      |  <--HTTP/REST-->     | AJAX app,     |
       -----------------                      | mobile app,   |
       |         |     \                      | Java app, etc.|
----------- ----------- -----------           -----------------
| Model A | | Model B | | Model C |
----------- ----------- ----------

Toast dispatches any request to the right model class by using naming conventions. That way a model can be exposed on the web by a simple configuration file under config/toast-api/coconut.rb:

expose(Coconut) {
  readables   :color, :weight

  via_get { # for GET /coconut/ID
    allow do |user, coconut, uri_params|
      user.is_admin?
    end
  }

  collection(:all) {
    via_get {  # for GET /coconuts
      allow do |user, coconut, uri_params|
        user.is_admin?
      end
    }
  }
}

This exposes the model Coconut. Toast would respond like this:

GET /coconuts
--> 200 OK
--> [{"self":"https://www.example.com/coconuts/1","color":...,"weight":...},
     {"self":"https://www.example.com/coconuts/2","color":...,"weight":...},
     {"self":"https://www.example.com/coconuts/3","color":...,"weight":...}]

given there are 3 rows in the table coconuts. Note that this request translates to the ActiveRecord call Coconut.all, hence the exposition of the all collection. Each of the URIs in the response will fetch the respective Coconut instances:

GET /coconut/2
--> 200 OK
--> {"self":   "https://www.example.com/coconuts/2",
     "color": "brown",
     "weight": 2.1}

color and weight were declared in the readables list. That means these attributes are exposed via GET requests, but not updatable by PATCH requests. To allow that attributes must be declared writable:

expose(Coconut) {
  readables :color
  writables :weight
}

POST and DELETE operations must be allowed explicitly:

expose(Coconut) {
  readables :color, :weight
  
  via_get { # for GET /coconut/ID
    allow do |user, coconut, uri_params|
      user.is_admin?
    end
  }

  via_delete { # for DELETE /coconut/ID
    allow do |user, coconut, uri_params|
      user.is_admin?
    end
  }

  collection(:all) {
    via_get {  # for GET /coconuts
      allow do |user, coconut, uri_params|
        user.is_admin?
      end
    }

    via_post {  # for POST /coconuts
      allow do |user, coconut, uri_params|
        user.is_admin?
      end
    }
  }
}

The above permits to POST a new record (== Coconut.create(...) and to DELETE single instances (== Coconnut.find(id).destroy):

POST /coconuts
<-- {"color": "yellow",
     "weight": 42.0}
--> 201 Created
--> {"self":   "https://www.example.com/coconuts/4",
     "color": "yellow",
     "weight": 42.0}

DELETE /coconut/3
--> 200 OK

Nonetheless exposing associations will render your entire data model (or parts of it) a complete web-service. Associations will be represented as URIs via which the associated resource(s) can be fetched:

class Coconut < ActiveRecord::Base
  belongs_to :tree
  has_many :consumers
end

together with config/toast-api/coconut.rb:

expose(Coconut) {
  readables :color, :weight

  association(:tree) {
    via_get {
      allow do |*args|
        true
      end
    }
  }

  association(:consumers) {
    via_get {
      allow do |user, coconut, uri_params|
        user.role == 'admin'
      end
    }

    via_post {
      allow do |user, coconut, uri_params|
        user.role == 'admin'
      end
    }
  }
}
GET /coconut/2
--> 200 OK
--> {"self":       "https://www.example.com/coconuts/2",
     "tree":      "https://www.example.com/coconuts/2/tree",
     "consumers": "https://www.example.com/consumers",
     "color":     "brown",
     "weight":    2.1}

Representation

Toast’s JSON representation is very minimal. As you can see above it does not have any qualification of the properties. Data and links and even structured property value are possible. There are other standards like HAL, Siren, Collection+JSON or JSON API on that matter you may also consider.

Toast’s key ideas and distinguishing features are: Simplicity and Opaque URIs.

The JSON representation was guided by the idea that any client(/-programmer) must know the representation of each resource by documentation anyway, so there is no need to nest links in a ‘links’ sub structure like JSON API does.

The only rules for Toast’s JSON representation are:

URIs

Toast treats URIs generally as opaque strings, meaning that it only uses complete URLs and has no concept of “URL templates” for processing. Such templates may only appear in the documentation.

The canonical form of a URI for a model/resource is: https://<HOST>/<PATH>/<RESOURCE>/<ID>?<PARAMS>, where

Association URIs composed like: <CANONICAL>/<ASSOCIATION>?<PARAMS>, where

Root collection URIs are also provided: https://<HOST>/<PATH>/<RESOURCES>?<PARAMS>. The string <RESOURCES> is named after a class method of a model that returns a relation to a collection of instances of the same model.

Association Treatment

Model instances link each other and construct a web of model instances/resources. The web is conveyed by URIs that can be traversed by GET request. All model association that are exposed appear in the JSON response as a URL, nothing else (embedding can be achieved through regular data properties).

Association properties never change when associations change, because they don’t use the canonical URI form.

They can be used to issue a

All these actions are directly mapped to the corresponding ActiveRecord calls on associations.