Skip to content

Commit a7b2ee8

Browse files
committed
refactor(routing): Cleaned up routing and updated tests, release 1.1.5
1 parent ffa7150 commit a7b2ee8

File tree

3 files changed

+70
-86
lines changed

3 files changed

+70
-86
lines changed

controller.js

Lines changed: 68 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ module.exports = Class.extend(
4747
},
4848

4949
autoRoute: function( app ) {
50-
var middleware = []
51-
, routes = this.route.split( '|' )
50+
var middleware = []
51+
, routes = this.route instanceof Array ? this.route : this.route.split( '|' );
5252

5353
debug( 'Autorouting for route ' + routes.join( ', ' ) );
5454

@@ -66,33 +66,39 @@ module.exports = Class.extend(
6666

6767
// Bind the actual routes
6868
routes.forEach(function( route ) {
69-
var actionIdRoute = [ route, ':action', ':id?' ].join( '/' )
70-
, actionRoute = [ route, ':action?' ].join( '/' );
69+
var methods = [ 'GET', 'POST', 'PUT', 'DELETE' ];
7170

72-
debug( 'Attaching route ' + actionIdRoute );
73-
app.all.apply( app, [ actionIdRoute ].concat( middleware ) ); // /example/:action/:id?
74-
75-
debug( 'Attaching route ' + actionRoute );
76-
app.all.apply( app, [ actionRoute ].concat( middleware ) ); // /example/?:action?
71+
debug( 'Attaching route ' + route );
72+
if ( /(^[^\/]+)\ ?(\/.*)/ig.test( route ) ) {
73+
methods = RegExp.$1;
74+
route = RegExp.$2;
75+
methods = methods.match( /\[([^\[\]]+)\]/ig );
76+
77+
if ( methods.length ) {
78+
methods = methods[ 0 ].replace( /(\[|\])/ig, '' );
79+
methods = methods.split( ',' );
80+
}
81+
}
82+
83+
methods.forEach( function( method ) {
84+
app[ method.toLowerCase() ].apply( app, [ route ].concat( middleware ) );
85+
});
7786
});
7887
},
7988

8089
extend: function() {
8190
var extendingArgs = [].slice.call( arguments )
82-
, autoRouting = ( extendingArgs.length === 2 )
83-
? extendingArgs[ 0 ].autoRouting !== false
84-
: this.autoRouting
85-
, definedRoute = ( extendingArgs.length === 2 )
86-
? extendingArgs[ 0 ].route !== undefined
87-
: this.route;
91+
, autoRouting = ( extendingArgs.length === 2 ) ? extendingArgs[ 0 ].autoRouting !== false : this.autoRouting
92+
, definedRoute = ( extendingArgs.length === 2 ) ? extendingArgs[ 0 ].route !== undefined : this.route;
8893

8994
// Figure out if we are autoRouting and do not have a defined route already
9095
if ( autoRouting && !definedRoute ) {
9196
var stack = new Error().stack.split( '\n' )
92-
, stack = stack.splice( 1, stack.length - 1 )
9397
, extendingFilePath = false
9498
, extendingFileName = false
9599
, route = null;
100+
101+
stack = stack.splice( 1, stack.length - 1 );
96102

97103
// Walk backwards over the stack to find the filename where this is defined
98104
while( stack.length > 0 && extendingFilePath === false ) {
@@ -110,7 +116,13 @@ module.exports = Class.extend(
110116
var singular = i.singularize( extendingFileName.replace( /(controller)?.js/ig, '' ).toLowerCase() )
111117
, plural = i.pluralize( singular );
112118

113-
route = [ '/', singular, '|', '/', plural ].join('');
119+
route = [];
120+
route.push( '/' + singular + '/:id/?' );
121+
route.push( '/' + singular + '/:id/:action/?' );
122+
route.push( '/' + plural + '/?' );
123+
route.push( '/' + plural + '/:action/?' );
124+
125+
route = route.join( '|' );
114126

115127
if ( extendingArgs.length === 2 ) {
116128
extendingArgs[ 0 ].route = route;
@@ -132,7 +144,11 @@ module.exports = Class.extend(
132144
resFunc: 'json',
133145
action: null,
134146

135-
setup: function(req, res, next) {
147+
setup: function( req, res, next ) {
148+
this.next = next;
149+
this.req = req;
150+
this.res = res;
151+
136152
try {
137153
return this.performanceSafeSetup( req, res, next );
138154
} catch( e ) {
@@ -141,80 +157,48 @@ module.exports = Class.extend(
141157
},
142158

143159
performanceSafeSetup: function( req, res, next ) {
144-
var method = null
145-
, funcName = null
146-
, parts = null;
147-
148-
this.next = next;
149-
this.req = req;
150-
this.res = res;
151-
152-
// Override routes where you attach specifically to a single route
153-
if ( this.Class.actionRouting && /\//.test( this.req.url ) ) {
154-
parts = this.req.url.split( '/' );
155-
funcName = parts[ parts.length - 1 ];
156-
157-
if(/\#|\?/.test(funcName)){
158-
funcName = funcName.split( /\#|\?/ )[ 0 ];
159-
}
160-
161-
if ( isNaN( funcName ) ) {
162-
funcName = funcName + 'Action';
163-
if ( typeof this[ funcName ] == 'function' ) {
164-
debug( 'actionRouting mapped to ' + funcName );
165-
166-
return [ null, funcName, next ];
167-
}
168-
}
160+
var methodAction = req.method.toLowerCase() + 'Action'
161+
, actionRouting = this.Class.actionRouting
162+
, actionMethod = /\/([a-zA-z]+)(\/?|\?.*|\#.*)?$/ig.test( req.url ) ? RegExp.$1 + 'Action' : ( req.params.action !== undefined ? req.params.action : false )
163+
, restfulRouting = this.Class.restfulRouting
164+
, idRegex = /(^[0-9]+$|^[0-9a-fA-F]{24}$)/
165+
, hasIdParam = req.params && req.params.id !== undefined ? true : false
166+
, id = !!hasIdParam && idRegex.test( req.params.id ) ? req.params.id : false
167+
, hasActionParam = req.params && req.params.action !== undefined ? true : false
168+
, action = !!hasActionParam && !idRegex.test( req.params.action ) ? req.params.action + 'Action' : false;
169+
170+
// console.log( 'methodAction:' + methodAction );
171+
// console.log( 'actionMethod:' + actionMethod );
172+
// console.log( 'actionRouting:' + actionRouting );
173+
// console.log( 'actionMethod:' + actionMethod );
174+
// console.log( 'restfulRouting:' + restfulRouting );
175+
// console.log( 'hasIdParam:' + hasIdParam );
176+
// console.log( 'id:' + id );
177+
// console.log( 'hasActionParam:' + hasActionParam );
178+
// console.log( 'action:' + action );
179+
180+
if ( !!actionRouting && !!hasActionParam && action !== false && typeof this[ action ] === 'function' ) {
181+
debug( 'actionRouting: mapped by url to ' + action );
182+
return [ null, action, next ];
169183
}
170184

171-
// Route based on an action first if we can
172-
if ( this.Class.actionRouting && typeof this.req.params !== 'undefined' && typeof this.req.params.action !== 'undefined' ) {
173-
// Action Defined Routing
174-
// Updated to consider ObjectId's as numbers for Mongo ids
175-
if ( !/^[0-9a-fA-F]{24}$/.test( this.req.params.action ) && isNaN( this.req.params.action ) ) {
176-
funcName = this.req.params.action + 'Action';
177-
178-
if ( typeof this[ funcName ] == 'function' ) {
179-
debug( 'actionRouting mapped to ' + funcName );
180-
return [ null, funcName, next ];
181-
} else {
182-
throw new NoActionException();
183-
}
184-
} else {
185-
// HTTP Method Based Routing
186-
method = this.req.method.toLowerCase() + 'Action';
187-
if ( typeof this[ method ] == 'function' ) {
188-
debug( 'http method route mapped to ' + method );
189-
190-
this.req.params.id = this.req.params.action;
191-
delete this.req.params.action;
192-
193-
return [ null, method, next ];
194-
} else {
195-
throw new NoActionException();
196-
}
197-
}
185+
if ( actionMethod !== false && typeof this[ actionMethod ] === 'function' ) {
186+
debug( 'actionRouting: mapped by param to ' + actionMethod );
187+
return [ null, actionMethod, next ];
198188
}
199189

200-
// Route based on the HTTP Method, otherwise throw an exception
201-
if ( this.Class.restfulRouting ) {
202-
if ( this.isGet() && ( this.req.params === undefined || this.req.params.id === undefined ) && typeof this.listAction === 'function' ) {
203-
method = 'listAction';
204-
205-
debug( 'restfulRouting mapped to ' + method );
206-
} else {
207-
method = this.req.method.toLowerCase() + 'Action';
208-
if ( typeof this[ method ] != 'function' ) {
209-
throw new NoActionException();
210-
}
190+
if ( !!restfulRouting ) {
191+
if ( methodAction === 'getAction' && !id && typeof this.listAction === 'function' ) {
192+
methodAction = 'listAction';
193+
}
211194

212-
debug( 'restfulRouting mapped to ' + method );
195+
if ( typeof this[ methodAction ] === 'function' ) {
196+
debug( 'restfulRouting mapped to ' + methodAction );
197+
return [ null, methodAction, next ];
213198
}
214199
}
215200

216-
// If we got this far without an action but with a method, then route based on that
217-
return [ null, method, next ];
201+
return [ new NoActionException(), null, next ];
218202
},
219203

220204
init: function( error, method, next ) {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "clever-controller",
33
"description": "Lightning-fast flexible controller prototype",
4-
"version": "1.1.4",
4+
"version": "1.1.5",
55
"private": false,
66
"repository": {
77
"type": "git",

test/controller.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ describe('Controller', function () {
186186
url: 'http://example.com/search/profile#?param1=value1&param2=value2',
187187
method: 'GET'
188188
},
189-
res = {},
189+
res = { json: sinon.spy() },
190190
next = sinon.spy();
191191
var c = new Ctrl(req, res, next);
192192
c.profileAction.called.should.be.true;

0 commit comments

Comments
 (0)