Permalink
Browse files

work on REST test, multi-actions.

  • Loading branch information...
1 parent 49c296f commit 0a5fc0091f868e78f661ed81c0250dc042cabad6 @bingomanatee committed Apr 22, 2012
@@ -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;
}
View
@@ -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 ****************** */
@@ -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;
},
@@ -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);
},
@@ -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')) {
@@ -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;
@@ -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);
},
@@ -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 {
@@ -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();
}
View
@@ -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);
@@ -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"
+}
@@ -0,0 +1,3 @@
+{
+ "port": 3335
+}
Oops, something went wrong.

0 comments on commit 0a5fc00

Please sign in to comment.