123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- 'use strict';
- const Mixed = require('../../schema/mixed');
- const defineKey = require('../document/compile').defineKey;
- const get = require('../get');
- const utils = require('../../utils');
- const CUSTOMIZABLE_DISCRIMINATOR_OPTIONS = {
- toJSON: true,
- toObject: true,
- _id: true,
- id: true
- };
- /*!
- * ignore
- */
- module.exports = function discriminator(model, name, schema, tiedValue, applyPlugins) {
- if (!(schema && schema.instanceOfSchema)) {
- throw new Error('You must pass a valid discriminator Schema');
- }
- if (model.schema.discriminatorMapping &&
- !model.schema.discriminatorMapping.isRoot) {
- throw new Error('Discriminator "' + name +
- '" can only be a discriminator of the root model');
- }
- if (applyPlugins) {
- const applyPluginsToDiscriminators = get(model.base,
- 'options.applyPluginsToDiscriminators', false);
- // Even if `applyPluginsToDiscriminators` isn't set, we should still apply
- // global plugins to schemas embedded in the discriminator schema (gh-7370)
- model.base._applyPlugins(schema, {
- skipTopLevel: !applyPluginsToDiscriminators
- });
- }
- const key = model.schema.options.discriminatorKey;
- const existingPath = model.schema.path(key);
- if (existingPath != null) {
- if (!utils.hasUserDefinedProperty(existingPath.options, 'select')) {
- existingPath.options.select = true;
- }
- existingPath.options.$skipDiscriminatorCheck = true;
- } else {
- const baseSchemaAddition = {};
- baseSchemaAddition[key] = {
- default: void 0,
- select: true,
- $skipDiscriminatorCheck: true
- };
- baseSchemaAddition[key][model.schema.options.typeKey] = String;
- model.schema.add(baseSchemaAddition);
- defineKey({
- prop: key,
- prototype: model.prototype,
- options: model.schema.options
- });
- }
- if (schema.path(key) && schema.path(key).options.$skipDiscriminatorCheck !== true) {
- throw new Error('Discriminator "' + name +
- '" cannot have field with name "' + key + '"');
- }
- let value = name;
- if ((typeof tiedValue === 'string' && tiedValue.length) || tiedValue != null) {
- value = tiedValue;
- }
- function merge(schema, baseSchema) {
- // Retain original schema before merging base schema
- schema._baseSchema = baseSchema;
- if (baseSchema.paths._id &&
- baseSchema.paths._id.options &&
- !baseSchema.paths._id.options.auto) {
- schema.remove('_id');
- }
- // Find conflicting paths: if something is a path in the base schema
- // and a nested path in the child schema, overwrite the base schema path.
- // See gh-6076
- const baseSchemaPaths = Object.keys(baseSchema.paths);
- const conflictingPaths = [];
- for (const path of baseSchemaPaths) {
- if (schema.nested[path]) {
- conflictingPaths.push(path);
- continue;
- }
- if (path.indexOf('.') === -1) {
- continue;
- }
- const sp = path.split('.').slice(0, -1);
- let cur = '';
- for (const piece of sp) {
- cur += (cur.length ? '.' : '') + piece;
- if (schema.paths[cur] instanceof Mixed ||
- schema.singleNestedPaths[cur] instanceof Mixed) {
- conflictingPaths.push(path);
- }
- }
- }
- utils.merge(schema, baseSchema, {
- isDiscriminatorSchemaMerge: true,
- omit: { discriminators: true, base: true, _applyDiscriminators: true },
- omitNested: conflictingPaths.reduce((cur, path) => {
- cur['tree.' + path] = true;
- return cur;
- }, {})
- });
- // Clean up conflicting paths _after_ merging re: gh-6076
- for (const conflictingPath of conflictingPaths) {
- delete schema.paths[conflictingPath];
- }
- // Rebuild schema models because schemas may have been merged re: #7884
- schema.childSchemas.forEach(obj => {
- obj.model.prototype.$__setSchema(obj.schema);
- });
- const obj = {};
- obj[key] = {
- default: value,
- select: true,
- set: function(newName) {
- if (newName === value || (Array.isArray(value) && utils.deepEqual(newName, value))) {
- return value;
- }
- throw new Error('Can\'t set discriminator key "' + key + '"');
- },
- $skipDiscriminatorCheck: true
- };
- obj[key][schema.options.typeKey] = existingPath ? existingPath.options[schema.options.typeKey] : String;
- schema.add(obj);
- schema.discriminatorMapping = { key: key, value: value, isRoot: false };
- if (baseSchema.options.collection) {
- schema.options.collection = baseSchema.options.collection;
- }
- const toJSON = schema.options.toJSON;
- const toObject = schema.options.toObject;
- const _id = schema.options._id;
- const id = schema.options.id;
- const keys = Object.keys(schema.options);
- schema.options.discriminatorKey = baseSchema.options.discriminatorKey;
- for (const _key of keys) {
- if (!CUSTOMIZABLE_DISCRIMINATOR_OPTIONS[_key]) {
- // Special case: compiling a model sets `pluralization = true` by default. Avoid throwing an error
- // for that case. See gh-9238
- if (_key === 'pluralization' && schema.options[_key] == true && baseSchema.options[_key] == null) {
- continue;
- }
- if (!utils.deepEqual(schema.options[_key], baseSchema.options[_key])) {
- throw new Error('Can\'t customize discriminator option ' + _key +
- ' (can only modify ' +
- Object.keys(CUSTOMIZABLE_DISCRIMINATOR_OPTIONS).join(', ') +
- ')');
- }
- }
- }
- schema.options = utils.clone(baseSchema.options);
- if (toJSON) schema.options.toJSON = toJSON;
- if (toObject) schema.options.toObject = toObject;
- if (typeof _id !== 'undefined') {
- schema.options._id = _id;
- }
- schema.options.id = id;
- schema.s.hooks = model.schema.s.hooks.merge(schema.s.hooks);
- schema.plugins = Array.prototype.slice.call(baseSchema.plugins);
- schema.callQueue = baseSchema.callQueue.concat(schema.callQueue);
- delete schema._requiredpaths; // reset just in case Schema#requiredPaths() was called on either schema
- }
- // merges base schema into new discriminator schema and sets new type field.
- merge(schema, model.schema);
- if (!model.discriminators) {
- model.discriminators = {};
- }
- if (!model.schema.discriminatorMapping) {
- model.schema.discriminatorMapping = { key: key, value: null, isRoot: true };
- }
- if (!model.schema.discriminators) {
- model.schema.discriminators = {};
- }
- model.schema.discriminators[name] = schema;
- if (model.discriminators[name] && !schema.options.overwriteModels) {
- throw new Error('Discriminator with name "' + name + '" already exists');
- }
- return schema;
- };
|