- Define virtual properties for a model
- Modify how a model is serialized to JSON
- Define a static method on the Model
-
We have so far seen how we use mongoose to set up our database and to make our models.
-
We have explored the different relationships and set up associations
-
We have queried our objects and seen how we can do that
-
How can we take this to the next level?
- The big thing we want to focus on are concepts in mongoose.js called
statics
andmethods
- Instances of Models are documents. Documents have many of their own built-in instance methods.
- We may also define our own custom document instance methods too.
- The best way to think about it, is
methods
are instance methods on an object, andstatics
can be thought of as class methods
Here is an example of an instance method
var animalSchema = new Schema({ name: String, type: String });
// assign a function to the "methods" object of our animalSchema
animalSchema.methods.findSimilarTypes = function(cb) {
return this.model('Animal').find({ type: this.type }, cb);
};
Now all of our animal instances have a findSimilarTypes method available to it.
var Animal = mongoose.model('Animal', animalSchema);
var dog = new Animal({ type: 'dog' });
dog.findSimilarTypes(function(err, dogs) {
console.log(dogs); // woof
});
Lets look at static methods
Adding static methods to a Model is simple as well. Continuing with our animalSchema:
// assign a function to the "statics" object of our animalSchema
animalSchema.statics.findByName = function(name, cb) {
return this.find({ name: new RegExp(name, 'i') }, cb);
};
var Animal = mongoose.model('Animal', animalSchema);
Animal.findByName('fido', function(err, animals) {
console.log(animals);
});
- Virtuals are document properties that you can get and set but that do not get persisted to MongoDB.
- The getters are useful for formatting or combining fields, while setters are useful for de-composing a single value into multiple values for storage.
// define a schema
var personSchema = new Schema({
name: {
first: String,
last: String
}
});
// compile our model
var Person = mongoose.model('Person', personSchema);
// create a document
var axl = new Person({
name: { first: 'Axl', last: 'Rose' }
});
Suppose you want to print out the person's full name. You could do it yourself:
console.log(axl.name.first + ' ' + axl.name.last); // Axl Rose
But concatenating the first and last name every time can get cumbersome. And what if you want to do some extra processing on the name, like removing diacritics?. A virtual property getter lets you define a fullName property that won't get persisted to MongoDB.
personSchema.virtual('fullName').get(function () {
return this.name.first + ' ' + this.name.last;
});
Now, mongoose will call your getter function every time you access the fullName property:
console.log(axl.fullName); // Axl Rose
note: If you use toJSON() or toObject() (or use JSON.stringify() on a mongoose document) mongoose will not include virtuals by default. Pass { virtuals: true } to either toObject() or toJSON().
- lets code along now. I am adding this code in here so you can refer back to this if you choose to implement some of these things inside your own project.
$ mkdir advanced-mongoose
$ cd advanced-mongoose
$ npm init -f
$ npm install mongoose
$ touch food.js
$ subl .
- now inside
food.js
, let's add our schema
var mongoose = require('mongoose');
var foodSchema = new mongoose.Schema({
name: {type: String, required: true},
group: {type: String, required: true, enum: ['Veg', 'Dairy', 'Meat', 'Bread']},
shelfLife: {type: Number, required: true}
});
module.exports = mongoose.model('Food', foodSchema);
- now inside our node REPL, let's play with this
- start mongo somewhere on your machine with
mongod
, then in a seperate window typenode
to get into the repl
$ node
> var mongoose = require('mongoose')
> mongoose.connect('mongodb://localhost/food’)
> var Food = require('./food')
> Food.create({name: 'Tomato', group: 'Veg', shelfLife: 5})
> Food.find({}).exec((err,food) => console.log(food))
- let's now create a
database.js
file so we can save time when you have to exit Node and restart it after modifying the schema
var mongoose = require('mongoose')
mongoose.connect('mongodb://localhost/food')
- now inside our
food.js
before themodule.exports
, let's add this code
// add before module.exports
foodSchema.virtual('description').get(function() {
return `${this.name} (${this.group}) - Good for ${this.shelfLife} days`;
});
module.exports = mongoose.model('Food', foodSchema);
- now exit node, turn it back on with
node
, then let's test
$ node
> require('./database')
> var Food = require('./food')
> Food.findOne({}).exec((err, item) => console.log(item.description))
- now in our
food.js
before module exports, lets add this
foodSchema.statics.findByGroup = function(group, cb) {
return this.find({group: group}, cb);
};
- now let's restart node, and type in this to test the static
$ node
> require('./database')
> var Food = require('./food')
> Food.findByGroup('Veg', (err, food) => console.log(food))
- lets add one final piece of code.
- much like rails does for us, we do not want to include our user's password in our JSON output
- lets see what that could look like
- at the bottom of
food.js
before the module exports, let's add this
foodSchema.set('toJSON', {
virtuals: true, // serialize virtuals
transform: function(doc, ret, options) {
// add an id property
ret.id = ret._id;
// remove a property like password or whatever
delete ret._id;
return ret;
}
});
- now let's restart node and test
$ node
> require('./database')
> var Food = require('./food')
> var item
> Food.findOne({}, function(err, food) { item = food })
> item // outputs actual document
> item.toJSON() // shows how document will be serialized when sent to client
It is very useful. Thank's.