-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
3.8 Release Notes
Mongoose 3.8 brings with it significant additions to our GeoJSON, Promise, and Query building support as well as dozens of bug fixes and lastly, some important deprecations and changes you should be aware of as detailed below.
In case you missed the 3.7 release announcement, the Mongoose project has moved to a release versioning approach similar to the MongoDB and Node.js projects. All odd numbered minor versions are to be considered unstable where you should expect larger, potentially breaking changes to occur during iteration towards the next even numbered (stable) release.
We feel this approach provides a cleaner, more consistent way to describe and iterate on new code branches than tagging pre-releases as "beta0", "beta1", etc.
Summary:
- X.OddNumber.X is an unstable release
- X.EvenNumber.X is a stable release
For example: 3.7.0, 3.7.1 and 4.11.398 are unstable, whereas 3.8.0, 3.8.1, and 3.20.1 are stable.
For more information on this versioning scheme, please see the MongoDB example.
Mongoose 3.8 brings with it some significant changes and improvements under the hood:
- added: geoSearch support
- added: geoNear support
- added: $geoWithin support (important if running MongoDB < 2.4)
- added: connection pool sharing
- added: model.update() now has an
overwrite
option - added: MongooseBuffer#subtype()
- added:
awaitdata
support - added: stand-alone custom base queries support
- added; query.remove(conditions, callback) support
- added; GeoJSON support for query.near()
- added: aggregation builder
- added: custom error message support for built-in validators
- added: option to disable collection name pluralization
- added: better support for Promises
- changed: no longer support node 0.6.x
- changed: Model.aggregate() API
- changed: new query builder (mquery)
- changed: document.remove callback arguments
- changed: document.toObject() now respects child schema transform options
- changed: document.toJSON() now respects child schema and populated documents toJSON options
- changed: disabling
safe
mode now disables versioning - changed: no longer pluralizing collection names ending with a non-alphabetic
- changed: required Mixed paths now validate properly
- changed: toObject/toJSON no longer ignore
minimize
option - deprecated: query#nearSphere - use
Query#near()
with thespherical
option instead - deprecated: Query#center - use
Query#circle()
instead. - deprecated: Query#centerSphere - use
Query#circle()
instead. - deprecated: Query#slaveOk
- deprecated: promise#addBack
- deprecated: promise#addCallback
- deprecated: promise#addErrback
- deprecated: promise#complete
- deprecated: Inferred unsafe Query#update/remove
Mongoose now supports new geoSearch command in MongoDB.
To use geoSearch, you must have a haystack index defined, which can be done as shown below:
var schema = new Schema({
pos : [Number],
complex : {},
type: String
});
schema.index({ "pos" : "geoHaystack", type : 1},{ bucketSize : 1});
mongoose.model('Geo', schema);
You can use the geoSearch
command much like you would use mapreduce()
.
Geo.geoSearch({ type : "place" }, { near : [9,9], maxDistance : 5 }, function (err, results, stats) {
console.log(results);
});
For information on the required options and parameters, please see the MongoDB docs
We now have support for the geoNear
command.
To use geoNear
, your model must have a 2d
or 2dsphere
index. For more information on these, see the MongoDB docs
var schema = new Schema({
pos : [Number],
type: String
});
schema.index({ "pos" : "2dsphere"});
mongoose.model('Geo', schema);
Once that has been defined, you can access the geoNear method on the model.
Geo.geoNear([9,9], { spherical : true, maxDistance : .1 }, function (err, results, stats) {
console.log(results);
});
For more information on the geoNear
command and required options, see the MongoDB docs
mquery is a fluent query building library for MongoDB. It's API is very similar to Mongoose's old query syntax, but it does have some improvements and is much more standardized. Any query you can construct with mquery, can now be constructed with Mongoose using the same syntax. At the same time, all old Mongoose syntax that mquery does not support should still be available (with 2 notable exceptions, see below). If any old syntax (that was using public APIs-- we do not guarantee support for any internal APIs used, and they have changed) does not work, please submit a report.
This change reduces the size of the Mongoose code base by a couple thousand lines. Additionally, it allows for you to integrate your own query engine should you desire.
In 3.6.x, a within query was constructed like this:
Model.where('loc').within.geometry(geojsonPoly);
The syntax has now changed to:
Model.where('loc').within().geometry(geojsonPoly);
A shim to revert these changes and return to the old syntax can be found here
In 3.6.x, a within query was constructed like this:
Model.where('line').intersects.geometry(geojsonLine)
The syntax has now changed to:
Model.where('line').intersects().geometry(geojsonLine)
A shim to revert these changes and return to the old syntax can be found here
geometry
no longer accepts a path argument. This makes the Mongoose query building much more semantically consistent.
A query like this:
Geo.within().geometry('loc', { type: 'Polygon', coordinates : coords });
Now turns into:
Geo.where('loc').within().geometry({ type: 'Polygon', coordinates : coords });
If you are running a version of MongoDB < 2.4 this affects you.
In MongoDB 2.4, $within
was deprecated and $geoWithin
was introduced which is 100% backward compatible with $within
. For .within()
queries, we now internally use $geoWithin
by default. However, you can change this to remain backward compatible with old releases of MongoDB and force Mongoose to continue using $within
by setting a flag. To toggle this flag, simply change the use$geoWithin
property on the Query
object.
var mongoose = require('mongoose');
mongoose.Query.use$geoWithin = false;
There is now an API for building aggregation pipelines, courtesy of njoyard. It works very similar to query building in Mongoose.
Model.aggregate({ $match: { age: { $gte: 21 }}}).unwind('tags').exec(cb);
All items returned will be plain objects and not mongoose documents.
The Model.aggregate()
argument signature has changed; now no longer accepting an options argument. Now that we are wrapping the driver's aggregate
method, we can provide a cleaner, less error prone approach through the use of the new aggregation builders read method.
// old way of specifying the read preference option
Model.aggregate({ $match: {..}}, .. , { readPreference: 'primary' }, callback);
// new approach
Model.aggregate({ $match: {..}}, ..).read('primary').exec(callback)
Say you have a common set of criteria you query for repeatedly throughout your application, like selecting only accounts that have not made a payment in the past 30 days:
Accounts.find({ lastPayment: { $lt: 30DaysAgo }}).select('name lastPayment').exec(callback)
To make managing these common sets of query criteria easier, Mongoose now supports creating reusable base queries.
var Late = Accounts.find({ lastPayment: { $lt: 30DaysAgo }}).select('name lastPayment').toConstructor();
Now, utilizing the Late
query constructor, we can clean up our codebase a bit:
Late().exec(callback)
Since Late
is a query constructor, not just an instance, we can make modifications to the returned query instance without any impacts to the original it was based on.
Late().where({ active: true }).lean().exec(callback)
And since Late
is a stand-alone subclass of mongoose.Query
, we can make customizations to the constructor itself without impacting any global queries either.
Late.prototype.startsWith = function (prefix) {
this.where({ name: new RegExp('^' + prefix) });
return this;
}
Late().startsWith('Fra').exec(callback)
All built-in validators now support error message customization. Error messages can be defined in the schema or globally.
// in the schema
new Schema({ x: { type: Number, min: [0, '{VALUE} is less than the minimum required ({MIN}) for path: {PATH}'] }})
// globally
var messages = require('mongoose').Error.messages;
messages.String.enum = "Your custom message for {PATH}.";
As seen in the above example, error messages now also support templating (if that's really a term):
var schema = new Schema({ name: { type: String, match: [/^T/, '{PATH} must start with "T". You provided: `{VALUE}`'] }});
var M = mongoose.model('M', schema);
var m = new M({ name: 'Nope' });
m.save(function (err) {
console.log(err);
// prints..
// { message: 'Validation failed',
// name: 'ValidationError',
// errors:
// { name:
// { message: 'name must start with "T". You provided: `Nope`',
// name: 'ValidatorError',
// path: 'name',
// type: 'regexp',
// value: 'Nope' } } }
})
The following tokens are replaced as follows:
{PATH}
is replaced with the invalid document path
{VALUE}
is replaced with the invalid value
{TYPE}
is replaced with the validator type such as "regexp", "min", or "user defined"
{MIN}
is replaced with the declared min value for the Number.min validator
{MAX}
is replaced with the declared max value for the Number.max validator
One change to be aware of for custom validators is that in previous versions, the error message was assigned to the type
property of the ValidationError
. Going forward, the type
property is assigned the value user defined
.
function validator () { return false }
Schema({ name: { type: String, validate: validator }})
doc.save(function (err) {
console.log(err.type) // 'user defined'
})
See issue #747
The mongoose query builders near()
method now supports passing GeoJSON objects as well:
Test.where('loc').near({ center: { type: 'Point', coordinates: [11,20]}, maxDistance : 1000000 })
Previously, Query#remove()
always executed the operation and did not accept query conditions. To be consistent with other Query
operations, query conditions and/or callback are now accepted. The operation is executed only if the callback is passed.
query.remove(conds, fn); // executes
query.remove(conds) // no execution
query.remove(fn) // executes
query.remove() // no execution
To execute a remove query without passing a callback, use remove()
followed by exec()
.
var promise = query.remove().exec()
Previousy, when a schema and its subdocument both had transforms defined, the top-level transform was used for the subdocuments as well. This behavior has now been corrected.
See #1412
Previously, subdocuments and populated child documents toJSON
options were ignored when calling parent.toJSON()
and the parents toJSON
options were used instead. This behavior has now been corrected.
See #1376
When { w: 0 }
or safe: false
is used in your schema to force non-acknowledged writes (rare but some people use this), no response is received from MongoDB causing an error to occur in the versioning handler. Because mongoose doesn't know if the write was successful under these conditions, versioning must be disabled as well. In 3.8 we now disable it automatically when using non-acknowledged writes.
See #1520
Mongoose has historically pluralized collection names. While some feel its a nice feature it often has lead to confusion when later exploring the database in the MongoDB shell. This release includes optionally disabling pluralization of collection names.
// disable globally
mongoose.set('pluralization', false)
// disable at the model/collection level
var schema = Schema({..}, { pluralization: false })
Eventually, in v4, all pluralization will be removed.
See #1707
In previous versions of mongoose, a model named "plan_" would get pluralized to "plan_s". This is no longer the case. Now, if the name ends with a non-alphabetic character, it will not be pluralized.
See #1703
Previously we were unable to make Mixed types required. This has been fixed.
See #1722
We have discontinued support of running mongoose on node < 0.8.x. While we made no actual code changes related to this announcement, the node project itself isn't supporting it, so neither will we.
Mongoose connection objects can now share their underlying connection pool across multiple databases. This helps avoid excessive numbers of open connections when working with multiple databases.
For example, an application working with 5 databases in a 3 node replica-set previously required creating 5 separate mongoose connection instances. Since each mongoose connection instance opens 6 internal connections (by default) to each node of your replica set, a total of 90 (635) connections were opened. Now, utilizing connection.useDb(dbname)
, we are able to just reuse the existing 18 (6*3) connections.
// create a connection to a database
var db = mongoose.createConnection(uri);
// use another database without creating additional connections
var db2 = db.useDb('someDbName');
// proceed as you would normally
var Model1 = db.model('Model1', m1Schema);
var Model2 = db2.model('Model2', m2Schema);
Since both db
and db2
use the same connection pool, when either of them close, both will be closed. Same goes for open()
and other events like connecting
, disconnecting
, etc.
See 1124.
You can now pass the overwrite option to a model in order to override default update semantics.
overwrite
is passed in via setOptions
var q = Model.where({ _id: id }).setOptions({ overwrite: true });
This will cause the update to ignore the built-in protections in Mongoose and simply send the update as-is. This means that passing {}
to update with overwrite will result in an empty document. This feature is useful when you need to override the default $set
update behavior.
Model.findOne(id, function (err, doc) {
console.log(doc); // { _id: 108, name: 'cajon' })
Model.where({ _id : id }).setOptions({ overwrite: true }).update({ changed: true }, function (err) {
base.findOne(function (err, doc) {
console.log(doc); // { _id: 108, changed: true }) - the doc was overwritten
});
});
})
The following model methods now return promises
:
- model.mapReduce()
- model.ensureIndexes()
- model.populate()
- model.create()
- model.geoSearch() new
- model.geoNear() new
While nearSphere
still exists in the API, it will be removed in a future release. In its place, use the near()
method with the { spherical : true }
option.
IE.
Model.nearSphere({ center : [10,10] });
// becomes...
Model.near({ center : [10,10], spherical : true })
The new query engine will now throw an error when nonsensical or invalid combinations are attempted. Some of these include:
- using
slice
withdistinct
- using
limit
withdistinct
- using
mod
without a precedingwhere
- using
maxScan
withcount
- and more
Important to note that these types of queries never worked before. They would either not return results that were truly representative of the query or would fail silently.
Please use Query#circle instead.
var area = { center: [50, 50], radius: 10, unique: true }
query.where('loc').within().circle(area)
Please use Query#circle instead with the spherical
option set.
var area = { center: [50, 50], radius: 10, unique: true, spherical: true }
query.where('loc').within().circle(area)
Use Query#read instead with secondaryPreferred
.
Use Promise#onResolve instead.
Use Promise#onFulFill instead.
Use Promise#onReject instead.
Use Promise#fulfill instead.
Callbacks passed to document.remove()
now receive the document which was removed.
model.findOne(function (err, doc1) {
doc1.remove(function (err, doc2) {
// doc2 == doc1
})
})
MongooseBuffer
has a new subtype()
method which sets it's subtype
option and marks the buffer modified if necessary.
var bson = require('bson');
doc.buf.subtype(bson.BSON_BINARY_SUBTYPE_UUID)
See this page for more detail.
Query#tailable()
now supports the awaitdata option.
query.tailable({ awaitdata: true })
Previously, update()
and remove()
would execute an unsafe update/delete if no callback was passed. This has been changed in this release. You now must either pass a callback or explicitly tell Mongoose to execute the query in an unsafe manner. This can be done by passing true
for the callback value to the query.
query.update({...},{...}, true);
query.remove(true);