RESTful Web API+Rack+solution

In a nutshel, a Rack application is a ruby object that responds to the :call method, just like a Proc.  It takes a single parameter, env which has a Hash like interface.  The Rack SPEC is mostly about what keys and values are in this hash.  Most of the variables correspond to widely used standards for middleware, not just ruby, and are already being used to dispatch Wagn requests "under the hood" in Rails.

 

This proposal splits Wagn into three components, a model, view and controller (MVC).  The CardController in the diagram is much like the current CardController, only instead of calling model and view methods directly, it loads wagn.* variables into the env to tell the lower middlewares what to do.  These wagn.* attributes are a private namespace to the Wagn MVC Rack architecture, just as rack.* entries are used by Rack and to communicate with Rack.

 

RESTful Web API+Rack+solution+diagram

Routing

The routing will now all be Rack based, which means it will use env['wagn.*'] objects in the rack env hash to pass objects along the processing chain.  Testing needs to use this routing rather than controller based testing as we will not keep that interface stable and we could even eliminate the CardController object.  The remaining controller would then be the ApplicationController which could be as simple as this:

class ApplicationController << ActionController::Base
  def all
    card.call(env)
    render show
  end
end

With the 'show' view defined very close to the render_show method of the current card controller (i.e. just get the renderer for the card/format and call render on it).

It that case, we would have move the routing and the processing described below for CardController into middleware.  Because this will all be Rack based, it will be either to make this transformation if it is desirable.

Wagn Variables

wagn.id => The 'id' part of the request path.

wagn.format => A matched format extension.

wagn.action => The requested action

wagn.view => The requested view

wagn.type => The requested type

wagn.args => Parameters for a card (params['card'])

wagn.params => The rest of the parameters

Card Controller

The first version of this has the routing in a 'matches?' method used as a 'constraint' to route everything that it matches to card#all.  This stage has to transform what the routing did further.  The wagn vars above are the inputs, and more will be created in this phase.

In REST terms, some operations are on a specific item in the collection and some are on the collection.  You create new items on the collection, you read, update or remove existing ones.  Missing cards are a special case for wagn, technicaly they are a view on the collection, as is the 'new' action that formats a form that would submit a create action.  The importance in the controller phase is in where we look for card arguments, in wagn.id for existing cards, or wagn.args for the specs of a new or missing card.

Wagn Variables

wagn.id    =>      If this is d+, we might make it an Int type rather than String

wagn.name =>   Transformed with cardname rules, (String, or maybe Wagn::Cardname)

wagn.format => Add defaults and regularize (Symbol)

wagn.view   =>   (Symbol)

wagn.model  =>  The model action (CRUD)

wagn.render =>   The renderer view

wagn.card   =>    The fetched or created (new/create) card

 

params['append']  =>  For update, content to be appended to current content (comments)

params['add_item'] =>  An argument for models like Pointer that have an add_item method

params['drop_item'] => An argument for drop_item

Card Model

Rack middleware interface.  Output in wagn.status, or throw an error.

create, read, update, remove      Returns just wagn.status for most errors.

create!, read!, update!, remove! Same functions as above, but throw errors, return only for normal render

Wagn Variables

wagn.status True if no errors

Renderer

We aren't quite here yet, but in the end, it would be nice to use Rack idioms here:

use Wagn::Renderer.new Card::Rack.new() Application::Dispatcher.new()

The idea would be that Application::Dispatcher.new() would be the app represented by the Rails application, routes.rb, and the above in config.ru would create our application thusly:

class Wagn::Renderer
  def self.new(app, *pipeline)
    @app = app
    @pipeline = pipline
  end
  def call(env)
@pipeline.each { |a| a.call(env) }
    @app.call(env)
  end
end