Skip to content

Commit

Permalink
Export code from railwayjs
Browse files Browse the repository at this point in the history
  • Loading branch information
1602 committed Dec 17, 2011
1 parent 7e1bd60 commit 9194466
Show file tree
Hide file tree
Showing 2 changed files with 294 additions and 13 deletions.
222 changes: 211 additions & 11 deletions lib/railway_routes.js
@@ -1,3 +1,5 @@
var singularize = require('./inflection').singularize;

exports.Map = Map;

function Map(app, bridge) {
Expand All @@ -6,7 +8,9 @@ function Map(app, bridge) {
this.paths = [];
this.ns = '';
// wtf???
this.glob_path = '/';
this.globPath = '/';
this.pathTo = {};
this.dump = [];
}

Map.prototype.urlHelperName = function (path, action) {
Expand Down Expand Up @@ -54,7 +58,7 @@ Map.prototype.urlHelperName = function (path, action) {

var path;
if (typeof subpath === 'string') {
path = this.glob_path + subpath.replace(/^\/|\/$/, '');
path = this.globPath + subpath.replace(/^\/|\/$/, '');
} else { // regex???
path = subpath;
}
Expand Down Expand Up @@ -84,17 +88,213 @@ Map.prototype.urlHelperName = function (path, action) {
}
args = args.concat(this.bridge(this.ns, controller, action));

// this.bridge.dump.push({
// helper: options.as || this.urlHelperName(path, action),
// method: method,
// path: path,
// file: this.ns + controller,
// name: controller,
// action: action
// });
this.dump.push({
helper: options.as || this.urlHelperName(path, action),
method: method,
path: path,
file: this.ns + controller,
name: controller,
action: action
});

// this.paths.push([path, action]);
this.addPath(path, action);

this.app[method].apply(this.app, args);
};
});

Map.prototype.addPath = function (templatePath, action) {
if (templatePath instanceof RegExp) {
// TODO: think about adding to `path_to` routes by reg ex
return;
}
var paramsLength = templatePath.match(/\/:/g);
paramsLength = paramsLength === null ? 0 : paramsLength.length;
var helperName = this.urlHelperName(templatePath, action);

// already defined? not need to redefine
if (this.pathTo[helperName]) return;

this.pathTo[helperName] = function () {
if (arguments.length < paramsLength) {
return '';
// throw new Error('Expected at least ' + paramsLength + ' params for build path ' + templatePath + ' but only ' + arguments.length + ' passed');
}
var value, arg, path = templatePath;
for (var i = 0; i < paramsLength; i += 1) {
value = null;
arg = arguments[i];
if (arg && typeof arg.to_param == 'function') {
value = arg.to_param();
} else if (arg && arg.id) {
value = arg.id;
} else {
value = arg;
}
path = path.replace(/:\w*/, value);
}
if (arguments[paramsLength]) {
var query = [];
for (var key in arguments[paramsLength]) {
if (key == 'format' && path.match(/\.:format\??$/)) {
path = path.replace(/\.:format\??$/, '.' + arguments[paramsLength][key]);
} else {
query.push(key + '=' + arguments[paramsLength][key]);
}
}
if (query.length) {
path += '?' + query.join('&');
}
}
path = path.replace(/\.:format\?/, '');
// add ability to hook url handling via app
if (this.app.hooks && this.app.hooks.path) {
this.app.hooks.path.forEach(function (hook) {
path = hook(path);
});
}
return path;
}.bind(this);
this.pathTo[helperName].toString = function () {
return this.pathTo[helperName]();
}.bind(this);
}

