From 03e891f10aa0b948d7f870ead47dbafc066fb61a Mon Sep 17 00:00:00 2001 From: Sam Gronblom Date: Fri, 15 Nov 2013 13:35:50 +0900 Subject: [PATCH 1/6] Require should so tests can be run using mocha --- test/resource.test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/resource.test.js b/test/resource.test.js index ee22990..9b24f84 100644 --- a/test/resource.test.js +++ b/test/resource.test.js @@ -1,6 +1,7 @@ var assert = require('assert') , express = require('express') + , should = require('should') , Resource = require('..') , request = require('supertest') , batch = require('./support/batch'); From a30372bb130f920ffbc4cf9813bf2cad23c4ab0d Mon Sep 17 00:00:00 2001 From: Sam Gronblom Date: Fri, 15 Nov 2013 13:37:05 +0900 Subject: [PATCH 2/6] Bind handler if it is a function - This will be properly bound if it is used in the handler - Not supported for the case where the handler is an object which maps format name strings to handler functions --- index.js | 4 +++- test/resource.test.js | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/index.js b/index.js index 7789c21..91ea7bf 100644 --- a/index.js +++ b/index.js @@ -61,7 +61,9 @@ function Resource(name, actions, app) { // default actions for (var i = 0, key; i < orderedActions.length; ++i) { key = orderedActions[i]; - if (actions[key]) this.mapDefaultAction(key, actions[key]); + var handler = actions[key]; + if (typeof handler === 'function') handler = handler.bind(actions); + if (handler) this.mapDefaultAction(key, handler); } // auto-loader diff --git a/test/resource.test.js b/test/resource.test.js index 9b24f84..c64d009 100644 --- a/test/resource.test.js +++ b/test/resource.test.js @@ -56,6 +56,21 @@ describe('app.resource()', function(){ .expect('destroy forum 5', next()); }) + it('should bind this correctly', function(done) { + var app = express(); + var testController = { + 'users': 'userA userB', + 'index': function(req, res) { + res.send(this.users); + } + }; + + app.resource('users', testController); + request(app) + .get('/users') + .expect('userA userB', done); + }); + it('should support root resources', function(done){ var app = express(); var next = batch(done); From 88263ce49db166447b79614447f8587a6f021430 Mon Sep 17 00:00:00 2001 From: Sam Gronblom Date: Fri, 15 Nov 2013 14:00:59 +0900 Subject: [PATCH 3/6] Add methods to Resource for creating various paths - One path for creating new resources - One path for editing existing resources - One path for the "root/collection" path that is routed to index - One path for accessing specific records, can be used with DELETE, PUT - Only creates paths without hostnames for the moment --- index.js | 41 +++++++++++++++++++++++++++++++++++++++++ test/resource.test.js | 22 ++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/index.js b/index.js index 91ea7bf..b444dd5 100644 --- a/index.js +++ b/index.js @@ -245,6 +245,47 @@ Resource.prototype.mapDefaultAction = function(key, fn){ } }; +/** + * Get edit path for resource. Ex '/users/42/edit'. + * + * @param {String} id of resource to edit + * @return {String} Url of resource "root" + */ + +Resource.prototype.editPath = function(id) { + return '/' + this.name + '/' + id + '/edit'; +} + +/** + * Get path for creating a new resource. Ex '/users/new'. + * + * @return {String} Url of path to form for creating new resources + */ + +Resource.prototype.newPath = function() { + return '/' + this.name + '/new'; +} + +/** + * Get "collection" url for resource. Ex '/users'. + * + * @return {String} Url of resource "root" + */ + +Resource.prototype.collectionPath = function() { + return '/' + this.name; +} + +/** + * Get "collection" url for resource. Ex '/users/42'. + * + * @return {String} Url of record resource including id + */ + +Resource.prototype.recordPath = function(id) { + return '/' + this.name + '/' + id; +} + /** * Setup http verb methods. */ diff --git a/test/resource.test.js b/test/resource.test.js index c64d009..1c6c9e9 100644 --- a/test/resource.test.js +++ b/test/resource.test.js @@ -143,6 +143,28 @@ describe('app.resource()', function(){ .expect('destroy thread 50 of forum 12', next()) }) + describe('url generation for non-nested resources', function() { + it('should return new urls', function() { + var app = express(); + assert.equal(app.resource('users').newPath(), '/users/new'); + }); + + it('should return edit urls', function() { + var app = express(); + assert.equal(app.resource('users').editPath(42), '/users/42/edit'); + }); + + it('should return collection urls', function() { + var app = express(); + assert.equal(app.resource('users').collectionPath(), '/users'); + }); + + it('should return record urls with ids', function() { + var app = express(); + assert.equal(app.resource('users').recordPath(42), '/users/42'); + }); + }); + describe('"id" option', function(){ it('should allow overriding the default', function(done){ var app = express(); From 73167cd2b7af478415c57ca90cd2f72aa15aa548 Mon Sep 17 00:00:00 2001 From: Sam Gronblom Date: Fri, 15 Nov 2013 14:09:02 +0900 Subject: [PATCH 4/6] Use beforeEach to setup test app --- test/resource.test.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/resource.test.js b/test/resource.test.js index 1c6c9e9..8458dbb 100644 --- a/test/resource.test.js +++ b/test/resource.test.js @@ -144,23 +144,24 @@ describe('app.resource()', function(){ }) describe('url generation for non-nested resources', function() { + var app; + beforeEach(function() { + app = express(); + app.resource('users'); + }); it('should return new urls', function() { - var app = express(); assert.equal(app.resource('users').newPath(), '/users/new'); }); it('should return edit urls', function() { - var app = express(); assert.equal(app.resource('users').editPath(42), '/users/42/edit'); }); it('should return collection urls', function() { - var app = express(); assert.equal(app.resource('users').collectionPath(), '/users'); }); it('should return record urls with ids', function() { - var app = express(); assert.equal(app.resource('users').recordPath(42), '/users/42'); }); }); From c2fa3720f360c83403b88b2454d28bea9b2330c2 Mon Sep 17 00:00:00 2001 From: Sam Gronblom Date: Fri, 15 Nov 2013 15:43:53 +0900 Subject: [PATCH 5/6] Support url generation for arbitrarily nested resources --- index.js | 28 +++++++++++++++++++--------- test/resource.test.js | 32 ++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 11 deletions(-) diff --git a/index.js b/index.js index b444dd5..7c51091 100644 --- a/index.js +++ b/index.js @@ -189,6 +189,8 @@ Resource.prototype.add = function(resource){ + (this.name ? this.name + '/': '') + this.param + '/'; + resource.parent = this; + // re-define previous actions for (var method in resource.routes) { routes = resource.routes[method]; @@ -248,42 +250,50 @@ Resource.prototype.mapDefaultAction = function(key, fn){ /** * Get edit path for resource. Ex '/users/42/edit'. * - * @param {String} id of resource to edit + * @param {String} ids Array of ids for parents if resource is nested * @return {String} Url of resource "root" */ -Resource.prototype.editPath = function(id) { - return '/' + this.name + '/' + id + '/edit'; +Resource.prototype.editPath = function(ids) { + var parentPath = this.parent ? this.parent.recordPath(ids.slice(0, -1)) : ''; + return parentPath + '/' + this.name + '/' + ids[ids.length - 1] + '/edit'; } /** * Get path for creating a new resource. Ex '/users/new'. * + * @param {Array[String]} ids Array of ids for parents if resource is nested * @return {String} Url of path to form for creating new resources */ -Resource.prototype.newPath = function() { - return '/' + this.name + '/new'; +Resource.prototype.newPath = function(ids) { + debugger; + var parentPath = this.parent ? this.parent.recordPath(ids) : ''; + return parentPath + '/' + this.name + '/new'; } /** * Get "collection" url for resource. Ex '/users'. * + * @param {Array[String]} ids Array of ids for parents if resource is nested * @return {String} Url of resource "root" */ -Resource.prototype.collectionPath = function() { - return '/' + this.name; +Resource.prototype.collectionPath = function(ids) { + var parentPath = this.parent ? this.parent.recordPath(ids) : ''; + return parentPath + '/' + this.name; } /** * Get "collection" url for resource. Ex '/users/42'. * + * @param {Array[String]} ids Array of ids for parents if resource is nested * @return {String} Url of record resource including id */ -Resource.prototype.recordPath = function(id) { - return '/' + this.name + '/' + id; +Resource.prototype.recordPath = function(ids) { + var parentPath = this.parent ? this.parent.recordPath(ids.slice(0, -1)) : ''; + return parentPath + '/' + this.name + '/' + ids[ids.length - 1]; } /** diff --git a/test/resource.test.js b/test/resource.test.js index 8458dbb..4ca80a2 100644 --- a/test/resource.test.js +++ b/test/resource.test.js @@ -154,7 +154,7 @@ describe('app.resource()', function(){ }); it('should return edit urls', function() { - assert.equal(app.resource('users').editPath(42), '/users/42/edit'); + assert.equal(app.resource('users').editPath([42]), '/users/42/edit'); }); it('should return collection urls', function() { @@ -162,7 +162,35 @@ describe('app.resource()', function(){ }); it('should return record urls with ids', function() { - assert.equal(app.resource('users').recordPath(42), '/users/42'); + assert.equal(app.resource('users').recordPath([42]), '/users/42'); + }); + }); + + describe('url generation for nested resources', function() { + var app; + var dummyController = {show: function(req, res) { res.send('hello world'); }}; + beforeEach(function() { + app = express(); + var forums = app.resource('forums', dummyController); + var threads = app.resource('threads', dummyController); + var replies = app.resource('replies', dummyController); + forums.add(threads); + threads.add(replies); + }); + it('should return new urls', function() { + assert.equal(app.resource('replies').newPath([1, 2]), '/forums/1/threads/2/replies/new'); + }); + + it('should return edit urls', function() { + assert.equal(app.resource('replies').editPath([1, 2, 3]), '/forums/1/threads/2/replies/3/edit'); + }); + + it('should return collection urls', function() { + assert.equal(app.resource('replies').collectionPath([1, 2]), '/forums/1/threads/2/replies'); + }); + + it('should return record urls with ids', function() { + assert.equal(app.resource('replies').recordPath([1, 2, 3]), '/forums/1/threads/2/replies/3'); }); }); From dc5454eac940d2931074a649b78ff954e3dbec90 Mon Sep 17 00:00:00 2001 From: Sam Gronblom Date: Fri, 15 Nov 2013 15:59:07 +0900 Subject: [PATCH 6/6] Support varargs for the ids for path methods --- index.js | 27 ++++++++++++++++----------- test/resource.test.js | 12 ++++++------ 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/index.js b/index.js index 7c51091..fb4f1c1 100644 --- a/index.js +++ b/index.js @@ -254,9 +254,11 @@ Resource.prototype.mapDefaultAction = function(key, fn){ * @return {String} Url of resource "root" */ -Resource.prototype.editPath = function(ids) { - var parentPath = this.parent ? this.parent.recordPath(ids.slice(0, -1)) : ''; - return parentPath + '/' + this.name + '/' + ids[ids.length - 1] + '/edit'; +Resource.prototype.editPath = function() { + var ids = Array.prototype.slice.call(arguments); + var id = ids.pop(); + var parentPath = this.parent ? this.parent.recordPath.apply(this.parent, ids) : ''; + return parentPath + '/' + this.name + '/' + id + '/edit'; } /** @@ -266,9 +268,9 @@ Resource.prototype.editPath = function(ids) { * @return {String} Url of path to form for creating new resources */ -Resource.prototype.newPath = function(ids) { - debugger; - var parentPath = this.parent ? this.parent.recordPath(ids) : ''; +Resource.prototype.newPath = function() { + var ids = Array.prototype.slice.call(arguments); + var parentPath = this.parent ? this.parent.recordPath.apply(this.parent, ids) : ''; return parentPath + '/' + this.name + '/new'; } @@ -279,8 +281,9 @@ Resource.prototype.newPath = function(ids) { * @return {String} Url of resource "root" */ -Resource.prototype.collectionPath = function(ids) { - var parentPath = this.parent ? this.parent.recordPath(ids) : ''; +Resource.prototype.collectionPath = function() { + var ids = Array.prototype.slice.call(arguments); + var parentPath = this.parent ? this.parent.recordPath.apply(this.parent, ids) : ''; return parentPath + '/' + this.name; } @@ -291,9 +294,11 @@ Resource.prototype.collectionPath = function(ids) { * @return {String} Url of record resource including id */ -Resource.prototype.recordPath = function(ids) { - var parentPath = this.parent ? this.parent.recordPath(ids.slice(0, -1)) : ''; - return parentPath + '/' + this.name + '/' + ids[ids.length - 1]; +Resource.prototype.recordPath = function() { + var ids = Array.prototype.slice.call(arguments); + var id = ids.pop(); + var parentPath = this.parent ? this.parent.recordPath.apply(this.parent, ids) : ''; + return parentPath + '/' + this.name + '/' + id; } /** diff --git a/test/resource.test.js b/test/resource.test.js index 4ca80a2..5725a31 100644 --- a/test/resource.test.js +++ b/test/resource.test.js @@ -154,7 +154,7 @@ describe('app.resource()', function(){ }); it('should return edit urls', function() { - assert.equal(app.resource('users').editPath([42]), '/users/42/edit'); + assert.equal(app.resource('users').editPath(42), '/users/42/edit'); }); it('should return collection urls', function() { @@ -162,7 +162,7 @@ describe('app.resource()', function(){ }); it('should return record urls with ids', function() { - assert.equal(app.resource('users').recordPath([42]), '/users/42'); + assert.equal(app.resource('users').recordPath(42), '/users/42'); }); }); @@ -178,19 +178,19 @@ describe('app.resource()', function(){ threads.add(replies); }); it('should return new urls', function() { - assert.equal(app.resource('replies').newPath([1, 2]), '/forums/1/threads/2/replies/new'); + assert.equal(app.resource('replies').newPath(1, 2), '/forums/1/threads/2/replies/new'); }); it('should return edit urls', function() { - assert.equal(app.resource('replies').editPath([1, 2, 3]), '/forums/1/threads/2/replies/3/edit'); + assert.equal(app.resource('replies').editPath(1, 2, 3), '/forums/1/threads/2/replies/3/edit'); }); it('should return collection urls', function() { - assert.equal(app.resource('replies').collectionPath([1, 2]), '/forums/1/threads/2/replies'); + assert.equal(app.resource('replies').collectionPath(1, 2), '/forums/1/threads/2/replies'); }); it('should return record urls with ids', function() { - assert.equal(app.resource('replies').recordPath([1, 2, 3]), '/forums/1/threads/2/replies/3'); + assert.equal(app.resource('replies').recordPath(1, 2, 3), '/forums/1/threads/2/replies/3'); }); });