1 | 'use strict';
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | const CastError = require('../error/cast');
|
8 | const EventEmitter = require('events').EventEmitter;
|
9 | const ObjectExpectedError = require('../error/objectExpected');
|
10 | const SchemaSingleNestedOptions = require('../options/SchemaSingleNestedOptions');
|
11 | const SchemaType = require('../schematype');
|
12 | const $exists = require('./operators/exists');
|
13 | const castToNumber = require('./operators/helpers').castToNumber;
|
14 | const discriminator = require('../helpers/model/discriminator');
|
15 | const geospatial = require('./operators/geospatial');
|
16 | const get = require('../helpers/get');
|
17 | const getConstructor = require('../helpers/discriminator/getConstructor');
|
18 | const handleIdOption = require('../helpers/schema/handleIdOption');
|
19 | const internalToObjectOptions = require('../options').internalToObjectOptions;
|
20 |
|
21 | let Subdocument;
|
22 |
|
23 | module.exports = SingleNestedPath;
|
24 |
|
25 | /**
|
26 | * Single nested subdocument SchemaType constructor.
|
27 | *
|
28 | * @param {Schema} schema
|
29 | * @param {String} key
|
30 | * @param {Object} options
|
31 | * @inherits SchemaType
|
32 | * @api public
|
33 | */
|
34 |
|
35 | function SingleNestedPath(schema, path, options) {
|
36 | schema = handleIdOption(schema, options);
|
37 |
|
38 | this.caster = _createConstructor(schema);
|
39 | this.caster.path = path;
|
40 | this.caster.prototype.$basePath = path;
|
41 | this.schema = schema;
|
42 | this.$isSingleNested = true;
|
43 | SchemaType.call(this, path, options, 'Embedded');
|
44 | }
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 | SingleNestedPath.prototype = Object.create(SchemaType.prototype);
|
51 | SingleNestedPath.prototype.constructor = SingleNestedPath;
|
52 | SingleNestedPath.prototype.OptionsConstructor = SchemaSingleNestedOptions;
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 | function _createConstructor(schema, baseClass) {
|
59 |
|
60 | Subdocument || (Subdocument = require('../types/subdocument'));
|
61 |
|
62 | const _embedded = function SingleNested(value, path, parent) {
|
63 | const _this = this;
|
64 |
|
65 | this.$parent = parent;
|
66 | Subdocument.apply(this, arguments);
|
67 |
|
68 | this.$session(this.ownerDocument().$session());
|
69 |
|
70 | if (parent) {
|
71 | parent.on('save', function() {
|
72 | _this.emit('save', _this);
|
73 | _this.constructor.emit('save', _this);
|
74 | });
|
75 |
|
76 | parent.on('isNew', function(val) {
|
77 | _this.isNew = val;
|
78 | _this.emit('isNew', val);
|
79 | _this.constructor.emit('isNew', val);
|
80 | });
|
81 | }
|
82 | };
|
83 |
|
84 | const proto = baseClass != null ? baseClass.prototype : Subdocument.prototype;
|
85 | _embedded.prototype = Object.create(proto);
|
86 | _embedded.prototype.$__setSchema(schema);
|
87 | _embedded.prototype.constructor = _embedded;
|
88 | _embedded.schema = schema;
|
89 | _embedded.$isSingleNested = true;
|
90 | _embedded.events = new EventEmitter();
|
91 | _embedded.prototype.toBSON = function() {
|
92 | return this.toObject(internalToObjectOptions);
|
93 | };
|
94 |
|
95 |
|
96 | for (const i in schema.methods) {
|
97 | _embedded.prototype[i] = schema.methods[i];
|
98 | }
|
99 |
|
100 |
|
101 | for (const i in schema.statics) {
|
102 | _embedded[i] = schema.statics[i];
|
103 | }
|
104 |
|
105 | for (const i in EventEmitter.prototype) {
|
106 | _embedded[i] = EventEmitter.prototype[i];
|
107 | }
|
108 |
|
109 | return _embedded;
|
110 | }
|
111 |
|
112 |
|
113 |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 |
|
121 | SingleNestedPath.prototype.$conditionalHandlers.$geoWithin = function handle$geoWithin(val) {
|
122 | return { $geometry: this.castForQuery(val.$geometry) };
|
123 | };
|
124 |
|
125 |
|
126 |
|
127 |
|
128 |
|
129 | SingleNestedPath.prototype.$conditionalHandlers.$near =
|
130 | SingleNestedPath.prototype.$conditionalHandlers.$nearSphere = geospatial.cast$near;
|
131 |
|
132 | SingleNestedPath.prototype.$conditionalHandlers.$within =
|
133 | SingleNestedPath.prototype.$conditionalHandlers.$geoWithin = geospatial.cast$within;
|
134 |
|
135 | SingleNestedPath.prototype.$conditionalHandlers.$geoIntersects =
|
136 | geospatial.cast$geoIntersects;
|
137 |
|
138 | SingleNestedPath.prototype.$conditionalHandlers.$minDistance = castToNumber;
|
139 | SingleNestedPath.prototype.$conditionalHandlers.$maxDistance = castToNumber;
|
140 |
|
141 | SingleNestedPath.prototype.$conditionalHandlers.$exists = $exists;
|
142 |
|
143 |
|
144 |
|
145 |
|
146 |
|
147 |
|
148 |
|
149 |
|
150 | SingleNestedPath.prototype.cast = function(val, doc, init, priorVal) {
|
151 | if (val && val.$isSingleNested && val.parent === doc) {
|
152 | return val;
|
153 | }
|
154 |
|
155 | if (val != null && (typeof val !== 'object' || Array.isArray(val))) {
|
156 | throw new ObjectExpectedError(this.path, val);
|
157 | }
|
158 |
|
159 | const Constructor = getConstructor(this.caster, val);
|
160 |
|
161 | let subdoc;
|
162 |
|
163 |
|
164 | const parentSelected = get(doc, '$__.selected', {});
|
165 | const path = this.path;
|
166 | const selected = Object.keys(parentSelected).reduce((obj, key) => {
|
167 | if (key.startsWith(path + '.')) {
|
168 | obj[key.substr(path.length + 1)] = parentSelected[key];
|
169 | }
|
170 | return obj;
|
171 | }, {});
|
172 |
|
173 | if (init) {
|
174 | subdoc = new Constructor(void 0, selected, doc);
|
175 | subdoc.init(val);
|
176 | } else {
|
177 | if (Object.keys(val).length === 0) {
|
178 | return new Constructor({}, selected, doc);
|
179 | }
|
180 |
|
181 | return new Constructor(val, selected, doc, undefined, { priorDoc: priorVal });
|
182 | }
|
183 |
|
184 | return subdoc;
|
185 | };
|
186 |
|
187 |
|
188 |
|
189 |
|
190 |
|
191 |
|
192 |
|
193 |
|
194 |
|
195 | SingleNestedPath.prototype.castForQuery = function($conditional, val, options) {
|
196 | let handler;
|
197 | if (arguments.length === 2) {
|
198 | handler = this.$conditionalHandlers[$conditional];
|
199 | if (!handler) {
|
200 | throw new Error('Can\'t use ' + $conditional);
|
201 | }
|
202 | return handler.call(this, val);
|
203 | }
|
204 | val = $conditional;
|
205 | if (val == null) {
|
206 | return val;
|
207 | }
|
208 |
|
209 | if (this.options.runSetters) {
|
210 | val = this._applySetters(val);
|
211 | }
|
212 |
|
213 | const Constructor = getConstructor(this.caster, val);
|
214 | const overrideStrict = options != null && options.strict != null ?
|
215 | options.strict :
|
216 | void 0;
|
217 |
|
218 | try {
|
219 | val = new Constructor(val, overrideStrict);
|
220 | } catch (error) {
|
221 |
|
222 | if (!(error instanceof CastError)) {
|
223 | throw new CastError('Embedded', val, this.path, error, this);
|
224 | }
|
225 | throw error;
|
226 | }
|
227 | return val;
|
228 | };
|
229 |
|
230 |
|
231 |
|
232 |
|
233 |
|
234 |
|
235 |
|
236 | SingleNestedPath.prototype.doValidate = function(value, fn, scope, options) {
|
237 | const Constructor = getConstructor(this.caster, value);
|
238 |
|
239 | if (options && options.skipSchemaValidators) {
|
240 | if (!(value instanceof Constructor)) {
|
241 | value = new Constructor(value, null, scope);
|
242 | }
|
243 | return value.validate(fn);
|
244 | }
|
245 |
|
246 | SchemaType.prototype.doValidate.call(this, value, function(error) {
|
247 | if (error) {
|
248 | return fn(error);
|
249 | }
|
250 | if (!value) {
|
251 | return fn(null);
|
252 | }
|
253 |
|
254 | value.validate(fn);
|
255 | }, scope, options);
|
256 | };
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 | SingleNestedPath.prototype.doValidateSync = function(value, scope, options) {
|
265 | if (!options || !options.skipSchemaValidators) {
|
266 | const schemaTypeError = SchemaType.prototype.doValidateSync.call(this, value, scope);
|
267 | if (schemaTypeError) {
|
268 | return schemaTypeError;
|
269 | }
|
270 | }
|
271 | if (!value) {
|
272 | return;
|
273 | }
|
274 | return value.validateSync();
|
275 | };
|
276 |
|
277 |
|
278 |
|
279 |
|
280 |
|
281 |
|
282 |
|
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 |
|
291 |
|
292 |
|
293 |
|
294 |
|
295 | SingleNestedPath.prototype.discriminator = function(name, schema, value) {
|
296 | schema = discriminator(this.caster, name, schema, value);
|
297 |
|
298 | this.caster.discriminators[name] = _createConstructor(schema, this.caster);
|
299 |
|
300 | return this.caster.discriminators[name];
|
301 | };
|
302 |
|
303 |
|
304 |
|
305 |
|
306 |
|
307 | SingleNestedPath.prototype.clone = function() {
|
308 | const options = Object.assign({}, this.options);
|
309 | const schematype = new this.constructor(this.schema, this.path, options);
|
310 | schematype.validators = this.validators.slice();
|
311 | schematype.caster.discriminators = Object.assign({}, this.caster.discriminators);
|
312 | return schematype;
|
313 | };
|