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

Directive Handler

handler defines a block of run-time code that should be executed in place of a default handler when processing a HTTP request.

The default handlers are the core of Toast. They implement the action triggered by API calls, which work on ActiveRecord objects and their associations. There are 13 handlers defined, which are documented here.

All of them can be overridden by the handler directive as necessary.

handler may appear under following directives:

The Handler Matrix

Follow the links in the matrix to see how the handler is implemented. Empty cells don’t have a handler, because not all combinations make sense.

  GET PATCH POST DELETE LINK UNLINK
single ->          
collection ->   ->      
singular association ->       -> ->
plural association ->   ->   -> ->
canonical -> ->   ->    

Handlers

GET Single

Fetch a single record using a models class method.

Default Handler:

handler do |uri_params|
  {MODEL}.{NAME}
end

, where

Example:

Model:

class Person < ApplicationRecord
  def self.richest
    order("net_value DESC").first
  end
end

Toast Configuration:

expose Person {
  readables :name, :net_value
  single :richest {
    via_get {
      allow do |*args|
        true
      end
      ## implicit Handler:
      # handler do |uri_params|
      #   Person.richest
      # end
    }
  }
}

Request:

GET /people/richest
-> { :name => "Uncle Scrooge",
     :net_value => 100000000000000000000,
     :self => "https://example.fernwerk.net/people/394" }

End Example

GET Collection

Fetch an Array of records using a models class method (scope)

Default Handler

handler do |uri_params|
  {MODEL}.{NAME}
end

, where

Example

Model:

class Person < ApplicationRecord
  def self.first_letter_a
    where("name LIKE 'a%'")
  end
end

Toast Configuration:

expose Person {
  readables :name
  collection :first_letter_a {
    via_get {
      allow do |*args|
        true
      end
      ## implicit Handler:
      # handler do |uri_params|
      #   Person.first_letter_a
      # end
    }
  }
}

Request:

GET /people/first_letter_a
-> [{ :name => "Abraham", :self => "https://example.fernwerk.net/people/1937" },
    { :name => "Alfons", :self => "https://example.fernwerk.net/people/19378" },
      ... ]

End Example

GET Singular Association

Fetch a single resource/model-instance from a model association

Default Handler

handler do |source, uri_params|
  source.{NAME}
end

, where

Example

Model:

class Person < ApplicationRecord
  belongs_to :group
end
class Group < ApplicationRecord
  has_many :members, class_name: "Person"
end

Toast Configuration:

expose(Person) {
  readables :name
  association :group {
    via_get {
      allow do |*args|
        true
      end
      ## implicit Handler:
      # handler do |source, uri_params|
      #   source.group
      # end
    }
  }
}
expose(Group) {
  readables :name
  association :members {
    via_get {
      allow do |*args|
        true
      end
    }
  }
}

Request:

GET /people/1937/group
-> { :name => "Clients",
     :self => "https://example.fernwerk.net/groups/23",
     :members => "https://example.fernwerk.net/groups/23/members"}

End Example

GET Plural Association

Fetch an array of resources/model-instances from a model associatiion.

Default Handler

handler do |source, uri_params|
  source.{NAME}
end

, where

Example

Model:

class Person < ApplicationRecord
  belongs_to :group
end
class Group < ApplicationRecord
  has_many :members, class_name: "Person"
end

Toast Configuration:

expose(Person) {
  readables :name
  association :group {
    via_get {
      allow do |*args|
        true
      end
    }
  }
}
expose(Group) {
  readables :name
  association :members {
    via_get {
      allow do |*args|
        true
      end
      ## Implicit handler
      # handler do |source, uri_params|
      #   source.members
      # end
    }
  }
}

Request:

GET /groups/23/members
-> [
     { :name => "Alfons",
       :self => "https://example.fernwerk.net/people/2947",
       :group => "https://example.fernwerk.net/people/2947/group" },
     { :name => "Bert",
       :self => "https://example.fernwerk.net/people/293",
       :group => "https://example.fernwerk.net/people/293/group" }
   ]

End Example

GET Canonical URI

Fetch a resource/model-instance by it’s canonical URI, which is a unique ID for it.

Default Handler

handler do |model_instance, uri_params|
  model_instance
end

, where

Example

Model:

class Group < ApplicationRecord
  has_many :members, class_name: "Person"
end

Toast Configuration:

expose(Group) {
  readables :name
  association(:members) {
    via_get {
      allow do |*args|
        true
      end
      ## implicit handler
      # handler do |model_instance, uri_params|
      #   model_instance
      # end
    }
  }

Request:

GET /groups/23
-> { :name => "Clients",
     :self => "https://example.fernwerk.net/groups/23",
     :members => "https://example.fernwerk.net/groups/23/members"}

End Example

PATCH Canonical URI

Update a resource/model-instance by it’s canonical URI.

Default Handler

handler do |model_instance, payload, uri_params|
  model_instance.update payload
end

, where

Example

Model:

class Person < ApplicationRecord
  has_and_belongs_to_many :groups
  def group_names
    groups.map(&:name).join(' ')
  end
end

Toast Configuration:

expose(Person) {
  writables :first_namue, :last_name
  readables :group_names
  via_get {
    allow do |*args|
      true
    end
  }
  via_patch {
    allow do |*args|
      true
    end
    ## implicit handler
    # handler do |model_instance, payload, uri_params|
    #   model_instance.update payload
    # end
  }
}

Request:

GET /people/5419
-> { :firs_name => "John",
     :last_name => "Foe",
     :self => "https://example.fernwerk.net/people/5419",
     :group_names => "Friends ToastCommitters"}

PATCH /people/5419, {"first_name":"John", "last_name":"Doe"}
-> { :firs_name => "John",
     :last_name => "Doe",
     :self => "https://example.fernwerk.net/people/5419",
     :group_names => "Friends ToastCommitters"}

End Example

POST (to) Collection

Create a new resource/model-instance of the collections type by the collections URI.

Note, that POST is only configurable for the all collection (URIs: /{MODEL} or /{MODEL}/all) and not for other collections/scopes of the model class.

Default Handler

handler do |payload, uri_params|
  {MODEL}.create payload
end

, where

Example

Model:

class Person < ApplicationRecord
  def self.first_letter_a
    where("name LIKE 'a%'")
  end
end

Toast Configuration:

expose(Person) {
  writables :first_name, :last_name
  collection(:all) {
    via_post { # allowed in collection(:all) only
      allow do |*args|
        true
      end
      ## implicit handler
      # handler do |payload, uri_params|
      #   Person.create payload
      # end
    }
  }
}

Request:

POST /people, {"first_name": "Henry", "last_name": "Conrad"}
-> { :firs_name => "Henry",
     :last_name => "Conrad",
     :self => "https://example.fernwerk.net/people/875",
     :group_names => ""}

End Example

POST (to) Plural Association

Create a new resource/model-instance via a association. The record is created and linked to the association to which the request is sent.

Default Handler

handler do |source, payload, uri_params|
  source.{NAME}.create payload
end

, where

Example

Model:

class Person < ApplicationRecord
  belongs_to :group
end
class Group < ApplicationRecord
  has_many :members, class_name: "Person"
end

Toast Configuration:

expose(Person) {
  readables :name
  association :group {
    via_get {
      allow do |*args|
        true
      end
    }
  }
}
expose(Group) {
  readables :name
  association :members {
    via_get {
      allow do |*args|
        true
      end
    }
    via_post {
      allow do |*args|
        true
      end
      ## Implicit handler
      # handler do |source, payload, uri_params|
      #   source.members.create payload
      # end
  }
}

Request:

GET /groups/23/members
-> [
     { "name":  "Alfons",
       "self":  "https://example.fernwerk.net/people/2947",
       "group": "https://example.fernwerk.net/people/2947/group" },
     { "name":  "Bert",
       "self":  "https://example.fernwerk.net/people/293",
       "group": "https://example.fernwerk.net/people/293/group" }
   ]

POST /groups/23/members, {"name":"Conrad"}
-> { "name" : "Conrad",
     "self" : "https://example.fernwerk.net/people/20348"
     "group" :   https://example.fernwerk.net/people/20348/group" }

GET /groups/23/members
-> [
     { "name":  "Alfons",
       "self":  "https://example.fernwerk.net/people/2947",
       "group": "https://example.fernwerk.net/people/2947/group" },
     { "name":  "Bert",
       "self":  "https://example.fernwerk.net/people/293",
       "group": "https://example.fernwerk.net/people/293/group" },
     { "name" : "Conrad",
       "self" : "https://example.fernwerk.net/people/20348"
       "group" :   https://example.fernwerk.net/people/20348/group" }
   ]

End Example

DELETE Canonical URI

Delete a resource/model-instance by it’s canonical URI.

Default Handler

handler do |model_instance, uri_params|
  model_instance.destroy
end

, where

Example

Model:

class Person < ApplicationRecord
end

Toast Configuration:

expose(Person) {
  writables :first_namue, :last_name

  via_delete {
    allow do |*args|
      true
    end
    ## implicit handler
    # handler do |model_instance, uri_params|
    #   model_instance.destroy
    # end
  }
}

Request:

DELETE /people/5419
-> "200 OK"

End Example

LINK Singular Association

Associate resources/model-instances via a singular model associations or update such associations.

Default Handler

handler do |source, target, uri_params|
  source.{NAME} = target
  source.save
end

, where

Example

Model:

class Person < ApplicationRecord
  belongs_to :group
end
class Group < ApplicationRecord
  has_many :members, class_name: "Person"
end

Toast Configuration:

expose(Person) {
  readables :name
  association :group {
    via_get {
      allow do |*args|
        true
      end
      via_link {
        allow do |*args|
          true
        end
        ## implicit handler
        # handler do |source, target, uri_params|
        #  source.group = target
        #  source.save
        # end
      }
    }
  }
}
expose(Group) {
  readables :name
  association :members {
    via_get {
      allow do |*args|
        true
      end
    }
  }
}

Request:

GET /people/1937/group
-> "404 Not Found"

LINK /people/1937/group, Link: <https://example.fernwerk.net/groups/23>,rel='related'
-> 200 OK

GET /people/1937/group
-> { "name": "Clients",
     "self": "https://example.fernwerk.net/groups/23",
     "members": "https://example.fernwerk.net/groups/23/members"}

End Example

LINK Plural Association

Associate resources/model-instances via plural model associations or update such associations.

Default Handler

handler do |source, target, uri_params|
  source.{NAME} << target
end

, where

Example

Model:

class Person < ApplicationRecord
  belongs_to :group
end
class Group < ApplicationRecord
  has_many :members, class_name: "Person"
end

Toast Configuration:

expose(Person) {
  readables :name
  association :group {
    via_get {
      allow do |*args|
        true
      end
    }

  }
}
expose(Group) {
  readables :name
  association :members {
    via_get {
      allow do |*args|
        true
      end
    }
    via_link {
      allow do |*args|
        true
      end
      ## implicit handler
      # handler do |source, target, uri_params|
      #  source.members << target
      # end
    }
  }
}

Request:

GET /group/781/members
-> [{"name": "Carlo", "self" : "https://example.fernwerk.net/people/14"}]

LINK /group/781/members, Link: <https://example.fernwerk.net/people/20>,rel='related'
-> 200 OK

GET /group/781/members
-> [{"name": "Carlo", "self": "https://example.fernwerk.net/people/14"},
    {"name": "Anna", "self": "https://example.fernwerk.net/people/20"}]

End Example

UNLINK Singular Association

Remove links between resources/model-instances via singular model associations.

Note that, the target to be unlinked must be passed with the Link header. If it’s not the currently linked one the request has no effect.

Default Handler

handler do |source, target, uri_params|
  if source.{NAME} == target
    source.{NAME} = nil
    source.save
  end
end

, where

Example

Model:

class Person < ApplicationRecord
  belongs_to :group
end
class Group < ApplicationRecord
  has_many :members, class_name: "Person"
end

Toast Configuration:

expose(Person){
  readables :name
  association :group {
    via_get {
      allow do |*args|
        true
      end
      via_unlink {
        allow do |*args|
          true
        end
        ## implicit handler
        # handler do |source, target, uri_params|
        #   if source.group == target
        #     source.group = nil
        #     source.save
        #   end
        # end
      }
    }
  }
}
expose(Group) {
  readables :name
  association :members {
    via_get {
      allow do |*args|
        true
      end
    }
  }
}

Request:

GET /people/1937/group
-> { "name": "Clients",
     "self": "https://example.fernwerk.net/groups/23",
     "members": "https://example.fernwerk.net/groups/23/members"}

UNLINK /people/1937/group, Link: <https://example.fernwerk.net/groups/23>,rel='related'
-> 200 OK

GET /people/1937/group
-> "404 Not Found"

End Example

UNLINK Plural Association

Remove links between resources/model-instances via plural model associations.

Default Handler

handler do |source, target, uri_params|
  source.{NAME}.delete(target)
end

, where

Example

Model:

class Person < ApplicationRecord
  belongs_to :group
end
class Group < ApplicationRecord
  has_many :members, class_name: "Person"3
end

Toast Configuration:

expose(Person) {
  readables :name
  association :group {
    via_get {
      allow do |*args|
        true
      end
    }

  }
}
expose(Group) {
  readables :name
  association :members {
    via_get {
      allow do |*args|
        true
      end
    }
    via_unlink {
      allow do |*args|
        true
      end
      ## implicit handler
      # handler do |source, target, uri_params|
      #  source.members.delete(target)
      # end
    }
  }
}

Request:

GET /group/781/members
-> [{"name": "Carlo", "self": "https://example.fernwerk.net/people/14"},
    {"name": "Anna", "self": "https://example.fernwerk.net/people/20"}]

UNLINK /group/781/members, Link: <https://example.fernwerk.net/people/14>,rel='related'
-> 200 OK

GET /group/781/members
-> [{"name": "Anna", "self" : "https://example.fernwerk.net/people/20"}]

End Example