diff --git a/lib/model.js b/lib/model.js index 86695ad84ca..bd548293190 100644 --- a/lib/model.js +++ b/lib/model.js @@ -1341,9 +1341,9 @@ Model.syncIndexes = function syncIndexes(options, callback) { /** * Deletes all indexes that aren't defined in this model's schema. Used by * `syncIndexes()`. - * + * * The returned promise resolves to a list of the dropped indexes' names as an array - * + * * @param {Function} [callback] optional callback * @return {Promise|undefined} Returns `undefined` if callback is specified, returns a promise if no callback. * @api public diff --git a/lib/schema.js b/lib/schema.js index 1aac5594522..f2f04bc70f6 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -6,6 +6,7 @@ const EventEmitter = require('events').EventEmitter; const Kareem = require('kareem'); +const MongooseError = require('./error/mongooseError'); const SchemaType = require('./schematype'); const SchemaTypeOptions = require('./options/SchemaTypeOptions'); const VirtualType = require('./virtualtype'); @@ -332,6 +333,49 @@ Schema.prototype.clone = function() { return s; }; +/** + * Returns a new schema that has the picked `paths` from this schema. + * + * This method is analagous to [Lodash's `pick()` function](https://lodash.com/docs/4.17.15#pick) for Mongoose schemas. + * + * ####Example: + * + * const schema = Schema({ name: String, age: Number }); + * // Creates a new schema with the same `name` path as `schema`, + * // but no `age` path. + * const newSchema = schema.pick(['name']); + * + * newSchema.path('name'); // SchemaString { ... } + * newSchema.path('age'); // undefined + * + * @param {Array} paths list of paths to pick + * @param {Object} [options] options to pass to the schema constructor. Defaults to `this.options` if not set. + * @return {Schema} + * @api public + */ + +Schema.prototype.pick = function(paths, options) { + const newSchema = new Schema({}, options || this.options); + if (!Array.isArray(paths)) { + throw new MongooseError('Schema#pick() only accepts an array argument, ' + + 'got "' + typeof paths + '"'); + } + + for (const path of paths) { + if (this.nested[path]) { + newSchema.add({ [path]: get(this.tree, path) }); + } else { + const schematype = this.path(path); + if (schematype == null) { + throw new MongooseError('Path `' + path + '` is not in the schema'); + } + newSchema.add({ [path]: schematype }); + } + } + + return newSchema; +}; + /** * Returns default options for this schema, merged with `options`. * diff --git a/test/schema.test.js b/test/schema.test.js index 30ca6a7c59d..b8b1d1dab51 100644 --- a/test/schema.test.js +++ b/test/schema.test.js @@ -1704,8 +1704,10 @@ describe('schema', function() { }); it('removes a single path', function(done) { + assert.ok(this.schema.paths.a); this.schema.remove('a'); assert.strictEqual(this.schema.path('a'), undefined); + assert.strictEqual(this.schema.paths.a, void 0); done(); }); @@ -2168,4 +2170,79 @@ describe('schema', function() { assert.equal(newSchema.path('title').options.type, String); }); + + describe('pick() (gh-8207)', function() { + it('works with nested paths', function() { + const schema = Schema({ + name: { + first: { + type: String, + required: true + }, + last: { + type: String, + required: true + } + }, + age: { + type: Number, + index: true + } + }); + assert.ok(schema.path('name.first')); + assert.ok(schema.path('name.last')); + + let newSchema = schema.pick(['age']); + assert.ok(!newSchema.path('name.first')); + assert.ok(newSchema.path('age')); + assert.ok(newSchema.path('age').index); + + newSchema = schema.pick(['name']); + assert.ok(newSchema.path('name.first')); + assert.ok(newSchema.path('name.first').required); + assert.ok(newSchema.path('name.last')); + assert.ok(newSchema.path('name.last').required); + assert.ok(!newSchema.path('age')); + + newSchema = schema.pick(['name.first']); + assert.ok(newSchema.path('name.first')); + assert.ok(newSchema.path('name.first').required); + assert.ok(!newSchema.path('name.last')); + assert.ok(!newSchema.path('age')); + }); + + it('with single nested paths', function() { + const schema = Schema({ + name: Schema({ + first: { + type: String, + required: true + }, + last: { + type: String, + required: true + } + }), + age: { + type: Number, + index: true + } + }); + assert.ok(schema.path('name.first')); + assert.ok(schema.path('name.last')); + + let newSchema = schema.pick(['name']); + assert.ok(newSchema.path('name.first')); + assert.ok(newSchema.path('name.first').required); + assert.ok(newSchema.path('name.last')); + assert.ok(newSchema.path('name.last').required); + assert.ok(!newSchema.path('age')); + + newSchema = schema.pick(['name.first']); + assert.ok(newSchema.path('name.first')); + assert.ok(newSchema.path('name.first').required); + assert.ok(!newSchema.path('name.last')); + assert.ok(!newSchema.path('age')); + }); + }); });