Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

work on REST test, multi-actions.

  • Loading branch information...
commit 0a5fc0091f868e78f661ed81c0250dc042cabad6 1 parent 49c296f
Dave Edelhart authored
4 lib/Action/handler/Config.js
View
@@ -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;
}
64 lib/Action/index.js
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 {
1  lib/Req_State/index.js
View
@@ -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();
}
36 lib/Resource/index.js
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);
162 test_resources/REST_test/app/con_folks/actions/action_folks/folks_action.js
View
@@ -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);
+ }
+ }
+}
7 test_resources/REST_test/app/con_folks/actions/action_folks/folks_config.json
View
@@ -0,0 +1,7 @@
+{
+ "method": "*",
+ "get_route": "/folks/:id",
+ "put_route": "/folks",
+ "post_route": "/folks/:id",
+ "delete_route": "/folks/:id"
+}
3  test_resources/REST_test/app/frame_config.json
View
@@ -0,0 +1,3 @@
+{
+ "port": 3335
+}
9 test_resources/REST_test/app/resources/express_helper/express_body_parser.js
View
@@ -0,0 +1,9 @@
+var express = require('express');
+
+module.exports = {
+ apply: function(server, target, cb){
+ // console.log('applying loggeg');
+ server.use(express.bodyParser());
+ cb();
+ }
+}
14 test_resources/REST_test/app/resources/express_helper/express_view.js
View
@@ -0,0 +1,14 @@
+var ejs = require('ejs');
+
+
+module.exports = {
+ apply: function(server, target, cb){
+ // console.log('applying loggeg');
+ server.set('view_engine', 'ejs');
+ server.set('view options', {
+ layout: false
+ });
+ server.register('.html', ejs);
+ cb();
+ }
+}
13 test_resources/REST_test/app/resources/express_helper/logger.js
View
@@ -0,0 +1,13 @@
+var Express = require('express');
+var util = require('util');
+var _ = require('underscore');
+
+
+module.exports = {
+ apply: function(server, target, cb){
+ // console.log('applying loggeg');
+ server.use(Express.logger());
+ cb();
+ }
+
+}
29 test_resources/REST_test/app/resources/model/model_folks.js
View
@@ -0,0 +1,29 @@
+var Mongoose_Model = require("node-support/mongoose_model");
+var NE = require('./../../../../../lib');
+var util = require('util');
+var _ = require('underscore');
+var mongoose = require('mongoose');
+function Mongoose_Model_Resource(model, config) {
+ Mongoose_Model.MongooseModel.call(this, model, config);
+}
+
+util.inherits(Mongoose_Model_Resource, Mongoose_Model.MongooseModel);
+_.extend(Mongoose_Model_Resource.prototype, NE.Resource.prototype);
+
+var _model;
+//console.log('SMR prototype: %s, model: %s', util.inspect(Simple_Model_Resource.prototype), util.inspect(Simple_Model_Resource));
+module.exports = function () {
+ if (!_model) {
+ var schema = new mongoose.Schema({
+ name:String,
+ notes:String,
+ birthday:Date,
+ gender: Number // 1 = male, -1 = female, 0/other == ??
+ });
+
+ var model = mongoose.model('Folks', schema);
+
+ _model = new Mongoose_Model_Resource(model, {});
+ }
+ return _model;
+}
92 tests/REST_test.js
View
@@ -0,0 +1,92 @@
+var Framework = require('../lib/Framework');
+var path = require('path');
+var _ = require('underscore');
+var util = require('util');
+var request = require('request');
+var fs = require('fs');
+var path = require('path');
+var mongoose = require('mongoose');
+
+mongoose.connect('mongodb://localhost/mmt');
+
+var framework;
+
+var app_path = path.resolve(__dirname + '/../test_resources/REST_test/app');
+var root = 'http://localhost:3335/';
+
+function _ss(a) {
+ return _.sortBy(a, function (i) {
+ return i
+ });
+}
+
+function _1(a) {
+ return _.map(a, function (aa) {
+ return aa[0]
+ });
+}
+
+var Roger;
+
+module.exports = {
+
+ setup:function (test) {
+ framework = new Framework({path:app_path});
+ Roger = {name:"Roger", notes:"three cheese blend"};
+ framework.start_load(function () {
+ test.done();
+ }, app_path);
+ },
+
+ test_server_load:function (test) {
+ framework.on('load_done', function () {
+ test.ok(!(framework.hasOwnProperty('abc')),
+ 'framework doesn\'t have alpha on load done');
+ });
+
+ framework.start_server(function () {
+ test.equal(framework.config.port, 3335, 'port is 3335');
+ framework.server().listen(framework.config.port);
+ console.log('server listening at port %s', framework.config.port);
+ test.done();
+ })
+ },
+
+ test_prop_response:function (test) {
+ request.post({uri:root + 'folks', form:Roger },
+ function (err, res, put_body) {
+ var Roger_JSON = JSON.parse(put_body);
+ test.ok(Roger_JSON._id, 'return json has ID');
+ var id = Roger_JSON._id;
+ delete Roger_JSON._id;
+ test.deepEqual(Roger, Roger_JSON, 'Rogering');
+
+ /* *********** RETRIEVE ROGER **************** */
+
+ process.nextTick(function(){
+ request.get(root + 'folks/' + id, function(err, rest, get_body){
+ test.equal(put_body, get_body, 'Can retrieve Roger');
+
+ /* *********** CALL HIM ROD ************** */
+
+ request.post({uri: root + 'folks/' + id, form: {name: "Rod"}}, function(err, r, updated_body){
+
+ var altered_rod = JSON.parse(updated_body);
+ test.equal(altered_rod.name, 'Rod', 'name is Rod');
+ test.equal(altered_rod.noted, Roger.notes, 'same old notes');
+ test.done();
+
+ })
+
+ });
+
+ })
+
+ });
+ },
+
+ test_done_response:function (test) {
+ framework.server().close();
+ test.done();
+ }
+}
Please sign in to comment.
Something went wrong with that request. Please try again.