Rake mongoose

Hacker - a man who steps on a rake that is hidden in a barn and locked

Mongoose is the most popular javascript mongodb module. The examples on the site allow you to quickly and successfully start using it, however mongoose has a number of unexpected features that can cause the programmer to begin to tear out the hair on his head. It is about these features that I am going to tell.

1. Naming collections


I'll start with the most harmless and easily detectable features. You create a model:

var mongoose = require('mongoose');
var User = new mongoose.Schema({
	email: String,
	password: String,
	data: {
		birthday: {
			type: Date,
			default: Date.now
		},
		status: {
			type: String,
			default: 'active',
			enum: ['active', 'unactive']
		},
		mix: {
			type: mongoose.Schema.Types.Mixed,
			default: {}
		}
	}
});
module.exports = mongoose.model('User', User);

Create a user:

var user = new User({email: 'test@test.com', password: '12345'});
user.save(ok(function() {
	console.log('ok');
}));

If we now execute the “show collections” command in the mongodb console, we will see that the users collection has been created. Those. mongoose when creating collections converts their names to lowercase and plural.

2. Overriding the toJSON () method


Suppose we needed to modify our model instance by introducing an attribute not described in the model into it:

User.findOne({email: 'test@test.com'}, ok(function(user) {
	user.someArea = 'custom value';
	console.log(user.someArea);
	console.log('====');
	console.log(user);
}));

In the console we will see (instead of console.log it can be used res.json):

custom value
====
{ __v: 0,
  _id: 54fc8c22c90fb7dd025eee7c,
  email: 'test@test.com',
  password: '12345',
  data: 
   { mix: {},
     status: 'active',
     birthday: Thu Mar 12 2015 23:46:06 GMT+0300 (MSK) } }

As you can see, the object has an attribute someArea, but when it dumped to the console, it suddenly disappeared somewhere. The thing is that mongoose overrides the toJson method and all fields not described in the model diagram are thrown out. A situation may arise when we add an attribute to an object and give it to the client, but the attribute does not reach the client. In order for it to successfully hit the client, you need to modify not a mongoose object. For these purposes, model instances have a toObject method that returns a native-Object, which you can modify as you like and nothing will be lost from it.

3. Comparison of _id


It might seem that _id is of type String, however, this is not at all the case. _id - an object and compare the identifiers of instances of mongoose-models as objects. Example:

User.findOne({email: 'test@test.com'}, ok(function(user1) {
	User.findOne({email: 'test@test.com'}, ok(function(user2) {
		log(user1._id == user2._id); // false
		log(user1._id.equals(user2._id)); // true
		log(user1._id.toString() == user2._id.toString()); // true
	}));
}));


4. Saving mixed fields


We have one field with type mixed in the scheme, this is data.mix. If we change it, for example:

User.findOne({email: 'test@test.com'}, ok(function(user) {
	user.data.mix = {msg: 'hello world'};
	user.save(ok(function() {
		console.log('ok');
	}));
}));

, then the changes will successfully get into the database.

However, if we now make a change inside data.mix, then the changes will not be made to the database.

User.findOne({email: 'test@test.com'}, ok(function(user) {
	user.data.mix.msg = 'Good bye';
	user.save(ok(function() {
		log(user);
	}));
}));

The user object containing our modification will be displayed in the console, and a query to the database will show that the user has not been changed. In order for the changes to get into the database, you need to notify mongoose before the save method that we modified the mixed field:

user.markModified('data.mix');

The same operation must be performed with objects of the Date type when they are modified by the built-in methods (setMonth, setDate, ...), this is stated in the documentation

5. Defaults for arrays


Suppose that when describing the model diagram we decided that in our field should be an array of objects. We need to register defaults for the array itself and for all objects nested in it. Mongoose uses a special type key for this:

var Lib = new mongoose.Schema({
	userId: mongoose.Schema.ObjectId,
	images: {
		// правила валидации и дефолты для каждого из полей объекта массива images
		type: [{
			uploaded: {
				type: Date,
				default: Date.now
			},
			src: String
		}],
		// значение по-умолчанию для поля images
		default: [{uploaded: new Date(2012, 11, 22), src: '/img/default.png'}]
	}
});
module.exports = mongoose.model('Lib', Lib);

Similarly, using the type keyword, we can create multi-level defaults for objects.

6. Streaming update


Sometimes it is necessary to update a very large collection of code. Downloading the entire collection is out of memory. You can manually set limits, load documents in batches and update, but mongoose has very convenient interfaces for this operation - streams.

e.m.users.find({}).stream()
	.on('data', function(user) {
		var me = this;
		me.pause();
		// выполняем надо пользователем очень хитрые асинронные манипуляции
		user.save(function(err) {
			me.resume(err);
		});
	})
	.on('error', function(err) {
		log(err);
	})
	.on('close', function() {
		log('All done');
	});

(However, if we extract users in batches, edit and save through async.parallel, this will work out a little faster, but less readable).

6. Disabling automatic index building


Mongodb uses unique indexes to ensure field uniqueness. With mongoose, they are very easy to create. Mongoose generally creates a high level of abstraction when working with data. However, our shortcomings are a continuation of our advantages and many forget to turn off the automatic creation of indexes in the production mode, although this is clearly stated in the official documentation .
In mongoose for these purposes there is even a special flag {autoIndex: false}, which must be specified when describing the data scheme:

var User = new mongoose.Schema({
	email: {
		type: String,
		unique: true,
		required: true
	},
	password: String
}, {
	autoIndex: process.env('mode') == 'development'
});

Now, automatic index building will only work in development mode.

7. Do not forget about the reserved keys


Perhaps not everyone is faced with a similar problem, but still I will pay attention to the fact that mongoose objects have a set of reserved names for attributes, they are given in the documentation . I had to deal with the naming of attributes with keys from the reserved list, after which it was necessary to scrub calls to these keys throughout the code. Mongoose does not swear at the use of reserved keys. The rake that I stepped on in this list of keys turned out to be the options key.

Also popular now: