UNPKG

6.53 kBJavaScriptView Raw
1'use strict';
2
3const modelNamesFromRefPath = require('./helpers/populate/modelNamesFromRefPath');
4const utils = require('./utils');
5
6const modelSymbol = require('./helpers/symbols').modelSymbol;
7
8/**
9 * VirtualType constructor
10 *
11 * This is what mongoose uses to define virtual attributes via `Schema.prototype.virtual`.
12 *
13 * #### Example:
14 *
15 * const fullname = schema.virtual('fullname');
16 * fullname instanceof mongoose.VirtualType // true
17 *
18 * @param {Object} options
19 * @param {String|Function} [options.ref] if `ref` is not nullish, this becomes a [populated virtual](https://mongoosejs.com/docs/populate.html#populate-virtuals)
20 * @param {String|Function} [options.localField] the local field to populate on if this is a populated virtual.
21 * @param {String|Function} [options.foreignField] the foreign field to populate on if this is a populated virtual.
22 * @param {Boolean} [options.justOne=false] by default, a populated virtual is an array. If you set `justOne`, the populated virtual will be a single doc or `null`.
23 * @param {Boolean} [options.getters=false] if you set this to `true`, Mongoose will call any custom getters you defined on this virtual
24 * @param {Boolean} [options.count=false] if you set this to `true`, `populate()` will set this virtual to the number of populated documents, as opposed to the documents themselves, using [`Query#countDocuments()`](https://mongoosejs.com/docs/api/query.html#Query.prototype.countDocuments())
25 * @param {Object|Function} [options.match=null] add an extra match condition to `populate()`
26 * @param {Number} [options.limit=null] add a default `limit` to the `populate()` query
27 * @param {Number} [options.skip=null] add a default `skip` to the `populate()` query
28 * @param {Number} [options.perDocumentLimit=null] For legacy reasons, `limit` with `populate()` may give incorrect results because it only executes a single query for every document being populated. If you set `perDocumentLimit`, Mongoose will ensure correct `limit` per document by executing a separate query for each document to `populate()`. For example, `.find().populate({ path: 'test', perDocumentLimit: 2 })` will execute 2 additional queries if `.find()` returns 2 documents.
29 * @param {Object} [options.options=null] Additional options like `limit` and `lean`.
30 * @param {String} name
31 * @api public
32 */
33
34function VirtualType(options, name) {
35 this.path = name;
36 this.getters = [];
37 this.setters = [];
38 this.options = Object.assign({}, options);
39}
40
41/**
42 * If no getters/setters, add a default
43 *
44 * @api private
45 */
46
47VirtualType.prototype._applyDefaultGetters = function() {
48 if (this.getters.length > 0 || this.setters.length > 0) {
49 return;
50 }
51
52 const path = this.path;
53 const internalProperty = '$' + path;
54 this.getters.push(function() {
55 return this.$locals[internalProperty];
56 });
57 this.setters.push(function(v) {
58 this.$locals[internalProperty] = v;
59 });
60};
61
62/*!
63 * ignore
64 */
65
66VirtualType.prototype.clone = function() {
67 const clone = new VirtualType(this.options, this.path);
68 clone.getters = [].concat(this.getters);
69 clone.setters = [].concat(this.setters);
70 return clone;
71};
72
73/**
74 * Adds a custom getter to this virtual.
75 *
76 * Mongoose calls the getter function with the below 3 parameters.
77 *
78 * - `value`: the value returned by the previous getter. If there is only one getter, `value` will be `undefined`.
79 * - `virtual`: the virtual object you called `.get()` on.
80 * - `doc`: the document this virtual is attached to. Equivalent to `this`.
81 *
82 * #### Example:
83 *
84 * const virtual = schema.virtual('fullname');
85 * virtual.get(function(value, virtual, doc) {
86 * return this.name.first + ' ' + this.name.last;
87 * });
88 *
89 * @param {Function} fn
90 * @return {VirtualType} this
91 * @api public
92 */
93
94VirtualType.prototype.get = function(fn) {
95 this.getters.push(fn);
96 return this;
97};
98
99/**
100 * Adds a custom setter to this virtual.
101 *
102 * Mongoose calls the setter function with the below 3 parameters.
103 *
104 * - `value`: the value being set.
105 * - `virtual`: the virtual object you're calling `.set()` on.
106 * - `doc`: the document this virtual is attached to. Equivalent to `this`.
107 *
108 * #### Example:
109 *
110 * const virtual = schema.virtual('fullname');
111 * virtual.set(function(value, virtual, doc) {
112 * const parts = value.split(' ');
113 * this.name.first = parts[0];
114 * this.name.last = parts[1];
115 * });
116 *
117 * const Model = mongoose.model('Test', schema);
118 * const doc = new Model();
119 * // Calls the setter with `value = 'Jean-Luc Picard'`
120 * doc.fullname = 'Jean-Luc Picard';
121 * doc.name.first; // 'Jean-Luc'
122 * doc.name.last; // 'Picard'
123 *
124 * @param {Function} fn
125 * @return {VirtualType} this
126 * @api public
127 */
128
129VirtualType.prototype.set = function(fn) {
130 this.setters.push(fn);
131 return this;
132};
133
134/**
135 * Applies getters to `value`.
136 *
137 * @param {Object} value
138 * @param {Document} doc The document this virtual is attached to
139 * @return {Any} the value after applying all getters
140 * @api public
141 */
142
143VirtualType.prototype.applyGetters = function(value, doc) {
144 if (utils.hasUserDefinedProperty(this.options, ['ref', 'refPath']) &&
145 doc.$$populatedVirtuals &&
146 doc.$$populatedVirtuals.hasOwnProperty(this.path)) {
147 value = doc.$$populatedVirtuals[this.path];
148 }
149
150 let v = value;
151 for (const getter of this.getters) {
152 v = getter.call(doc, v, this, doc);
153 }
154 return v;
155};
156
157/**
158 * Applies setters to `value`.
159 *
160 * @param {Object} value
161 * @param {Document} doc
162 * @return {Any} the value after applying all setters
163 * @api public
164 */
165
166VirtualType.prototype.applySetters = function(value, doc) {
167 let v = value;
168 for (const setter of this.setters) {
169 v = setter.call(doc, v, this, doc);
170 }
171 return v;
172};
173
174/**
175 * Get the names of models used to populate this model given a doc
176 *
177 * @param {Document} doc
178 * @return {Array<string> | null}
179 * @api private
180 */
181
182VirtualType.prototype._getModelNamesForPopulate = function _getModelNamesForPopulate(doc) {
183 if (this.options.refPath) {
184 return modelNamesFromRefPath(this.options.refPath, doc, this.path);
185 }
186
187 let normalizedRef = null;
188 if (typeof this.options.ref === 'function' && !this.options.ref[modelSymbol]) {
189 normalizedRef = this.options.ref.call(doc, doc);
190 } else {
191 normalizedRef = this.options.ref;
192 }
193 if (normalizedRef != null && !Array.isArray(normalizedRef)) {
194 return [normalizedRef];
195 }
196
197 return normalizedRef;
198};
199
200/*!
201 * exports
202 */
203
204module.exports = VirtualType;