From d447515e77ff58a96af8ae8fa3bf5ac4fa536c61 Mon Sep 17 00:00:00 2001 From: Matt Roman Date: Mon, 4 Nov 2013 08:33:06 -0500 Subject: [PATCH 1/2] Makes the lean call optional so that read requests can return virtuals. Default is on. --- lib/express-restify-mongoose.js | 71 +++++++++++++++++++-------------- package.json | 2 +- test/setup.js | 16 ++++++-- test/test.js | 63 ++++++++++++++++++++++++++--- 4 files changed, 110 insertions(+), 42 deletions(-) diff --git a/lib/express-restify-mongoose.js b/lib/express-restify-mongoose.js index dd170aab..b01e06b6 100644 --- a/lib/express-restify-mongoose.js +++ b/lib/express-restify-mongoose.js @@ -34,23 +34,29 @@ function filterjson(json, excludedkeys) { } function filterItem(item, model, excludedkeys) { - // just deleting the excluded keys from item does - // not modify the object. therefore we build a copy - var excludedarr = excludedkeys.split(','); - var it = {}; - - for (var key in item) { - if ((model.schema.options.versionKey && - key === model.schema.options.versionKey) || + // just deleting the excluded keys from item does + // not modify the object. therefore we build a copy + var excludedarr = excludedkeys.split(','); + var it = {}; + + for (var key in item) { + if ((model.schema.options.versionKey && + key === model.schema.options.versionKey) || key === '_id') { - it[key] = item[key]; - } else if (model.schema.paths.hasOwnProperty(key)) { - if (excludedarr.indexOf(key) === -1) { - it[key] = item[key]; - } - } - } - return it; + it[key] = item[key]; + } else if (model.schema.paths.hasOwnProperty(key)) { + if (excludedarr.indexOf(key) === -1) { + it[key] = item[key]; + } + } + } + return it; +} + +function filterItems(items, model, excludedKeys) { + return items.map(function (item) { + return filterItem(item, model, excludedKeys); + }); } function outputExpress(res, result) { @@ -63,10 +69,10 @@ function outputRestify(res, result) { } var restify = function (app, model, options) { - var postProcess, exclude, + var postProcess, exclude, lean, usingExpress = true, queryOptions = { - protected: ['skip', 'limit', 'sort', 'populate', 'select'], + protected: ['skip', 'limit', 'sort', 'populate', 'select', 'lean'], current: {} }; @@ -75,6 +81,7 @@ var restify = function (app, model, options) { options.version = options.version || '/v1'; postProcess = options.postProcess || function () {}; exclude = options.exclude; + lean = typeof options.lean === 'undefined' ? true : options.lean; if (options.plural !== false) { options.plural = true; @@ -103,7 +110,7 @@ var restify = function (app, model, options) { } options.middleware.unshift(cleanQuery); - + var outputFn = options.restify ? outputRestify : outputExpress; app.delete = app.del; @@ -195,9 +202,9 @@ var restify = function (app, model, options) { if (model.schema.options.versionKey) { delete req.body[model.schema.options.versionKey]; } - + var key, path; - + if (Array.isArray(req.body)) { for (var i = 0; i < req.body.length; ++i) { for (key in req.body[i]) { @@ -227,7 +234,7 @@ var restify = function (app, model, options) { res.send(400, JSON.stringify(err)); } else { var result = null; - + if (Array.isArray(req.body)) { var items = Array.prototype.slice.call(arguments, 1); @@ -242,7 +249,7 @@ var restify = function (app, model, options) { } else { result = exclude ? filterItem(item, model, exclude) : item; } - + outputFn(res, result); next(); } @@ -271,9 +278,9 @@ var restify = function (app, model, options) { if (err) { res.send(404); } else { - if (exclude) { - item = filterItem(item, model, exclude); - } + if (exclude) { + item = filterItem(item, model, exclude); + } outputFn(res, item); next(); } @@ -284,12 +291,14 @@ var restify = function (app, model, options) { write_middleware.push(ensureContentType); app.get(uri_items, options.middleware, function (req, res, next) { - buildQuery(model.find(), req.query).lean().exec(function (err, items) { + buildQuery(model.find(), req.query).lean(lean) + .exec(function (err, items) { if (err) { res.send(400, 'Bad request'); } else { if (exclude) { - items = filterjson(items, exclude); + items = lean ? filterjson(items, exclude) : + filterItems(items, model, exclude); } outputFn(res, items); next(); @@ -329,9 +338,9 @@ var restify = function (app, model, options) { if (err || !item) { res.send(404); } else { - if (exclude) { - item = filterItem(item, model, exclude); - } + if (exclude) { + item = filterItem(item, model, exclude); + } outputFn(res, item); next(); } diff --git a/package.json b/package.json index a8b78a1b..7413774e 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "readmeFilename": "README.md", "dependencies": { "express": "~3.3.5", - "mongoose": "~3.6.13" + "mongoose": "~3.8.0" }, "devDependencies": { "jshint": "~2.1.4", diff --git a/test/setup.js b/test/setup.js index 09e0c3fb..17d86c0d 100644 --- a/test/setup.js +++ b/test/setup.js @@ -2,16 +2,24 @@ var mongoose = require('mongoose'), Schema = mongoose.Schema; var assert = require('assertmessage'); +var opts = { + toObject: { virtuals: true }, + toJSON: { virtuals: true } +}; var Customer = new Schema({ name: { type: String, required: true }, comment: { type: String } -}); +}, opts); var Invoice = new Schema({ - customer: { type: Schema.Types.ObjectId, ref: 'Customer' }, - amount: { type: Number } + customer: { type: Schema.Types.ObjectId, ref: 'Customer' }, + amount: { type: Number } }, { versionKey: '__version' +}, opts); + +Customer.virtual('info').get(function () { + return this.name + ' is awesome'; }); var setup = module.exports = function () { @@ -40,4 +48,4 @@ var setup = module.exports = function () { }; setup.customerModel = {}; -setup.invoiceModel = {}; \ No newline at end of file +setup.invoiceModel = {}; diff --git a/test/test.js b/test/test.js index b81501d6..741b06c0 100644 --- a/test/test.js +++ b/test/test.js @@ -32,15 +32,21 @@ function Restify() { [Express, Restify].each(function (createFn) { describe(createFn.name, function () { - describe('General', function () { + describe.only('General', function () { var savedCustomer, savedInvoice, server, app = createFn(); setup(); before(function (done) { - erm.serve(app, setup.customerModel, { restify: app.isRestify }); - erm.serve(app, setup.invoiceModel, { restify: app.isRestify }); + erm.serve(app, setup.customerModel, { + restify: app.isRestify, + lean: false + }); + erm.serve(app, setup.invoiceModel, { + restify: app.isRestify, + lean: false + }); server = app.listen(testPort, done); }); @@ -50,7 +56,7 @@ function Restify() { } server.close(done); }); - + it('200 GET Customers should return no objects', function (done) { request.get({ url: util.format('%s/api/v1/Customers', testUrl), @@ -120,7 +126,7 @@ function Restify() { done(); }); }); - + it('200 GET Customers/count should return 3', function (done) { request.get({ url: util.format('%s/api/v1/Customers/count', testUrl), @@ -131,7 +137,7 @@ function Restify() { done(); }); }); - + it('200 POST Invoice using pre-defined version', function (done) { request.post({ url: util.format('%s/api/v1/Invoices', testUrl), @@ -260,6 +266,7 @@ function Restify() { it('200 PUT Customers/:id', function (done) { savedCustomer.name = 'Test 2'; + savedCustomer.info = savedCustomer.name + ' is awesome'; request.put({ url: util.format('%s/api/v1/Customers/%s', testUrl, savedCustomer._id), @@ -306,6 +313,50 @@ function Restify() { }); }); + describe('Return virtuals', function () { + var savedCustomer, server, + app = createFn(); + + setup(); + + before(function (done) { + erm.serve(app, setup.customerModel, { + lean: false, + restify: app.isRestify + }); + server = app.listen(testPort, function () { + request.post({ + url: util.format('%s/api/v1/Customers', testUrl), + json: { + name: 'Bob' + } + }, function (err, res, body) { + savedCustomer = body; + done(); + }); + }); + }); + + after(function (done) { + if (app.close) { + return app.close(done); + } + server.close(done); + }); + + it('200 GET Customers', function (done) { + var info = savedCustomer.name + ' is awesome'; + request.get({ + url: util.format('%s/api/v1/Customers', testUrl), + json: true + }, function (err, res, body) { + assert.equal(res.statusCode, 200, 'Wrong status code'); + assert.equal(body[0].info, info, 'info is not defined'); + done(); + }); + }); + }); + describe('Excluded comment field', function () { var savedCustomer, server, app = createFn(); From 02a1537772461fc211202fc9de78b4a23016f360 Mon Sep 17 00:00:00 2001 From: Matt Roman Date: Mon, 4 Nov 2013 10:55:25 -0500 Subject: [PATCH 2/2] Updates readme to add lean option --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 05780940..4441b8ae 100644 --- a/README.md +++ b/README.md @@ -101,7 +101,9 @@ serve(app, model, [options]) * postProcess - A middleware to be called after the response has been sent. It is only executed on success. If an error is sent to the client, this is not executed. - + * lean - If ```false```, will not convert to returned values to plain old javascript + objects. This is bad for performance, but it allows for returning virtuals, getters and setters. + ## Contributors * Enric León (https://github.com/nothingbuttumbleweed) * David Higginbotham (https://github.com/dhigginbotham)