Ananke

Ananke is a DSL that extends the functionality of Sinatra for easy creation of Restful Services and Resources:

#myapp.rb
require 'sinatra/base'
require 'sinatra/ananke'

resource :user do
all do
  [{:id => ]
end
end

Install the gem and run with:

gem install ananke
ruby myapp.rb

All Users http://localhost:4567/user One User http://localhost:4567/user/1

REST Resources

route defines a complete Resource, and constructs Sinatra Routes based on what's available in it's respective Repository. Routes are:

get     '/name/?'   -> Repository::Capitalize(name).all
get     '/name/id'  -> Repository::Capitalize(name).one(id)
post    '/name'     -> Repository::Capitalize(name).new(data)
put     '/name/id'  -> Repository::Capitalize(name).edit(id, data)
delete  '/name/id'  -> Repository::Capitalize(name).delete(id)

Repositories

The Default Repository can be changed:

ananke.default_repository = 'MyRepository'

HyperMedia

Linked

route :user do
id :id
linked :computer
end

module Repository
module User
  def self.one(id)
    [Return User for User Id]
  end
  def self.computer_id_list(id)
    [Return an array of single value id's]
  end
end
end

Repository Method:

[ResourceRepository].[linked name]_id_list

Routes Available/Registered:

/user/:id

Output:

{
user: {
        user_id: "1"
        name: "One"
      }
links:  [
          {
            rel: "self"
            uri: "/user/1"
          },
          {
            rel: "computer"
            uri: "/computer/2"
          }
        ]
}

The Respository for User needs to have a method that returns an array of id's for use in the HyperMedia links. The link to Self uses this method. The output will be something like this:

route :user do
id :id
link_to :car
end
route :car do
id :id
route_for :user
end

module Repository
module User
  def self.one(id)
    [Return User for User Id]
  end
end
module Car
  def self.user(id)
    [Return List of Cars for User Id]
  end
end
end

Routes Available/Registered:

/user/:id
/car/user/:id

Output:

{
user: {
        user_id: "1"
        name: "One"
      }
links:  [
          {
            rel: "self"
            uri: "/user/1"
          },
          {
            rel: "computer"
            uri: "/computer/user/1"
          }
        ]
}

This way of linking solves a lot of problems, and can also be used for searching support. route_for supports an optional 2nd parameter to specify the type of request it must register for:

route :car do
id :id
route_for :user, :post
end

The Called Repository can also have multiple input paramaters:

def some_method(id, name, email)
...
end

will need an incoming Request with these paramaters:

id, name, email

Media Type

The REST media type can be built up:

required :name
optional :country

Exposing the Media Type is on the cards.

Validation

Validation can be added on any field by providing arguments after a field declaration:

required :name, :length => 4

This will cause the paramater to be validated against the method defined in Ananke::Rules. Custom Rules can be added to the module and provided as arguments. Current Default included rules are:

length(min)

Validation Methods are Invoked in the Ananke::Rules context, and has access to a class variable named value, which holds the value for the currently valuated Parameter.

To Add a Custom Rule:

rule :name, do
value == [expected] ? nil : 'Not Expected Value'
end

required :name, :name

or

module Ananke
module Rules
  def validate_name
    value == [expected] ? nil : 'Not Expected Value'
  end
end
end

required :name, :name

or Advanced

module Ananke
module Rules
  def validate_name(extra)
    value == [expected using extra] ? nil : 'Not Expected Value'
  end
end
end

required :name, :name => extra

Future

A short list of future development:

- Refactor!
- Return Value Strategy
- Resource Exposes Media Type
- Lots more `bullet-proofing`
- ETag Support

More