Map.prototype.resources = function (name, params, actions) {
var self = this;
// params are optional
params = params || {};
// if params arg omitted, second arg may be `actions`
if (typeof params == 'function') {
actions = params;
params = {};
}
// we have bunch of actions here, will create routes for them
var activeRoutes = getActiveRoutes(params);
// but first, create subroutes
if (typeof actions == 'function') {
this.subroutes(name + '/:' + (singularize(name) || name) + '_id', actions);
}
// now let's walk through action routes
for (var action in activeRoutes) {
var route = activeRoutes[action].split(/\s+/);
var method = route[0];
var path = route[1];

// append format
if (path == '/') {
path = '.:format?';
} else {
path += '.:format?';
}

// middleware logic (backward compatibility)
var middlewareExcept = params.middlewareExcept, skipMiddleware = false;
if (middlewareExcept) {
if (typeof middlewareExcept == 'string') {
middlewareExcept = [middlewareExcept];
}
middlewareExcept.forEach(function (a) {
if (a == action) {
skipMiddleware = true;
}
});
}

// params.path setting allows to override common path component
var effectivePath = (params.path || name) + path;

// and call map.{get|post|update|delete}
// with the path, controller, middleware and options
this[method.toLowerCase()].call(
this,
effectivePath,
name + '#' + action,
skipMiddleware ? [] : params.middleware,
getParams(action, params)
);
}

// calculate set of routes based on params.only and params.except
function getActiveRoutes(params) {
var activeRoutes = {},
availableRoutes =
{ 'index': 'GET /'
, 'create': 'POST /'
, 'new': 'GET /new'
, 'edit': 'GET /:id/edit'
, 'destroy': 'DELETE /:id'
, 'update': 'PUT /:id'
, 'show': 'GET /:id'
};

// 1. only
if (params.only) {
if (typeof params.only == 'string') {
params.only = [params.only];
}
params.only.forEach(function (action) {
if (action in availableRoutes) {
activeRoutes[action] = availableRoutes[action];
}
});
}
// 2. except
else if (params.except) {
if (typeof params.except == 'string') {
params.except = [params.except];
}
for (var action in availableRoutes) {
if (params.except.indexOf(action) == -1) {
activeRoutes[action] = availableRoutes[action];
}
}
}
// 3. all
else {
for (var action in availableRoutes) {
activeRoutes[action] = availableRoutes[action];
}
}
return activeRoutes;
}

function getParams(action, params) {
var p = {};
var plural = action === 'index' || action === 'create';
if (params.as) {
p.as = plural ? params.as : singularize(params.as);
p.as = self.urlHelperName(self.globPath + p.as);
if (action === 'new' || action === 'edit') {
p.as = action + '_' + p.as;
}
}
if (params.path && !p.as) {
var aname = plural ? name : singularize(name);
aname = self.urlHelperName(self.globPath + aname);
p.as = action === 'new' || action === 'edit' ? action + '_' + aname : aname;
}
return p;
}
};

Map.prototype.namespace = function (name, subroutes) {
// store previous ns
var old_ns = this.ns, oldGlobPath = this.globPath;
// add new ns to old (ensure tail slash present)
this.ns = old_ns + name.replace(/\/$/, '') + '/';
this.globPath = oldGlobPath + name.replace(/\/$/, '') + '/';
subroutes(this);
this.ns = old_ns;
this.globPath = oldGlobPath;
};

Map.prototype.subroutes = function (name, subroutes) {
// store previous ns
var oldGlobPath = this.globPath;
// add new ns to old (ensure tail slash present)
this.globPath = oldGlobPath + name.replace(/\/$/, '') + '/';
subroutes(this);
this.globPath = oldGlobPath;
};

85 changes: 83 additions & 2 deletions test/railway_routes_test.js
@@ -1,5 +1,5 @@
require('./spec_helper').init(module.exports);
var pathetic = require('../lib/railway_routes');
var routes = require('../lib/railway_routes');

