You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							206 lines
						
					
					
						
							6.4 KiB
						
					
					
				
			
		
		
	
	
							206 lines
						
					
					
						
							6.4 KiB
						
					
					
				| '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(key, null, model.prototype, null, [key], 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 },
 | |
|       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]) {
 | |
|     throw new Error('Discriminator with name "' + name + '" already exists');
 | |
|   }
 | |
| 
 | |
|   return schema;
 | |
| };
 |