RESTful Web API+Rack
Idea
+issues
We need a simple standards based api all part of Wagn. Rack is a well establish specification for web service middlewares. Rack integration is also part of Rails 3, so this is also part of the path to that.
+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.
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
+discussed in support tickets
+relevant user stories