function fakeApp(container) {
var app = {};
Expand All @@ -21,7 +21,7 @@ function fakeBridge() {

it('should produce routes for all methods', function (test) {
var paths = [];
var map = new pathetic.Map(fakeApp(paths), fakeBridge());
var map = new routes.Map(fakeApp(paths), fakeBridge());
map.get('/signin', 'session#new');
map.post('/signin', 'session#create');
map.del('/signout', 'session#destroy');
Expand All @@ -37,7 +37,88 @@ it('should produce routes for all methods', function (test) {
[ 'PUT', '/signup', 'users#create' ],
[ 'ALL', '/path', 'controller#action' ]
]);
test.equal(map.pathTo.signin(), '/signin');
test.equal(map.pathTo.signin, '/signin');
test.equal(map.pathTo.signout(), '/signout');
test.equal(map.pathTo.signup(), '/signup');
test.equal(map.pathTo.path(), '/path');
test.done();
});

it('should create resourceful routes', function (test) {
var paths = [];
var map = new routes.Map(fakeApp(paths), fakeBridge());
map.resources('users');
test.deepEqual(paths, [
[ 'GET', '/users.:format?', 'users#index' ],
[ 'POST', '/users.:format?', 'users#create' ],
[ 'GET', '/users/new.:format?', 'users#new' ],
[ 'GET', '/users/:id/edit.:format?', 'users#edit' ],
[ 'DELETE', '/users/:id.:format?', 'users#destroy' ],
[ 'PUT', '/users/:id.:format?', 'users#update' ],
[ 'GET', '/users/:id.:format?', 'users#show' ]
]);
test.equal(map.pathTo.users(), '/users');
test.equal(map.pathTo.users, '/users');
test.equal(map.pathTo.new_user(), '/users/new');
test.equal(map.pathTo.new_user, '/users/new');
test.equal(map.pathTo.edit_user(1), '/users/1/edit');
test.equal(map.pathTo.user(2), '/users/2');
test.done();
});

it('should describe namespaced route', function (test) {
var paths = [];
var map = new routes.Map(fakeApp(paths), fakeBridge());
map.namespace('admin', function (admin) {
admin.get('dashboard', 'dashboard#index');
});
test.deepEqual(paths, [
[ 'GET', '/admin/dashboard', 'admin/dashboard#index' ]
]);
test.equal(map.pathTo.admin_dashboard(), '/admin/dashboard');
test.done();
});

it('should describe namespaced resource', function (test) {
var paths = [];
var map = new routes.Map(fakeApp(paths), fakeBridge());
map.namespace('admin', function (admin) {
admin.resources('pages');
});
test.deepEqual(paths, [
[ 'GET', '/admin/pages.:format?', 'admin/pages#index' ],
[ 'POST', '/admin/pages.:format?', 'admin/pages#create' ],
[ 'GET', '/admin/pages/new.:format?', 'admin/pages#new' ],
[ 'GET', '/admin/pages/:id/edit.:format?', 'admin/pages#edit' ],
[ 'DELETE', '/admin/pages/:id.:format?', 'admin/pages#destroy' ],
[ 'PUT', '/admin/pages/:id.:format?', 'admin/pages#update' ],
[ 'GET', '/admin/pages/:id.:format?', 'admin/pages#show' ]
]);
test.equal(map.pathTo.admin_pages, '/admin/pages');
test.equal(map.pathTo.new_admin_page(), '/admin/pages/new');
test.equal(map.pathTo.new_admin_page, '/admin/pages/new');
test.equal(map.pathTo.edit_admin_page(1), '/admin/pages/1/edit');
test.equal(map.pathTo.admin_page(2), '/admin/pages/2');
test.done();
});

it('should allow overwrite path and helper', function (test) {
var paths = [];
var map = new routes.Map(fakeApp(paths), fakeBridge());
map.resources('avatars', {as: 'images', path: 'pictures'});
test.deepEqual(paths, [
[ 'GET', '/pictures.:format?', 'avatars#index' ],
[ 'POST', '/pictures.:format?', 'avatars#create' ],
[ 'GET', '/pictures/new.:format?', 'avatars#new' ],
[ 'GET', '/pictures/:id/edit.:format?', 'avatars#edit' ],
[ 'DELETE', '/pictures/:id.:format?', 'avatars#destroy' ],
[ 'PUT', '/pictures/:id.:format?', 'avatars#update' ],
[ 'GET', '/pictures/:id.:format?', 'avatars#show' ]
]);
test.equal(map.pathTo.pictures, '/pictures');
test.equal(map.pathTo.new_picture, '/pictures/new');
test.equal(map.pathTo.edit_picture(1), '/pictures/1/edit');
test.equal(map.pathTo.picture(1602), '/pictures/1602');
test.done();
});

0 comments on commit 9194466

Please sign in to comment.