From 8287161096753fb3c5be6921742585dac0215a89 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 30 Sep 2022 11:39:15 -0400 Subject: [PATCH] feat(schema): add `alias()` method that makes it easier to define multiple aliases for a given path Fix #12368 --- lib/schema.js | 68 +++++++++++++++++++++++++++++++++++++-------- test/schema.test.js | 14 ++++++++++ 2 files changed, 71 insertions(+), 11 deletions(-) diff --git a/lib/schema.js b/lib/schema.js index c07d40bef1c..153f36a8d56 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -170,20 +170,25 @@ function Schema(obj, options) { * @api private */ function aliasFields(schema, paths) { - paths = paths || Object.keys(schema.paths); - for (const path of paths) { - const options = get(schema.paths[path], 'options'); - if (options == null) { - continue; - } + for (const path of Object.keys(paths)) { + let alias = null; + if (typeof paths[path] === 'string') { + alias = paths[path]; + } else { + const options = get(schema.paths[path], 'options'); + if (options == null) { + continue; + } - const prop = schema.paths[path].path; - const alias = options.alias; + alias = options.alias; + } if (!alias) { continue; } + const prop = schema.paths[path].path; + if (typeof alias !== 'string') { throw new Error('Invalid value for alias option on ' + prop + ', got ' + alias); } @@ -663,9 +668,50 @@ Schema.prototype.add = function add(obj, prefix) { } } - const addedKeys = Object.keys(obj). - map(key => prefix ? prefix + key : key); - aliasFields(this, addedKeys); + const aliasObj = Object.fromEntries( + Object.entries(obj).map(([key]) => ([prefix + key, null])) + ); + aliasFields(this, aliasObj); + return this; +}; + +/** + * Add an alias for `path`. This means getting or setting the `alias` + * is equivalent to getting or setting the `path`. + * + * #### Example: + * + * const toySchema = new Schema({ n: String }); + * + * // Make 'name' an alias for 'n' + * toySchema.alias('n', 'name'); + * + * const Toy = mongoose.model('Toy', ToySchema); + * const turboMan = new Toy({ n: 'Turbo Man' }); + * + * turboMan.name; // 'Turbo Man' + * turboMan.n; // 'Turbo Man' + * + * turboMan.name = 'Turbo Man Action Figure'; + * turboMan.n; // 'Turbo Man Action Figure' + * + * await turboMan.save(); // Saves { _id: ..., n: 'Turbo Man Action Figure' } + * + * + * @param {string} path real path to alias + * @param {string|string[]} alias the path(s) to use as an alias for `path` + * @return {Schema} the Schema instance + * @api public + */ + +Schema.prototype.alias = function alias(path, alias) { + if (Array.isArray(alias)) { + for (const a of alias) { + this.alias(path, a); + } + return this; + } + aliasFields(this, { [path]: alias }); return this; }; diff --git a/test/schema.test.js b/test/schema.test.js index c09895de3b3..bcb562cbf48 100644 --- a/test/schema.test.js +++ b/test/schema.test.js @@ -2867,4 +2867,18 @@ describe('schema', function() { assert.equal(doc1.domain, mongooseDomain); assert.equal(doc1.domain, doc2.domain); }); + + it('alias (gh-12368)', function() { + const schema = new Schema({ name: String }); + + schema.alias('name', 'otherName'); + assert.equal(schema.aliases['otherName'], 'name'); + assert.ok(schema.virtuals['otherName']); + + schema.alias('name', ['name1', 'name2']); + assert.equal(schema.aliases['name1'], 'name'); + assert.equal(schema.aliases['name2'], 'name'); + assert.ok(schema.virtuals['name1']); + assert.ok(schema.virtuals['name2']); + }); });