Skip to content

Commit

Permalink
Merge pull request #7333 from Automattic/5.4
Browse files Browse the repository at this point in the history
5.4
  • Loading branch information
vkarpov15 committed Dec 14, 2018
2 parents 95d55f8 + 3a18679 commit bd5c04d
Show file tree
Hide file tree
Showing 42 changed files with 2,134 additions and 215 deletions.
40 changes: 33 additions & 7 deletions docs/populate.jade
Original file line number Diff line number Diff line change
Expand Up @@ -444,21 +444,19 @@ block content

<h3 id="populate-virtuals"><a href="#populate-virtuals">Populate Virtuals</a></h3>

_New in 4.5.0_

So far you've only populated based on the `_id` field. However, that's
sometimes not the right choice.
In particular, [arrays that grow without bound are a MongoDB anti-pattern](https://docs.mongodb.com/manual/tutorial/model-referenced-one-to-many-relationships-between-documents/).
Using mongoose virtuals, you can define more sophisticated relationships
between documents.

```javascript
var PersonSchema = new Schema({
const PersonSchema = new Schema({
name: String,
band: String
});

var BandSchema = new Schema({
const BandSchema = new Schema({
name: String
});
BandSchema.virtual('members', {
Expand All @@ -471,8 +469,8 @@ block content
options: { sort: { name: -1 }, limit: 5 } // Query options, see http://bit.ly/mongoose-query-options
});

var Person = mongoose.model('Person', PersonSchema);
var Band = mongoose.model('Band', BandSchema);
const Person = mongoose.model('Person', PersonSchema);
const Band = mongoose.model('Band', BandSchema);

/**
* Suppose you have 2 bands: "Guns N' Roses" and "Motley Crue"
Expand All @@ -491,7 +489,7 @@ block content

```javascript
// Set `virtuals: true` so `res.json()` works
var BandSchema = new Schema({
const BandSchema = new Schema({
name: String
}, { toJSON: { virtuals: true } });
```
Expand All @@ -515,6 +513,34 @@ block content
});
```

<h3 id="count"><a href="#populate-virtuals">Populate Virtuals: The Count Option</a></h3>

Populate virtuals also support counting the number of documents with
matching `foreignField` as opposed to the documents themselves. Set the
`count` option on your virtual:

```javascript
const PersonSchema = new Schema({
name: String,
band: String
});

const BandSchema = new Schema({
name: String
});
BandSchema.virtual('numMembers', {
ref: 'Person', // The model to use
localField: 'name', // Find people where `localField`
foreignField: 'band', // is equal to `foreignField`
count: true // And only get the number of docs
});

// Later
const doc = await Band.findOne({ name: 'Motley Crue' }).
populate('numMembers');
doc.numMembers; // 2
```

<h3 id="populate-middleware"><a href="#populate-middleware">Populate in Middleware</a></h3>

You can populate in either pre or post [hooks](http://mongoosejs.com/docs/middleware.html). If you want to
Expand Down
24 changes: 19 additions & 5 deletions lib/aggregate.js
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,7 @@ Aggregate.prototype.explain = function(callback) {
}
cb(null, result);
});
});
}, this._model.events);
};

/**
Expand Down Expand Up @@ -823,16 +823,16 @@ Aggregate.prototype.cursor = function(options) {
* @param {Boolean} value
* @return {Aggregate} this
* @api public
* @see mongodb http://mongodb.github.io/node-mongodb-native/2.2/api/Cursor.html#addCursorFlag
* @deprecated Use [`.option()`](api.html#aggregate_Aggregate-option) instead. Note that MongoDB aggregations do **not** support a `noCursorTimeout` option.
*/

Aggregate.prototype.addCursorFlag = function(flag, value) {
Aggregate.prototype.addCursorFlag = util.deprecate(function(flag, value) {
if (!this.options) {
this.options = {};
}
this.options[flag] = value;
return this;
};
}, 'Mongoose: `Aggregate#addCursorFlag()` is deprecated, use `option()` instead');

/**
* Adds a collation
Expand Down Expand Up @@ -963,7 +963,7 @@ Aggregate.prototype.exec = function(callback) {
});
});
});
});
}, model.events);
};

/**
Expand All @@ -982,6 +982,20 @@ Aggregate.prototype.then = function(resolve, reject) {
return this.exec().then(resolve, reject);
};

/**
* Executes the query returning a `Promise` which will be
* resolved with either the doc(s) or rejected with the error.
* Like [`.then()`](#query_Query-then), but only takes a rejection handler.
*
* @param {Function} [reject]
* @return {Promise}
* @api public
*/

Aggregate.prototype.catch = function(reject) {
return this.exec().then(null, reject);
};

/**
* Returns an asyncIterator for use with [`for/await/of` loops](http://bit.ly/async-iterators)
* This function *only* works for `find()` queries.
Expand Down
6 changes: 6 additions & 0 deletions lib/browserDocument.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,12 @@ function Document(obj, schema, fields, skipId, skipInit) {
Document.prototype = Object.create(NodeJSDocument.prototype);
Document.prototype.constructor = Document;

/*!
* ignore
*/

Document.events = new EventEmitter();

/*!
* Browser doc exposes the event emitter API
*/
Expand Down
41 changes: 41 additions & 0 deletions lib/cast/date.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
'use strict';

const assert = require('assert');

