Skip to content

Commit

Permalink
work on REST test, multi-actions.
Browse files Browse the repository at this point in the history
  • Loading branch information
bingomanatee committed Apr 22, 2012
1 parent 49c296f commit 0a5fc00
Show file tree
Hide file tree
Showing 12 changed files with 399 additions and 35 deletions.
4 changes: 2 additions & 2 deletions lib/Action/handler/Config.js
Expand Up @@ -12,8 +12,8 @@ function _cc(prop, value, target) {
} else {
value = value.toUpperCase();

if (!_.include(['GET', 'PUT', 'POST', 'DELETE'], value)) {
throw new Error('bad rest verb ' + value + 'passed to config of ' + targetname);
if (!_.include(['GET', 'PUT', 'POST', 'DELETE', "*"], value)) {
throw new Error('bad rest verb ' + value + 'passed to config of ' + target.name);
}
target.method = value;
}
Expand Down
64 changes: 40 additions & 24 deletions lib/Action/index.js
Expand Up @@ -23,11 +23,11 @@ function Action(config) {
}

_.extend(Action.prototype, Loader.prototype, {
route: false,
route:false,
type:'',
method:'get',
CLASS:'ACTION',
name: false,
name:false,

/* ********************* EVENTS ****************** */

Expand All @@ -37,59 +37,74 @@ _.extend(Action.prototype, Loader.prototype, {
},

extend:function (e) {
// console.log('extending action with %s', util.inspect(e));
// console.log('extending action with %s', util.inspect(e));
_.extend(this, e);
},

on_load_done: function(){
if (this.route === false){
on_load_done:function () {
if (this.route === false) {
this._make_route();
}
},

_make_route: function(){
_make_route:function () {
var route_path = [this.controller.name, this.name];
if(this.controller.parent.CLASS == 'COMPONENT'){
if (this.controller.parent.CLASS == 'COMPONENT') {
route_path.unshift(this.controller.parent.name);
}
this.route = '/' + route_path.join('/');
// console.log('making route for %s: %s', this.name, this.route);
// console.log('making route for %s: %s', this.name, this.route);
},

/* ********************* SERVER ****************** */

start_server:function (server, framework, cb) {
this.digest_res(framework.get_resources());
var self = this;

this.digest_res(framework.get_resources());
this.framework = framework;

//@TODO: handle multiple requiest type
var method = this.method.toLowerCase();
var route = this.route;
if (method == '*') {
//console.log('MULTI METHOD!!!! %s', util.inspect(this));
['get', 'put', 'post', 'delete'].forEach(function (method) {
if (self.config.hasOwnProperty(method + '_route')) {
var route = self.config[method + '_route'];
self._ss_route(server, method, route);
}
});
} else {
var route = this.config.hasOwnProperty(method + '_route') ? this.config[method + '_route'] : this.route;
this._ss_route(server, method, route)
}

framework.log('action %s added route %s', this.name, this.route);

cb();
},

_ss_route:function (server, method, route) {
var self = this;
//console.log('routing %s: %s for %s', route, method, this.path);
if (!method){
if (!method) {
var msg = util.format('no method for action %s', this.path);
console.log(msg);
return cb( new Error(msg));
} else if (!route){
return cb(new Error(msg));
} else if (!route) {
var msg = util.format('no route for action %s', this.path);
route = this.heritage().replace(/:/g, '/').replace(/^app/, '');
console.log(msg + ': forcing route to %s', route);
// return cb( new Error(msg));
// return cb( new Error(msg));
}
server[method](route, function (req, res) {
return self.respond(req, res);
});

framework.log('action %s added route %s', this.name, this.route);

cb();
},

/* ********************** NAME ******************* */

heritage:function(){
heritage:function () {
return this.controller.heritage() + ':' + this.name;
},

Expand All @@ -105,7 +120,7 @@ _.extend(Action.prototype, Loader.prototype, {
},

respond:function (req, res) {
// console.log('responding: base');
// console.log('responding: base');
var rs = new Req_State(req, res, this, this.controller, this.framework);
this.validate(rs);
},
Expand Down Expand Up @@ -163,8 +178,8 @@ _.extend(Action.prototype, Loader.prototype, {
},

on_route:function (rs) {
var method = rs.method().toUpperCase();

var method = rs.method().toLowerCase();
console.log('routing %s', method);
switch (method) {
case 'get':
if (this.hasOwnProperty('on_get')) {
Expand All @@ -180,6 +195,7 @@ _.extend(Action.prototype, Loader.prototype, {

case 'put':
if (this.hasOwnProperty('on_put')) {
console.log('putting');
return this.on_put(rs);
}
break;
Expand All @@ -197,7 +213,7 @@ _.extend(Action.prototype, Loader.prototype, {
},

on_respond:function (rs) {
// console.log('responding with %s', this.path);
// console.log('responding with %s', this.path);
this.on_input(rs);
},

Expand All @@ -211,7 +227,7 @@ _.extend(Action.prototype, Loader.prototype, {

on_output:function (rs, output) {
var template = this.page_template_path();
// console.log('action: %s, template: %s', thisname, template);
// console.log('action: %s, template: %s', thisname, template);
if (template) {
rs.render(template, output);
} else {
Expand Down
1 change: 0 additions & 1 deletion lib/Req_State/index.js
Expand Up @@ -7,7 +7,6 @@ function Req_State(req, res, action, framework) {
this.action = action;
this.controller = action.controller;
this.framework = framework ? framework : action.framework;
this.framework = framework;
this.req_props = {};
this.read_req_props();
}
Expand Down
36 changes: 28 additions & 8 deletions lib/Resource/index.js
Expand Up @@ -6,19 +6,39 @@ var util = require('util');
var events = require('events');

/**
* A Res is a catchall for a reusable bundle of functionality.
* View helpers, models and action helpers are all Ress.
* Resource is ulitmately an interface: it must have a name and a type, and that combination must
* be unique across the applciation. The name and type of a resouce deafaults to the values
* set by convention. ie., resource/model/model_foo.js will by default be a model named foo.
*
* Note - as a rule, all Res modules should be FACTORIES -
* that is, functions that return a Res. This is because
* some Ress (like models) are SINGLETONS - one object exists and
* is shared for all users. Others are unique per owner. The calling
* contexts shouldn't have to know which case is true for a given resouce.
* Resources must be defined as either parameterless factory functions or naked objects.
*
* In the latter case, resources are created by extending a new Resource with said object.
*
* In the former case they can be - or be created by - any number of methods and may or may not be singletons.
* They also are not absolutely required to extend from the Resource class, as long as they implement
* name and type properties, and (if they are mixins or express_helpers) the apply method.
*
* In the case of factories, any configuration or individuation of a particular resource
* must be done underneath the closure.
*
* A resource may optionally have an on_add(target, callback) method that responds to being
* added to its parent. There is no guarantee regarding the ordering of resource loading at this point so
* on_add should not depend in any way on resource load ordering. it could, for instance,
* add an event listener for "load_done" on the target framework to execute an action after all
* resources have loaded.
*
* Two other specialist resources are express_helpers and mixins.
*
* mixins have an apply method, apply(framework, callback) that is called after all resources have been loaded
* but before the express_helpers have been apply()'d.
*
* express_helpers are specifically designed to alter or configure the express server
* and must have an apply(server, framework, callback) method designed to facilitate this activity.
* Again there is no guarantee as to which order this alteration will occur in, and its best to
* put them all in app/resources to avoid overlapping component or controller alteration of the server.
*
*/


function Res(config) {
if (config){
_.extend(this, config);
Expand Down
@@ -0,0 +1,162 @@
var util = require('util');

module.exports = {

on_get:function (rs) {
this.get_validate(rs);
},

get_validate:function (rs) {
if (rs.req_props.id) {
this.get_process(rs);
} else {
this.rest_err(rs, "get folks called with no ID");
}
},

get_process:function (rs) {
var self = this;
// console.log('getting %s', rs.req_props.id);
this.models.folks.get(rs.req_props.id, function (err, folk) {
// console.log('folk gotten: %s', util.inspect(folk));
if (err) {
self.rest_err(rs, 'cannot get folk id %s: %s', rs.req_props.id, err.toString());
} else if (folk) {
self.on_output(rs, folk.toObject());
} else {
self.rest_err(rs, 'cannot find folk id %s', rs.req_props.id);
}
});

},

on_post:function (rs) {
this.post_validate(rs);
},

post_validate:function (rs) {
var self = this;
delete rs.req_props.id;
var trial_model = new this.models.folks.model(rs.req_props);
trial_model.validate(function (errs) {

if (errs) {
return self.rest_err(rs, errs);
} else {
self.post_process(rs);
}
});
},

post_process:function (rs) {
var self = this;

this.models.folks.put(rs.req_props, function (err, folk) {
// console.log('have put %s -- %s', util.inspect(err), util.inspect(folk));
if (err) {
self.rest_err(rs, 'cannot put folk %s: %s', JSON.stringify(rs.req_props), err.toString());
} else if (folk) {
self.on_output(rs, folk.toObject());
} else {
self.rest_err(rs, 'cannot put folk %s', JSON.stringify(rs.req_props));
}

});
},

on_put:function (rs) {
this.put_validate(rs);
},

put_validate:function (rs) {
var id = rs.req_props.id;
delete rs.req_props.id;
/**
* note - unlike post, were we are definitively given a complete record,
* we are not assuming that the data is complete or valid - an additive model.
* So we are not fully validating the input here, just the existince of
* a previous model.
*/

if (!id) {
return this.rest_err(rs, 'cannot put to id %s', id);
}

this.models.folks.count({_id:id}, function (err, num) {
if (err) {
return this.rest_err(rs, err);
} else {
switch (num) {
case 0:
return this.rest_err(rs, 'cannot put to id %s - record not found', id);
break;

case 1:
return this.put_process(rs, {id:id});
break;

default:
//@TODO: err!
}
}
});
},

put_process:function (rs, input) {
var self = this;
this.models.folks.get(input.id, function (err, old_folk) {
if (err) {
return self.rest_err(rs, 'err getting %s: %s', input.id, err.toString());
} else {
for (var f in rs.req_props) {
old_folk[f] = rs.req_props[f];
}

old_folk.save(function (err, saved_folk) {
if (err) {
return self.rest_err(rs, 'err updating (put) %s: %s', input.id, err.toString());
} else {
self.on_output(rs, saved_folk.toObject());
}
});
}

});

},

on_delete:function (rs) {
var self = this;
if (rs.req_props.id) {
self.model.folks.get(rs.req_props.id, function (err, folk) {
if (err) {
self.rest_err(rs, 'cannot get folk id %s: %s', rs.req_props.id, err.toString());
} else if (folk) {
self.delete_process(rs, folk);
} else {
self.rest_err(rs, 'cannot find folk id %s', rs.req_props.id);
}
});
} else {
this.rest_err(rs, "get folks called with no ID");
}
},

delete_process:function (rs, folk) {
var self = this;
folk.remove(function (err, deleted_folk) {
self.on_output(rs, deleted_folk.toObject());
});
},

rest_err:function (rs, err) {
if (err instanceof Error) {
rs.send({error:err.toString() }, 400);
} else {

var args = [].slice.call(arguments, 0);
args.shift();
rs.send({error:util.format.apply(util, args)}, 400);
}
}
}
@@ -0,0 +1,7 @@
{
"method": "*",
"get_route": "/folks/:id",
"put_route": "/folks",
"post_route": "/folks/:id",
"delete_route": "/folks/:id"
}
3 changes: 3 additions & 0 deletions test_resources/REST_test/app/frame_config.json
@@ -0,0 +1,3 @@
{
"port": 3335
}

0 comments on commit 0a5fc00

Please sign in to comment.