From 8287161096753fb3c5be6921742585dac0215a89 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Fri, 30 Sep 2022 11:39:15 -0400 Subject: [PATCH 1/5] 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']); + }); }); From 9c17de03d8cf3acd4c2d0ba12e9ef2b62cae396d Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 3 Oct 2022 15:09:24 -0400 Subject: [PATCH 2/5] Update lib/schema.js Co-authored-by: hasezoey --- lib/schema.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/schema.js b/lib/schema.js index 153f36a8d56..5c6c08ed50a 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -698,8 +698,8 @@ Schema.prototype.add = function add(obj, prefix) { * 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` + * @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 */ From 03ff83fa85ec6471ebe11e04d18b4cccc8df37af Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 3 Oct 2022 15:09:29 -0400 Subject: [PATCH 3/5] Update lib/schema.js Co-authored-by: hasezoey --- lib/schema.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/schema.js b/lib/schema.js index 5c6c08ed50a..de625c39478 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -686,7 +686,7 @@ Schema.prototype.add = function add(obj, prefix) { * // Make 'name' an alias for 'n' * toySchema.alias('n', 'name'); * - * const Toy = mongoose.model('Toy', ToySchema); + * const Toy = mongoose.model('Toy', toySchema); * const turboMan = new Toy({ n: 'Turbo Man' }); * * turboMan.name; // 'Turbo Man' From 0e8e9c4cddcbce125dc8dfe35bd1d5181dc93b1f Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Mon, 3 Oct 2022 15:56:14 -0400 Subject: [PATCH 4/5] feat(schema): allow defining array of aliases in schema as well Fix #12368 --- lib/schema.js | 28 ++++++++++++++++++++++++++++ test/schema.alias.test.js | 30 ++++++++++++++++++++++++++++++ test/schema.test.js | 15 --------------- types/index.d.ts | 6 ++++++ types/schematypes.d.ts | 2 +- 5 files changed, 65 insertions(+), 16 deletions(-) diff --git a/lib/schema.js b/lib/schema.js index 62061f97ed9..9ca0b207e23 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -189,6 +189,34 @@ function aliasFields(schema, paths) { const prop = schema.paths[path].path; + if (Array.isArray(alias)) { + for (const a of alias) { + if (typeof a !== 'string') { + throw new Error('Invalid value for alias option on ' + prop + ', got ' + a); + } + + schema.aliases[a] = prop; + + schema. + virtual(a). + get((function(p) { + return function() { + if (typeof this.get === 'function') { + return this.get(p); + } + return this[p]; + }; + })(prop)). + set((function(p) { + return function(v) { + return this.$set(p, v); + }; + })(prop)); + } + + continue; + } + if (typeof alias !== 'string') { throw new Error('Invalid value for alias option on ' + prop + ', got ' + alias); } diff --git a/test/schema.alias.test.js b/test/schema.alias.test.js index 5cdfdab9306..ca25511e2e5 100644 --- a/test/schema.alias.test.js +++ b/test/schema.alias.test.js @@ -159,4 +159,34 @@ describe('schema alias option', function() { done(); // acquit:ignore:end }); + + it('array of aliases (gh-12368)', function() { + const productSchema = new Schema({ + n: { + type: String, + alias: ['name', 'product_name'] + } + }); + + const Product = db.model('Test', productSchema); + const doc = new Product({}); + + doc['product_name'] = 'Turbo Man'; + assert.equal(doc.n, 'Turbo Man'); + assert.equal(doc.name, 'Turbo Man'); + }); + + it('alias() method (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']); + }); }); diff --git a/test/schema.test.js b/test/schema.test.js index f73aaccce14..36368c58b2d 100644 --- a/test/schema.test.js +++ b/test/schema.test.js @@ -2868,21 +2868,6 @@ describe('schema', function() { 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']); - }); - it('allows defining ObjectIds and Decimal128s using Types.* (gh-12205)', function() { const schema = new Schema({ testId: mongoose.Types.ObjectId, diff --git a/types/index.d.ts b/types/index.d.ts index 300657604d7..9acecd1fe65 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -196,6 +196,12 @@ declare module 'mongoose' { /** Adds key path / schema type pairs to this schema. */ add(obj: SchemaDefinition> | Schema, prefix?: string): this; + /** + * Add an alias for `path`. This means getting or setting the `alias` + * is equivalent to getting or setting the `path`. + */ + alias(path: string, alias: string | string[]): this; + /** * Array of child schemas (from document arrays and single nested subdocs) * and their corresponding compiled models. Each element of the array is diff --git a/types/schematypes.d.ts b/types/schematypes.d.ts index 75e2258a7a4..899660d9fe6 100644 --- a/types/schematypes.d.ts +++ b/types/schematypes.d.ts @@ -57,7 +57,7 @@ declare module 'mongoose' { T | typeof SchemaType | Schema | SchemaDefinition | Function | AnyArray; /** Defines a virtual with the given name that gets/sets this path. */ - alias?: string; + alias?: string | string[]; /** Function or object describing how to validate this schematype. See [validation docs](https://mongoosejs.com/docs/validation.html). */ validate?: SchemaValidator | AnyArray>; From fd2449c01411e96531fd71de7e46cb7415445539 Mon Sep 17 00:00:00 2001 From: Valeri Karpov Date: Wed, 5 Oct 2022 11:56:46 -0400 Subject: [PATCH 5/5] refactor(schema): remove unnecessary conditional re: @hasezoey code review --- lib/schema.js | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/lib/schema.js b/lib/schema.js index 9ca0b207e23..00680194085 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -172,7 +172,7 @@ function Schema(obj, options) { function aliasFields(schema, paths) { for (const path of Object.keys(paths)) { let alias = null; - if (typeof paths[path] === 'string') { + if (paths[path] != null) { alias = paths[path]; } else { const options = get(schema.paths[path], 'options'); @@ -188,7 +188,6 @@ function aliasFields(schema, paths) { } const prop = schema.paths[path].path; - if (Array.isArray(alias)) { for (const a of alias) { if (typeof a !== 'string') { @@ -729,12 +728,6 @@ Schema.prototype.add = function add(obj, prefix) { */ 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; };