module.exports = function castDate(value) {
// Support empty string because of empty form values. Originally introduced
// in https://github.com/Automattic/mongoose/commit/efc72a1898fc3c33a319d915b8c5463a22938dfe
if (value == null || value === '') {
return null;
}

if (value instanceof Date) {
assert.ok(!isNaN(value.valueOf()));

return value;
}

let date;

assert.ok(typeof value !== 'boolean');

if (value instanceof Number || typeof value === 'number') {
date = new Date(value);
} else if (typeof value === 'string' && !isNaN(Number(value)) && (Number(value) >= 275761 || Number(value) < -271820)) {
// string representation of milliseconds take this path
date = new Date(Number(value));
} else if (typeof value.valueOf === 'function') {
// support for moment.js. This is also the path strings will take because
// strings have a `valueOf()`
date = new Date(value.valueOf());
} else {
// fallback
date = new Date(value);
}

if (!isNaN(date.valueOf())) {
return date;
}

assert.ok(false);
};
36 changes: 36 additions & 0 deletions lib/cast/decimal128.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use strict';

const Decimal128Type = require('../types/decimal128');
const assert = require('assert');

module.exports = function castDecimal128(value) {
if (value == null) {
return value;
}

if (typeof value === 'object' && typeof value.$numberDecimal === 'string') {
return Decimal128Type.fromString(value.$numberDecimal);
}

if (value instanceof Decimal128Type) {
return value;
}

if (typeof value === 'string') {
return Decimal128Type.fromString(value);
}

if (Buffer.isBuffer(value)) {
return new Decimal128Type(value);
}

if (typeof value === 'number') {
return Decimal128Type.fromString(String(value));
}

if (typeof value.valueOf === 'function' && typeof value.valueOf() === 'string') {
return Decimal128Type.fromString(value.valueOf());
}

assert.ok(false);
};
16 changes: 6 additions & 10 deletions lib/cast/number.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

const CastError = require('../error/cast');
const assert = require('assert');

/*!
* Given a value, cast it to a number, or throw a `CastError` if the value
Expand All @@ -9,14 +9,12 @@ const CastError = require('../error/cast');
* @param {Any} value
* @param {String} [path] optional the path to set on the CastError
* @return {Boolean|null|undefined}
* @throws {CastError} if `value` is not one of the allowed values
* @throws {Error} if `value` is not one of the allowed values
* @api private
*/

module.exports = function castNumber(val, path) {
if (isNaN(val)) {
throw new CastError('number', val, path);
}
module.exports = function castNumber(val) {
assert.ok(!isNaN(val));

if (val == null) {
return val;
Expand All @@ -29,9 +27,7 @@ module.exports = function castNumber(val, path) {
val = Number(val);
}

if (isNaN(val)) {
throw new CastError('number', val, path);
}
assert.ok(!isNaN(val));
if (val instanceof Number) {
return val;
}
Expand All @@ -45,5 +41,5 @@ module.exports = function castNumber(val, path) {
return new Number(val);
}

throw new CastError('number', val, path);
assert.ok(false);
};
29 changes: 29 additions & 0 deletions lib/cast/objectid.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict';

const ObjectId = require('../driver').get().ObjectId;
const assert = require('assert');

module.exports = function castObjectId(value) {
if (value == null) {
return value;
}

if (value instanceof ObjectId) {
return value;
}

if (value._id) {
if (value._id instanceof ObjectId) {
return value._id;
}
if (value._id.toString instanceof Function) {
return new ObjectId(value._id.toString());
}
}

if (value.toString instanceof Function) {
return new ObjectId(value.toString());
}

assert.ok(false);
};
8 changes: 8 additions & 0 deletions lib/collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,14 @@ Collection.prototype.findOneAndDelete = function() {
throw new Error('Collection#findOneAndDelete unimplemented by driver');
};

/**
* Abstract method that drivers must implement.
*/

Collection.prototype.findOneAndReplace = function() {
throw new Error('Collection#findOneAndReplace unimplemented by driver');
};

/**
* Abstract method that drivers must implement.
*/
Expand Down
5 changes: 3 additions & 2 deletions lib/cursor/QueryCursor.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ function QueryCursor(query, options) {
const model = query.model;
this._mongooseOptions = {};
this._transforms = [];
this.model = model;
model.hooks.execPre('find', query, () => {
this._transforms = this._transforms.concat(query._transforms.slice());
if (options.transform) {
Expand Down Expand Up @@ -157,7 +158,7 @@ QueryCursor.prototype.close = function(callback) {
this.emit('close');
cb(null);
});
});
}, this.model.events);
};

/**
Expand All @@ -178,7 +179,7 @@ QueryCursor.prototype.next = function(callback) {
}
cb(null, doc);
});
});
}, this.model.events);
};

/**
Expand Down
19 changes: 13 additions & 6 deletions lib/document.js
Original file line number Diff line number Diff line change
Expand Up @@ -594,10 +594,17 @@ Document.prototype.update = function update() {
* @instance
*/

Document.prototype.updateOne = function updateOne() {
const args = utils.args(arguments);
args.unshift({_id: this._id});
return this.constructor.updateOne.apply(this.constructor, args);
Document.prototype.updateOne = function updateOne(doc, options, callback) {
return utils.promiseOrCallback(callback,
cb => this.$__updateOne(doc, options, cb), this.constructor.events);
};

/*!
* ignore
*/

Document.prototype.$__updateOne = function $__updateOne(doc, options, callback) {
return this.constructor.updateOne({ _id: this._id }, doc, options, callback);
};

/**
Expand Down Expand Up @@ -1705,7 +1712,7 @@ Document.prototype.validate = function(options, callback) {

return utils.promiseOrCallback(callback, cb => this.$__validate(function(error) {
cb(error);
}));
}), this.constructor.events);
};

/*!
Expand Down Expand Up @@ -3012,7 +3019,7 @@ Document.prototype.populate = function populate() {
Document.prototype.execPopulate = function(callback) {
return utils.promiseOrCallback(callback, cb => {
this.populate(cb);
});
}, this.constructor.events);
};

/**
Expand Down
Loading

0 comments on commit bd5c04d

Please sign in to comment.